mirror of
https://github.com/playfultechnology/esp32-eyes.git
synced 2025-12-06 17:15:42 -08:00
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:
38
Face.cpp
38
Face.cpp
@@ -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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
106
esp32-eyes.ino
106
esp32-eyes.ino
@@ -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);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user