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 "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)
: 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;
Height = screenHeight;
EyeSize = eyeSize;
@@ -26,6 +37,7 @@ Face::Face(uint16_t screenWidth, uint16_t screenHeight, uint16_t eyeSize)
LeftEye.IsMirrored = true;
Behavior.Clear();
Behavior.Timer.Start();
}
@@ -69,28 +81,16 @@ void Face::Update() {
}
void Face::Draw() {
// Clear the display
u8g2.clearBuffer();
// 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);
// Draw left eye
LeftEye.CenterX = CenterX - EyeSize / 2 - EyeInterDistance;
LeftEye.CenterY = CenterY;
LeftEye.Draw();
// Draw right eye
RightEye.CenterX = CenterX + EyeSize / 2 + EyeInterDistance;
RightEye.CenterY = CenterY;
RightEye.Draw();
//u8g2.setDisplayRotation(U8G2_R3);
//_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);
// Transfer the redrawn buffer to the display
u8g2.sendBuffer();
}

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) {
Timer.Start();
Clear();
Emotions[(int)eEmotions::Normal] = 2.0;
Emotions[(int)eEmotions::Happy] = 1.0;
Emotions[(int)eEmotions::Normal] = 1.0;
}
void FaceBehavior::SetEmotion(eEmotions emotion, float value) {
@@ -45,13 +44,12 @@ eEmotions FaceBehavior::GetRandomEmotion() {
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;
for (int emotion = 0; emotion < eEmotions::EMOTIONS_COUNT; emotion++) {
if (Emotions[emotion] == 0) continue;
acc += Emotions[emotion] * (eEmotions::EMOTIONS_COUNT - 1) / sum_of_weight;
acc += Emotions[emotion];
if (rand <= acc) {
return (eEmotions)emotion;
}

View File

@@ -27,6 +27,7 @@ void LookAssistant::LookAt(float x, float y)
float scaleY_x;
float scaleY_y;
// What is this witchcraft...?!
moveX_x = -25 * x;
moveY_x = -3 * x;
moveY_y = 20 * y;
@@ -51,18 +52,14 @@ void LookAssistant::LookAt(float x, float y)
_face.LeftEye.Transformation.Animation.Restart();
}
void LookAssistant::Update()
{
void LookAssistant::Update() {
Timer.Update();
if (Timer.IsExpired())
{
if (Timer.IsExpired()) {
Timer.Reset();
auto x = random(-50, 50);
auto y = random(-50, 50);
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
#include "Face.h"
// Built-in Arduino I2C library
#include <Wire.h>
#include "Common.h"
// Defines all face functionality
#include "Face.h"
// DEFINES
#define WIDTH 128 //180
#define HEIGHT 64 //240
#define EYE 40 //40
// CONSTANTS
const byte blinkPin = 16;
// GLOBALS
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data= */ 5);
Face *face;
String emotionName[] = {
"Normal",
@@ -45,50 +43,96 @@ String emotionName[] = {
};
void setup(void) {
// Create a serial connection
Serial.begin(115200);
Serial.println(__FILE__ __DATE__);
face = new Face(WIDTH, HEIGHT, EYE);
// face->Expression.GoTo_Happy();
face->Behavior.Clear();
face->Behavior.SetEmotion(eEmotions::Glee, 1.0);
pinMode(blinkPin, INPUT_PULLUP);
// Unlike almost every other Arduino application, I2C address scanner etc., u8g2 library
// requires 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
// Create a new face
face = new Face(/* screenWidth = */ 128, /* screenHeight = */ 64, /* eyeSize = */ 40);
// Assign the current expression
face->Expression.GoTo_Normal();
// Assign a weight to each emotion that can be chosen
face->Behavior.SetEmotion(eEmotions::Normal, 1.0);
//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(){
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 uint8_t emotionno = 0;
static uint8_t emotionflag = 0;
static uint8_t changeEmotionFlag = 0;
static uint8_t n = 0;
if(millis() - counter > 6000) {
counter = millis();
emotionno = n++;
if(n >= EMOTIONS_COUNT) n = 0;
emotionflag = 1;
changeEmotionFlag = 1;
}
if(emotionflag) {
emotionflag = 0;
// Update the eyes' emotion
if(changeEmotionFlag) {
// Clear any previously assigned weights
face->Behavior.Clear();
// Assign weight of 1.0 to the next emotion in the list
face->Behavior.SetEmotion((eEmotions)emotionno, 1.0);
// 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
// Print the new emotion to the serial monitor
Serial.println(emotionName[emotionno]);
// Reset the flag
changeEmotionFlag = 0;
}
*/
face->Update();
delay(10);
//delay(10);
}