Added manual look and blink control

Added ability to override "random" look, blink, and behaviour change operation, so that look direction and blink can be set with, e.g. a joystick control.
This commit is contained in:
alastaira
2023-09-18 15:47:47 +01:00
parent 6392643a2b
commit 17412d86cc
4 changed files with 100 additions and 61 deletions

View File

@@ -14,9 +14,20 @@ You should have received a copy of the GNU Affero General Public License along w
#include "Face.h" #include "Face.h"
#include "Common.h" #include "Common.h"
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data= */ 5);
Face::Face(uint16_t screenWidth, uint16_t screenHeight, uint16_t eyeSize) Face::Face(uint16_t screenWidth, uint16_t screenHeight, uint16_t eyeSize)
: LeftEye(*this), RightEye(*this), Blink(*this), Look(*this), Behavior(*this), Expression(*this) : LeftEye(*this), RightEye(*this), Blink(*this), Look(*this), Behavior(*this), Expression(*this) {
{
// Unlike almost every other Arduino library (and the I2C address scanner script etc.)
// u8g2 uses 8-bit I2C address, so we shift the 7-bit address left by one
u8g2.setI2CAddress(0x3C<<1);
u8g2.begin();
u8g2.clearBuffer(); // clear the internal memory
u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
u8g2.drawStr(0,10,"Hello World!"); // write something to the internal memory
u8g2.sendBuffer(); // transfer internal memory to the display
Width = screenWidth; Width = screenWidth;
Height = screenHeight; Height = screenHeight;
EyeSize = eyeSize; EyeSize = eyeSize;
@@ -26,6 +37,7 @@ Face::Face(uint16_t screenWidth, uint16_t screenHeight, uint16_t eyeSize)
LeftEye.IsMirrored = true; LeftEye.IsMirrored = true;
Behavior.Clear();
Behavior.Timer.Start(); Behavior.Timer.Start();
} }
@@ -69,28 +81,16 @@ void Face::Update() {
} }
void Face::Draw() { void Face::Draw() {
// Clear the display
u8g2.clearBuffer(); u8g2.clearBuffer();
// Draw left eye
// Only clear the section of the screen with the eyes - not the text underneath!
//u8g2.setDrawColor(0);
//u8g2.drawBox(20, 0, 64, 128);
//u8g2.setDrawColor(1);
LeftEye.CenterX = CenterX - EyeSize / 2 - EyeInterDistance; LeftEye.CenterX = CenterX - EyeSize / 2 - EyeInterDistance;
LeftEye.CenterY = CenterY; LeftEye.CenterY = CenterY;
LeftEye.Draw(); LeftEye.Draw();
// Draw right eye
RightEye.CenterX = CenterX + EyeSize / 2 + EyeInterDistance; RightEye.CenterX = CenterX + EyeSize / 2 + EyeInterDistance;
RightEye.CenterY = CenterY; RightEye.CenterY = CenterY;
RightEye.Draw(); RightEye.Draw();
// Transfer the redrawn buffer to the display
//u8g2.setDisplayRotation(U8G2_R3); u8g2.sendBuffer();
//_buffer.setPivot(_buffer.width(), 0);//_buffer.height()/2);
//_buffer.pushRotated(270, -1);
//_buffer.pushSprite(0, 0);
u8g2.sendBuffer(); // transfer internal memory to the display
//u8g2.setDisplayRotation(U8G2_R0);
} }

View File

