#include <Adafruit_SSD1306.h>
#include "ActionSystem.h"
// One global action system
ActionSystem actions;
// =====================================================
// INPUT PINS (FAKE SENSORS)
// =====================================================
#define SHAKE_BTN 33
#define PICKUP_BTN 26
#define PUTDOWN_BTN 25
#define TOUCH_BTN 27
#define MENU_BTN 14
// =====================================================
// EYE CONFIG
// =====================================================
float eyeScale = 1.0;
const int BASE_EYE_W = 36;
const int BASE_EYE_H = 36;
const int BASE_RADIUS = 8;
const int BASE_SPACE = 10;
// =====================================================
// DISPLAY + EYES
// =====================================================
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#include "./FluxGarage_RoboEyes.h"
roboEyes eyes; // create eyes
// =====================================================
// CREATURE VALUES (CONTINUOUS)
// =====================================================
float energy = 0.6;
float irritation = 0.0;
float curiosity = 0.3;
// =====================================================
// SIMPLE STATE
// =====================================================
bool awake = false;
bool pickedUp = false;
unsigned long lastInteraction = 0;
bool menuIsOpen = false;
// =====================================================
// TIMERS
// =====================================================
unsigned long lastIrritationDecay = 0;
unsigned long lastCuriosityDecay = 0;
unsigned long lastEnergyDecay = 0;
unsigned long nextLookTime = 0;
// =====================================================
// SETUP
// =====================================================
void setup() {
Serial.begin(115200);
pinMode(SHAKE_BTN, INPUT_PULLUP);
pinMode(PICKUP_BTN, INPUT_PULLUP);
pinMode(PUTDOWN_BTN, INPUT_PULLUP);
pinMode(TOUCH_BTN, INPUT_PULLUP);
pinMode(MENU_BTN, INPUT_PULLUP);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for (;;);
}
eyes.begin(SCREEN_WIDTH, SCREEN_HEIGHT, 100);
applyBaseEyes();
eyes.setPosition(DEFAULT);
eyes.close();
}
void applyBaseEyes() {
eyes.setWidth(BASE_EYE_W, BASE_EYE_W);
eyes.setHeight(BASE_EYE_H, BASE_EYE_H);
eyes.setBorderradius(BASE_RADIUS, BASE_RADIUS);
eyes.setSpacebetween(BASE_SPACE);
eyes.setPosition(DEFAULT);
}
// =====================================================
// MAIN LOOP
// =====================================================
void loop() {
eyes.update();
actions.update();
readInputs();
updateCreatureValues();
updateSleepWake();
renderFace();
}
// =====================================================
// INPUT SYSTEM (EVENTS)
// =====================================================
void readInputs() {
if (digitalRead(SHAKE_BTN) == LOW) {
delay(200);
onShake();
}
if (digitalRead(PICKUP_BTN) == LOW && !pickedUp) {
delay(200);
onPickup();
}
if (digitalRead(PUTDOWN_BTN) == LOW && pickedUp) {
delay(200);
onPutDown();
}
if (digitalRead(TOUCH_BTN) == LOW) {
delay(200);
onTouch();
}
if (digitalRead(MENU_BTN) == LOW) {
delay(200);
openMenu();
}
}
// =====================================================
// INPUT EVENTS
// =====================================================
void onShake() {
wakeUp();
irritation += 0.3;
curiosity += 0.1;
actions.trigger(ACT_CONFUSED_SHAKE);
logCreatureState("SHAKE");
}
void onPickup() {
wakeUp();
pickedUp = true;
eyes.setPosition(N);
irritation = max(0.0, irritation - 0.2);
curiosity += 0.2;
actions.trigger(ACT_BLINK_BURST);
logCreatureState("PICKUP");
}
void onPutDown() {
eyes.setPosition(DEFAULT);
wakeUp();
pickedUp = false;
curiosity -= 0.1;
logCreatureState("PUT DOWN");
}
void onTouch() {
wakeUp();
if (irritation <= 0.7){
actions.trigger(ACT_LAUGHTER);
curiosity += 0.1;
irritation += 0.05;
} else {
actions.trigger(ACT_BLINK_BURST);
irritation += 0.1;
}
logCreatureState("TOUCH");
}
void openMenu() {
wakeUp();
menuIsOpen = !menuIsOpen;
if (menuIsOpen) {
logCreatureState("MENU OPEN");
eyeScale = 0.5; // 50% size
eyes.setWidth(BASE_EYE_W * eyeScale, BASE_EYE_W * eyeScale);
eyes.setHeight(BASE_EYE_H * eyeScale, BASE_EYE_H * eyeScale);
eyes.setBorderradius(BASE_RADIUS * eyeScale, BASE_RADIUS * eyeScale);
eyes.setSpacebetween(BASE_SPACE * eyeScale);
eyes.setPosition(N);
// anchor eyes to top
} else {
logCreatureState("MENU CLOSED");
eyeScale = 1.0;
eyes.setWidth(BASE_EYE_W, BASE_EYE_W);
eyes.setHeight(BASE_EYE_H, BASE_EYE_H);
eyes.setBorderradius(BASE_RADIUS, BASE_RADIUS);
eyes.setSpacebetween(BASE_SPACE);
eyes.setPosition(DEFAULT);
}
}
// =====================================================
// CREATURE UPDATE Irritation / Curiosity / Energy
// =====================================================
void updateCreatureValues() {
// irritation decay
if (irritation > 0 && millis() - lastIrritationDecay > 5000) {
irritation -= 0.1;
lastIrritationDecay = millis();
}
if (curiosity > 0 && millis() - lastCuriosityDecay > 2000){
curiosity -= 0.1;
lastCuriosityDecay = millis();
}
if (pickedUp && energy > 0 && millis() - lastEnergyDecay > 2000){
energy -= 0.01;
lastEnergyDecay = millis();
}
if (!awake && energy < 1 && millis() - lastEnergyDecay > 2000){
energy += 0.01;
lastEnergyDecay = millis();
}
irritation = constrain(irritation, 0.0, 1.0);
curiosity = constrain(curiosity, 0.0, 1.0);
energy = constrain(energy, 0.0, 1.0);
}
// =====================================================
// SLEEP / WAKE
// =====================================================
void updateSleepWake() {
if (awake && !pickedUp && millis() - lastInteraction > 10000) {
awake = false;
logCreatureState("Inactivity");
}
}
void wakeUp() {
awake = true;
lastInteraction = millis();
eyes.open();
}
// =====================================================
// FACE ENGINE (ONLY VISUALS)
// =====================================================
void renderFace() {
if (!awake) {
eyes.close();
return;
}
// base expression from values
if (irritation > 0.6) {
//eyes.setPosition(DEFAULT);
eyes.setCuriosity(false);
eyes.setMood(ANGRY);
eyes.setAutoblinker(OFF);
} else if ( energy > 0.8) {
eyes.setMood(HAPPY);
eyes.setAutoblinker(ON, 3, 1);
//idleLookAround();
} else if ( curiosity > 0.5) {
eyes.setCuriosity(true);
eyes.setMood(DEFAULT);
eyes.setAutoblinker(ON, 3, 2);
//idleLookAround();
} else {
//eyes.setPosition(DEFAULT);
eyes.setCuriosity(false);
eyes.setMood(DEFAULT);
eyes.setAutoblinker(ON, 5, 5);
//idleLookAround();
}
}
// =====================================================
// IDLE ACTION (SELF-INITIATED)
// =====================================================
void idleLookAround() {
if (millis() > nextLookTime && menuIsOpen) {
static const uint8_t dirs[] = { N,N, NE, NW};
eyes.setPosition(dirs[random(0, 7)]);
nextLookTime = millis() + random(3000, 7000);
}
if (millis() > nextLookTime) {
static const uint8_t dirs[] = { DEFAULT, N, NE, E, W, NW, S };
eyes.setPosition(dirs[random(0, 7)]);
nextLookTime = millis() + random(3000, 7000);
}
}
void logCreatureState(const char* source) {
Serial.print("[");
Serial.print(source);
Serial.print("] ");
Serial.print("awake=");
Serial.print(awake);
Serial.print(" pickedUp=");
Serial.print(pickedUp);
Serial.print(" energy=");
Serial.print(energy, 2);
Serial.print(" irritation=");
Serial.print(irritation, 2);
Serial.print(" curiosity=");
Serial.println(curiosity, 2);
}
Loading
ili9341-cap-touch
ili9341-cap-touch