@@ -17,8 +17,7 @@ You should have received a copy of the GNU Affero General Public License along w
FaceBehavior::FaceBehavior(Face& face) : _face(face), Timer(500) { FaceBehavior::FaceBehavior(Face& face) : _face(face), Timer(500) {
Timer.Start(); Timer.Start();
Clear(); Clear();
Emotions[(int)eEmotions::Normal] = 2.0; Emotions[(int)eEmotions::Normal] = 1.0;
Emotions[(int)eEmotions::Happy] = 1.0;
} }
void FaceBehavior::SetEmotion(eEmotions emotion, float value) { void FaceBehavior::SetEmotion(eEmotions emotion, float value) {
@@ -45,13 +44,12 @@ eEmotions FaceBehavior::GetRandomEmotion() {
return eEmotions::Normal; return eEmotions::Normal;
} }
float rand = random(0, 1000 * eEmotions::EMOTIONS_COUNT) / 1000.0; float rand = random(0, 1000 * sum_of_weight) / 1000.0;
float acc = 0; float acc = 0;
for (int emotion = 0; emotion < eEmotions::EMOTIONS_COUNT; emotion++) { for (int emotion = 0; emotion < eEmotions::EMOTIONS_COUNT; emotion++) {
if (Emotions[emotion] == 0) continue; if (Emotions[emotion] == 0) continue;
acc += Emotions[emotion] * (eEmotions::EMOTIONS_COUNT - 1) / sum_of_weight; acc += Emotions[emotion];
if (rand <= acc) { if (rand <= acc) {
return (eEmotions)emotion; return (eEmotions)emotion;
} }

View File

@@ -27,6 +27,7 @@ void LookAssistant::LookAt(float x, float y)
float scaleY_x; float scaleY_x;
float scaleY_y; float scaleY_y;
// What is this witchcraft...?!
moveX_x = -25 * x; moveX_x = -25 * x;
moveY_x = -3 * x; moveY_x = -3 * x;
moveY_y = 20 * y; moveY_y = 20 * y;
@@ -51,18 +52,14 @@ void LookAssistant::LookAt(float x, float y)
_face.LeftEye.Transformation.Animation.Restart(); _face.LeftEye.Transformation.Animation.Restart();
} }
void LookAssistant::Update() void LookAssistant::Update() {
{
Timer.Update(); Timer.Update();
if (Timer.IsExpired()) if (Timer.IsExpired()) {
{
Timer.Reset(); Timer.Reset();
auto x = random(-50, 50); auto x = random(-50, 50);
auto y = random(-50, 50); auto y = random(-50, 50);
LookAt((float)x / 100, (float)y / 100); LookAt((float)x / 100, (float)y / 100);
} }
} }

View File

@@ -11,17 +11,15 @@ You should have received a copy of the GNU Affero General Public License along w
****************************************************/ ****************************************************/
// INCLUDES // INCLUDES
#include "Face.h" // Built-in Arduino I2C library
#include <Wire.h> #include <Wire.h>
#include "Common.h" // Defines all face functionality
#include "Face.h"
// DEFINES // CONSTANTS
#define WIDTH 128 //180 const byte blinkPin = 16;
#define HEIGHT 64 //240
#define EYE 40 //40
// GLOBALS // GLOBALS
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data= */ 5);
Face *face; Face *face;
String emotionName[] = { String emotionName[] = {
"Normal", "Normal",
@@ -45,50 +43,96 @@ String emotionName[] = {
}; };
void setup(void) { void setup(void) {
// Create a serial connection
Serial.begin(115200); Serial.begin(115200);
Serial.println(__FILE__ __DATE__); Serial.println(__FILE__ __DATE__);
face = new Face(WIDTH, HEIGHT, EYE);
// face->Expression.GoTo_Happy(); pinMode(blinkPin, INPUT_PULLUP);
face->Behavior.Clear();
face->Behavior.SetEmotion(eEmotions::Glee, 1.0);
// Unlike almost every other Arduino application, I2C address scanner etc., u8g2 library // Create a new face
// requires 8-bit I2C address, so we shift the 7-bit address left by one. face = new Face(/* screenWidth = */ 128, /* screenHeight = */ 64, /* eyeSize = */ 40);
u8g2.setI2CAddress(0x3C<<1); // Assign the current expression
u8g2.begin(); face->Expression.GoTo_Normal();
u8g2.clearBuffer(); // clear the internal memory
u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font // Assign a weight to each emotion that can be chosen
u8g2.drawStr(0,10,"Hello World!"); // write something to the internal memory face->Behavior.SetEmotion(eEmotions::Normal, 1.0);
u8g2.sendBuffer(); // transfer internal memory to the display //face->Behavior.SetEmotion(eEmotions::Angry, 1.0);
//face->Behavior.SetEmotion(eEmotions::Sad, 1.0);
// Automatically select a random behaviour (based on the weight assigned to each emotion)
face->RandomBehavior = true;
// Automatically blink
face->RandomBlink = true;
// Set blink rate
face->Blink.Timer.SetIntervalMillis(4000);
//face->Blink.Timer.Stop();
// Automatically choose a new random direction to look
face->RandomLook = true;
}
float mapFloat(float x, float in_min, float in_max, float out_min, float out_max) {
// Check if x is outside the input range
if (x <= in_min) return out_min;
if (x >= in_max) return out_max;
// Calculate the proportion of x relative to the input range
float proportion = (x - in_min) / (in_max - in_min);
// Map the proportion to the output range and return the result
return (proportion * (out_max - out_min)) + out_min;
} }
void loop(){ void loop(){
static int lastMoveTime;
// To avoid making eyes too twitchy (and to allow time for previous animation to end), we only recalculate new position every 500ms
if(millis() - lastMoveTime > 500) {
int yRaw = analogRead(25);
int xRaw = analogRead(26);
float y = mapFloat(yRaw, 0, 4095, 1.0, -1.0);
float x = mapFloat(xRaw, 0, 4095, 1.0, -1.0);
face->Look.LookAt(x, y);
lastMoveTime = millis();
}
if(!digitalRead(blinkPin)){
face->DoBlink();
}
/*
// Use this code to set a particular emotion from a button
int32_t potValue = analogRead(15);
float anger = map(potValue, 0, 4095, 0.0, 2.0);
face->Behavior.SetEmotion(eEmotions::Angry, anger);
*/
/*
// Use this code to cycle automatically through all emotions
static uint32_t counter = millis(); static uint32_t counter = millis();
static uint8_t emotionno = 0; static uint8_t emotionno = 0;
static uint8_t emotionflag = 0; static uint8_t changeEmotionFlag = 0;
static uint8_t n = 0; static uint8_t n = 0;
if(millis() - counter > 6000) { if(millis() - counter > 6000) {
counter = millis(); counter = millis();
emotionno = n++; emotionno = n++;
if(n >= EMOTIONS_COUNT) n = 0; if(n >= EMOTIONS_COUNT) n = 0;
emotionflag = 1; changeEmotionFlag = 1;
} }
if(emotionflag) { if(changeEmotionFlag) {
emotionflag = 0; // Clear any previously assigned weights
// Update the eyes' emotion
face->Behavior.Clear(); face->Behavior.Clear();
// Assign weight of 1.0 to the next emotion in the list
face->Behavior.SetEmotion((eEmotions)emotionno, 1.0); face->Behavior.SetEmotion((eEmotions)emotionno, 1.0);
// Print the new emotion to the serial monitor
// Draw a text string
//u8g2.setCursor((WIDTH-(emotionName[emotionno].length() * 5)) / 2, HEIGHT);
//u8g2.print(emotionName[emotionno]);
//u8g2.sendBuffer(); // transfer internal memory to the display
Serial.println(emotionName[emotionno]); Serial.println(emotionName[emotionno]);
// Reset the flag
changeEmotionFlag = 0;
} }
*/
face->Update(); face->Update();
delay(10); //delay(10);
} }