diff --git a/M4_Eyes/HeatSensor.cpp b/M4_Eyes/HeatSensor.cpp index 4989eb1bc..65c6476c3 100644 --- a/M4_Eyes/HeatSensor.cpp +++ b/M4_Eyes/HeatSensor.cpp @@ -22,7 +22,7 @@ void HeatSensor::setup() // default settings status = amg.begin(); - if (!status) { + if(!status) { Serial.println("Could not find a valid AMG88xx sensor, check wiring!"); while (1); } @@ -77,7 +77,7 @@ void HeatSensor::find_focus() for (int i = 1; i <= AMG88xx_PIXEL_ARRAY_SIZE; i++) { int val = min(5, round(max(0, pixels[i-1] - 20) / 2)); Serial.print(charPixels[val]); - if (i % 8 == 0) + if(i % 8 == 0) Serial.println(); } Serial.println(); diff --git a/M4_Eyes/M4_Eyes.ino b/M4_Eyes/M4_Eyes.ino index f61ef5350..367926c6b 100644 --- a/M4_Eyes/M4_Eyes.ino +++ b/M4_Eyes/M4_Eyes.ino @@ -59,6 +59,8 @@ int iPupilFactor = 42; uint32_t boopSum = 0, boopSumFiltered = 0; bool booped = false; +bool eyelidsClosed = false; +bool eyelidsWide = false; int fixate = 7; uint8_t lightSensorFailCount = 0; @@ -133,9 +135,54 @@ uint32_t availableRAM(void) { return &top - (char *)sbrk(0); // Top of stack minus end of heap } +// USER CALLABLE FUNCTIONS + +// Start a blink. +void eyesBlink() { + Serial.println("eyesBlink()"); + timeToNextBlink = 0; +} + +// Force the booped flag to be set true. +void eyesBoop() { + Serial.println("eyesBoop()"); + boopSum = 99999; +} + +// Close eyelids. +void eyesClose() { + Serial.println("eyesClose()"); + eyelidsClosed = true; +} + +// Return the eyes to normal random movement. +void eyesNormal() { +// Serial.println("eyesNormal()"); + moveEyesRandomly = true; +} + +// Open eyelids wide. +void eyesWide() { + Serial.println("eyesWide()"); + eyelidsWide = true; +} + +// Force the eyes to a position on the screen. +void eyesToCorner(float x, float y, bool immediate) { +// Serial.println("eyesToCorner(" + String(x) + ", " + String(-y) + ", " + (immediate ? "TRUE" : "FALSE") + ")"); + moveEyesRandomly = false; + eyeTargetX = x; + eyeTargetY = y; + if(immediate) + eyeMoveDuration = 0; +} + + // SETUP FUNCTION - CALLED ONCE AT PROGRAM START --------------------------- void setup() { + Serial.println("SETUP BEGINS"); + if(!arcada.arcadaBegin()) fatal("Arcada init fail!", 100); #if defined(USE_TINYUSB) if(!arcada.filesysBeginMSD()) fatal("No filesystem found!", 250); @@ -187,10 +234,10 @@ void setup() { #endif yield(); - if (showSplashScreen) { - if (arcada.drawBMP((char *)"/splash.bmp", 0, 0, (eye[0].display)) == IMAGE_SUCCESS) { + if(showSplashScreen) { + if(arcada.drawBMP((char *)"/splash.bmp", 0, 0, (eye[0].display)) == IMAGE_SUCCESS) { Serial.println("Splashing"); - if (NUM_EYES > 1) { // other eye + if(NUM_EYES > 1) { // other eye yield(); arcada.drawBMP((char *)"/splash.bmp", 0, 0, (eye[1].display)); } @@ -421,10 +468,11 @@ void setup() { } lastLightReadTime = micros() + 2000000; // Delay initial light reading + + Serial.println("END OF SETUP"); } - // LOOP FUNCTION - CALLED REPEATEDLY UNTIL POWER-OFF ----------------------- /* @@ -475,29 +523,29 @@ void loop() { // Eye movement int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event if(eyeInMotion) { // Currently moving? - if(dt >= eyeMoveDuration) { // Time up? Destination reached. - eyeInMotion = false; // Stop moving - if (moveEyesRandomly) { - eyeMoveDuration = random(10000, 3000000); // 0.01-3 sec stop - eyeMoveStartTime = t; // Save initial time of stop + if(dt >= eyeMoveDuration) { // Time up? Destination reached. + eyeInMotion = false; // Stop moving + if(moveEyesRandomly) { + eyeMoveDuration = random(10000, 3000000); // 0.01-3 sec stop + eyeMoveStartTime = t; // Save initial time of stop } eyeX = eyeOldX = eyeNewX; // Save position eyeY = eyeOldY = eyeNewY; } else { // Move time's not yet fully elapsed -- interpolate position float e = (float)dt / float(eyeMoveDuration); // 0.0 to 1.0 during move e = 3 * e * e - 2 * e * e * e; // Easing function: 3*e^2-2*e^3 0.0 to 1.0 - eyeX = eyeOldX + (eyeNewX - eyeOldX) * e; // Interp X - eyeY = eyeOldY + (eyeNewY - eyeOldY) * e; // and Y + eyeX = eyeOldX + (eyeNewX - eyeOldX) * e; // Interp X + eyeY = eyeOldY + (eyeNewY - eyeOldY) * e; // and Y } - } else { // Eye stopped + } else { // Eye stopped eyeX = eyeOldX; eyeY = eyeOldY; - if(dt > eyeMoveDuration) { // Time up? Begin new move. + if(dt > eyeMoveDuration) { // Time up? Begin new move. // r is the radius in X and Y that the eye can go, from (0,0) in the center. float r = (float)mapDiameter - (float)DISPLAY_SIZE * M_PI_2; // radius of motion r *= 0.6; // calibration constant - if (moveEyesRandomly) { + if(moveEyesRandomly) { eyeNewX = random(-r, r); float h = sqrt(r * r - x * x); eyeNewY = random(-h, h); @@ -510,9 +558,9 @@ void loop() { eyeNewY += mapRadius; // Set the duration for this move, and start it going. - eyeMoveDuration = random(83000, 166000); // ~1/12 - ~1/6 sec - eyeMoveStartTime = t; // Save initial time of move - eyeInMotion = true; // Start move on next frame + eyeMoveDuration = random(83000, 166000); // ~1/12 - ~1/6 sec + eyeMoveStartTime = t; // Save initial time of move + eyeInMotion = true; // Start move on next frame } } @@ -554,17 +602,20 @@ void loop() { iy = (int)map2screen(mapRadius - eye[eyeNum].eyeY) + (DISPLAY_SIZE/2); // on screen iy += irisRadius * trackFactor; if(eyeNum & 1) ix = DISPLAY_SIZE - 1 - ix; // Flip for right eye - if(iy > upperOpen[ix]) { + if(eyelidsWide) { uq = 1.0; - } else if(iy < upperClosed[ix]) { - uq = 0.0; - } else { - uq = (float)(iy - upperClosed[ix]) / (float)(upperOpen[ix] - upperClosed[ix]); - } - if(booped) { + lq = 1.0; + } else if(booped) { uq = 0.9; lq = 0.7; } else { + if(iy > upperOpen[ix]) { + uq = 1.0; + } else if(iy < upperClosed[ix]) { + uq = 0.0; + } else { + uq = (float)(iy - upperClosed[ix]) / (float)(upperOpen[ix] - upperClosed[ix]); + } lq = 1.0 - uq; } } else { @@ -572,6 +623,10 @@ void loop() { uq = 1.0; lq = 1.0; } + if(eyelidsClosed) { + uq = 0.0; + lq = 0.0; + } // Dampen eyelid movements slightly // SAVE upper & lower lid factors per eye, // they need to stay consistent across frame @@ -602,13 +657,14 @@ void loop() { // of both screens is about 1/2 this. frames++; if(((t - lastFrameRateReportTime) >= 1000000) && t) { // Once per sec. - Serial.println((frames * 1000) / (t / 1000)); + Serial.printf("Frame rate: %d\n", (frames * 1000) / (t / 1000)); lastFrameRateReportTime = t; } // Once per frame (of eye #0), reset boopSum... if((eyeNum == 0) && (boopPin >= 0)) { boopSumFiltered = ((boopSumFiltered * 3) + boopSum) / 4; +// Serial.printf("boopSum: %d, boopSumFiltered: %d, boopThreshold: %d, booped: %s\n", boopSum, boopSumFiltered, boopThreshold, (booped ? "true" : "false")); if(boopSumFiltered > boopThreshold) { if(!booped) { Serial.println("BOOP!"); @@ -620,6 +676,12 @@ void loop() { boopSum = 0; } + // Once per frame (of eye #1), reset eyelid states... + if(eyeNum == 1) { + eyelidsClosed = false; + eyelidsWide = false; + } + float mins = (float)millis() / 60000.0; if(eye[eyeNum].iris.iSpin) { // Spin works in fixed amount per frame (eyes may lose sync, but "wagon wheel" tricks work) @@ -884,7 +946,8 @@ void loop() { lightSensorPin = -1; // Stop trying to use the light sensor } else { lastLightReadTime = t - LIGHT_INTERVAL + 30000; // Try again in 30 ms - } } + } + } } irisValue = (irisValue * 0.97) + (lastLightValue * 0.03); // Filter response for smooth reaction } else { diff --git a/M4_Eyes/eyes/eagle/config.eye b/M4_Eyes/eyes/eagle/config.eye new file mode 100644 index 000000000..78070c9e8 --- /dev/null +++ b/M4_Eyes/eyes/eagle/config.eye @@ -0,0 +1,41 @@ +{ + "boopThreshold" : 17500, // lower is more sensitive + "eyeRadius" : 125, // radius, in pixels + "irisRadius" : 120, // radius, in pixels + "slitPupilRadius" : 0, // height, in pixels; 0 is round pupil + + "irisTexture" : "eagle/iris.bmp", +// "scleraTexture" : "eagle/sclera.bmp", + "scleraColor" : [ 64, 24, 22 ], + "pupilColor" : [ 0, 0, 0 ], + "backColor" : [ 140, 40, 20 ], // covers the outermost/backmost part of the eye where the sclera texture map (or color) doesn’t reach + "eyelidIndex" : "0x00", // 8-bit value; from table learn.adafruit.com/assets/61921 + + // independent irisTexture, scleraTexture, irisColor, scleraColor, + // pupilColor, backColor, irisAngle, scleraAngle, irisSpin, scleraSpin, + // irisMirror, scleraMirror, and rotate can be specified + "left" : { + }, + "right" : { + }, + + "upperEyelid" : "eagle/upper.bmp", + "lowerEyelid" : "eagle/lower.bmp", + "tracking" : true, + "squint" : 0.5, // offsets eyelid center point vertically + + "lightSensor" : 102, // light sensor pin; 102 is MONSTER M4SK, 21 is HalloWing M4 + "pupilMin" : 0.05, // smallest pupil size as a fraction of iris size; from 0.0 to 1.0 + "pupilMax" : 0.3, // largest pupil size as a fraction of iris size; from 0.0 to 1.0 + + "voice" : false, + "pitch" : 1.0, + "gain" : 1.0, // microphone gain (sensitivity) +// "waveform" : "sine" // "square", "sine", "tri" and "saw" are supported +// "modulate" : 30 // waveform modulation, in Hz + + "wiichuck" : { + "min" : 28, + "max" : 229 + } +} diff --git a/M4_Eyes/eyes/eagle/eyelid.psd b/M4_Eyes/eyes/eagle/eyelid.psd new file mode 100644 index 000000000..ad7875541 Binary files /dev/null and b/M4_Eyes/eyes/eagle/eyelid.psd differ diff --git a/M4_Eyes/eyes/eagle/iris.bmp b/M4_Eyes/eyes/eagle/iris.bmp new file mode 100644 index 000000000..e9cda74ef Binary files /dev/null and b/M4_Eyes/eyes/eagle/iris.bmp differ diff --git a/M4_Eyes/eyes/eagle/iris.psd b/M4_Eyes/eyes/eagle/iris.psd new file mode 100644 index 000000000..c88af1214 Binary files /dev/null and b/M4_Eyes/eyes/eagle/iris.psd differ diff --git a/M4_Eyes/eyes/eagle/lower.bmp b/M4_Eyes/eyes/eagle/lower.bmp new file mode 100644 index 000000000..0f9cbed39 Binary files /dev/null and b/M4_Eyes/eyes/eagle/lower.bmp differ diff --git a/M4_Eyes/eyes/eagle/sclera.bmp b/M4_Eyes/eyes/eagle/sclera.bmp new file mode 100644 index 000000000..ea196b147 Binary files /dev/null and b/M4_Eyes/eyes/eagle/sclera.bmp differ diff --git a/M4_Eyes/eyes/eagle/upper.bmp b/M4_Eyes/eyes/eagle/upper.bmp new file mode 100644 index 000000000..2e7045213 Binary files /dev/null and b/M4_Eyes/eyes/eagle/upper.bmp differ diff --git a/M4_Eyes/file.cpp b/M4_Eyes/file.cpp index 605f6fdcb..d42f74905 100644 --- a/M4_Eyes/file.cpp +++ b/M4_Eyes/file.cpp @@ -1,7 +1,8 @@ //34567890123456789012345678901234567890123456789012345678901234567890123456 -#define ARDUINOJSON_ENABLE_COMMENTS 1 -#include // JSON config file functions +#define ARDUINOJSON_ENABLE_COMMENTS 1 // ARDUINOJSON_ENABLE_COMMENTS must be set to 1 before including the library. + // The same value of ARDUINOJSON_ENABLE_COMMENTS must be set in each compilation unit. +#include // JSON config file functions #include "globals.h" extern Adafruit_Arcada arcada; @@ -89,7 +90,7 @@ void loadConfig(char *filename) { DeserializationError error = deserializeJson(doc, file); yield(); if(error) { - Serial.println("Config file error, using default settings"); + Serial.println("Config file error, using default settings."); Serial.println(error.c_str()); } else { uint8_t e; @@ -284,8 +285,9 @@ void loadConfig(char *filename) { #endif // ADAFRUIT_MONSTER_M4SK_EXPRESS } file.close(); + user_setup(doc); } else { - Serial.println("Can't open config file, using default settings"); + Serial.println("Can't open config file, using default settings."); } // INITIALIZE DEFAULT VALUES if config file missing or in error ---------- @@ -329,7 +331,7 @@ ImageReturnCode loadEyelid(char *filename, Adafruit_ImageReader *reader; reader = arcada.getImageReader(); - if (!reader) { + if(!reader) { return IMAGE_ERR_FILE_NOT_FOUND; } @@ -339,7 +341,7 @@ ImageReturnCode loadEyelid(char *filename, // This is the "booster seat" described in m4eyes.ino if(reader->bmpDimensions(filename, &w, &h) == IMAGE_SUCCESS) { tempBytes = ((w + 7) / 8) * h; // Bitmap size in bytes - if (maxRam > tempBytes) { + if(maxRam > tempBytes) { if((tempPtr = (uint8_t *)malloc(maxRam - tempBytes)) != NULL) { // Make SOME tempPtr reference, or optimizer removes the alloc! tempPtr[0] = 0; @@ -415,14 +417,14 @@ ImageReturnCode loadTexture(char *filename, uint16_t **data, Adafruit_ImageReader *reader; reader = arcada.getImageReader(); - if (!reader) { + if(!reader) { return IMAGE_ERR_FILE_NOT_FOUND; } // This is the "booster seat" described in m4eyes.ino if(reader->bmpDimensions(filename, &w, &h) == IMAGE_SUCCESS) { tempBytes = w * h * 2; // Image size in bytes (converted to 16bpp) - if (maxRam > tempBytes) { + if(maxRam > tempBytes) { if((tempPtr = (uint8_t *)malloc(maxRam - tempBytes)) != NULL) { // Make SOME tempPtr reference, or optimizer removes the alloc! tempPtr[0] = 0; @@ -455,3 +457,15 @@ ImageReturnCode loadTexture(char *filename, uint16_t **data, return status; } + +// Utility functions for use by user functions to grab an integer value from the config file +// using dwim() and providing a default value. +int32_t getDocInt(StaticJsonDocument<2048> &doc, const char *nm, int32_t def) { + return dwim(doc[nm], def); +} +int32_t getDocInt(StaticJsonDocument<2048> &doc, const char *nm, const char *nm2, int32_t def) { + return dwim(doc[nm][nm2], def); +} +int32_t getDocInt(StaticJsonDocument<2048> &doc, const char *nm, const char *nm2, const char *nm3, int32_t def) { + return dwim(doc[nm][nm2][nm3], def); +} diff --git a/M4_Eyes/globals.h b/M4_Eyes/globals.h index 296300994..3cd5b589a 100644 --- a/M4_Eyes/globals.h +++ b/M4_Eyes/globals.h @@ -72,7 +72,7 @@ GLOBAL_VAR float trackFactor GLOBAL_INIT(0.5); // Random eye motion: provided by the base project, but overridable by user code. GLOBAL_VAR bool moveEyesRandomly GLOBAL_INIT(true); // Clear to suppress random eye motion and let user code control it -GLOBAL_VAR float eyeTargetX GLOBAL_INIT(0.0); // THen set these continuously in user_loop. +GLOBAL_VAR float eyeTargetX GLOBAL_INIT(0.0); // Then set these continuously in user_loop. GLOBAL_VAR float eyeTargetY GLOBAL_INIT(0.0); // Range is from -1.0 to +1.0. // Pin definition stuff will go here @@ -210,6 +210,9 @@ extern bool filesystem_change_flag GLOBAL_INIT(true); extern void loadConfig(char *filename); extern ImageReturnCode loadEyelid(char *filename, uint8_t *minArray, uint8_t *maxArray, uint8_t init, uint32_t maxRam); extern ImageReturnCode loadTexture(char *filename, uint16_t **data, uint16_t *width, uint16_t *height, uint32_t maxRam); +extern int32_t getDocInt(StaticJsonDocument<2048> &doc, const char *nm, int32_t def); +extern int32_t getDocInt(StaticJsonDocument<2048> &doc, const char *nm, const char *nm2, int32_t def); +extern int32_t getDocInt(StaticJsonDocument<2048> &doc, const char *nm, const char *nm2, const char *nm3, int32_t def); // Functions in memory.cpp extern uint32_t availableRAM(void); @@ -232,5 +235,17 @@ extern float screen2map(int in); extern float map2screen(int in); // Functions in user.cpp +#define ARDUINOJSON_ENABLE_COMMENTS 1 // ARDUINOJSON_ENABLE_COMMENTS must be set to 1 before including the library. + // The same value of ARDUINOJSON_ENABLE_COMMENTS must be set in each compilation unit. +#include // JSON config file functions extern void user_setup(void); +extern void user_setup(StaticJsonDocument<2048> &doc); extern void user_loop(void); + +// User callable functions in M4_Eyes.ino +extern void eyesBlink(); +extern void eyesBoop(); +extern void eyesClose(); +extern void eyesNormal(); +extern void eyesWide(); +extern void eyesToCorner(float x, float y, bool immediate); diff --git a/M4_Eyes/user.cpp b/M4_Eyes/user.cpp index 19fe91642..863a0b7fd 100644 --- a/M4_Eyes/user.cpp +++ b/M4_Eyes/user.cpp @@ -15,6 +15,14 @@ void user_setup(void) { } +// Called once after the processing of the configuration file. This allows +// user configuration to also be done based on the config file. +#define ARDUINOJSON_ENABLE_COMMENTS 1 // ARDUINOJSON_ENABLE_COMMENTS must be set to 1 before including the library. + // The same value of ARDUINOJSON_ENABLE_COMMENTS must be set in each compilation unit. +#include // JSON config file functions +void user_setup(StaticJsonDocument<2048> &doc) { +} + // Called periodically during eye animation. This is invoked in the // interval before starting drawing on the last eye (left eye on MONSTER // M4SK, sole eye on HalloWing M0) so it won't exacerbate visible tearing diff --git a/M4_Eyes/user_fizzgig.cpp b/M4_Eyes/user_fizzgig.cpp index e7ed6bfde..a20f8d6f9 100644 --- a/M4_Eyes/user_fizzgig.cpp +++ b/M4_Eyes/user_fizzgig.cpp @@ -60,6 +60,14 @@ void user_setup(void) { } } +// Called once after the processing of the configuration file. This allows +// user configuration to also be done based on the config file. +#define ARDUINOJSON_ENABLE_COMMENTS 1 // ARDUINOJSON_ENABLE_COMMENTS must be set to 1 before including the library. + // The same value of ARDUINOJSON_ENABLE_COMMENTS must be set in each compilation unit. +#include // JSON config file functions +void user_setup(StaticJsonDocument<2048> &doc) { +} + void user_loop(void) { if(playing) { // While WAV is playing, wiggle servo between middle and open-mouth positions: diff --git a/M4_Eyes/user_hid.cpp b/M4_Eyes/user_hid.cpp index ecde9bda4..de0bd7065 100644 --- a/M4_Eyes/user_hid.cpp +++ b/M4_Eyes/user_hid.cpp @@ -38,8 +38,16 @@ void user_setup(void) { while( !USBDevice.mounted() ) delay(1); } +// Called once after the processing of the configuration file. This allows +// user configuration to also be done based on the config file. +#define ARDUINOJSON_ENABLE_COMMENTS 1 // ARDUINOJSON_ENABLE_COMMENTS must be set to 1 before including the library. + // The same value of ARDUINOJSON_ENABLE_COMMENTS must be set in each compilation unit. +#include // JSON config file functions +void user_setup(StaticJsonDocument<2048> &doc) { +} + void user_loop(void) { - if ( !usb_hid.ready() ) { + if( !usb_hid.ready() ) { Serial.println("not ready"); return; } @@ -47,23 +55,23 @@ void user_loop(void) { uint8_t keycode[6] = { 0 }; uint32_t buttonState = arcada.readButtons(); - if (buttonState & ARCADA_BUTTONMASK_UP) { + if(buttonState & ARCADA_BUTTONMASK_UP) { Serial.println("Up"); keycode[0] = UP_BUTTON_KEYCODE_TO_SEND; } - if (buttonState & ARCADA_BUTTONMASK_A) { + if(buttonState & ARCADA_BUTTONMASK_A) { Serial.println("A"); keycode[1] = A_BUTTON_KEYCODE_TO_SEND; } - if (buttonState & ARCADA_BUTTONMASK_DOWN) { + if(buttonState & ARCADA_BUTTONMASK_DOWN) { Serial.println("Down"); keycode[2] = DOWN_BUTTON_KEYCODE_TO_SEND; } uint8_t shake = arcada.accel.getClick(); - if (shake & 0x30) { + if(shake & 0x30) { Serial.print("shake detected (0x"); Serial.print(shake, HEX); Serial.print("): "); - if (shake & 0x10) Serial.println(" single shake"); + if(shake & 0x10) Serial.println(" single shake"); keycode[3] = SHAKE_KEYCODE_TO_SEND; } @@ -78,12 +86,12 @@ void user_loop(void) { bool anypressed = false; for (int k=0; k // JSON config file functions +void user_setup(StaticJsonDocument<2048> &doc) { +} + void user_loop(void) { for(int i=0; i // JSON config file functions +void user_setup(StaticJsonDocument<2048> &doc) { +} + void user_loop(void) { uint32_t elapsedSince = micros(); if(elapsedSince - lastTouchSample > TOUCH_SAMPLE_TIME) { @@ -252,7 +260,7 @@ void user_loop(void) { uint8_t pressed_buttons = arcada.readButtons(); uint8_t justpressed_buttons = arcada.justPressedButtons(); - if (justpressed_buttons & ARCADA_BUTTONMASK_UP){ + if(justpressed_buttons & ARCADA_BUTTONMASK_UP){ changeBehavior(0, elapsedSince); } else if(justpressed_buttons & ARCADA_BUTTONMASK_DOWN) { @@ -266,7 +274,7 @@ void user_loop(void) { } } - if (elapsedSince - lastWave > SAMPLE_TIME) { + if(elapsedSince - lastWave > SAMPLE_TIME) { lastWave = elapsedSince; switch (currentBehavior) { case 0: @@ -287,7 +295,7 @@ void user_loop(void) { break; } - if ((elapsedSince - lastBehaviorChange) > (SAMPLE_TIME * 1000)) { + if((elapsedSince - lastBehaviorChange) > (SAMPLE_TIME * 1000)) { lastBehaviorChange = elapsedSince; currentBehavior++; currentBehavior %= 4; diff --git a/M4_Eyes/user_watch.cpp b/M4_Eyes/user_watch.cpp index a3dd18625..59c454702 100644 --- a/M4_Eyes/user_watch.cpp +++ b/M4_Eyes/user_watch.cpp @@ -2,7 +2,7 @@ // CORRESPONDING LINE IN HeatSensor.cpp MUST ALSO BE ENABLED! #include "globals.h" -#include "heatSensor.h" +#include "HeatSensor.h" // For heat sensing HeatSensor heatSensor; @@ -25,6 +25,11 @@ void user_setup(void) { heatSensor.setup(); } +// Called once after the processing of the configuration file. This allows +// user configuration to also be done via the config file. +void user_setup(StaticJsonDocument<2048> &doc) { +} + // Called periodically during eye animation. This is invoked in the // interval before starting drawing on the last eye (left eye on MONSTER // M4SK, sole eye on HalloWing M0) so it won't exacerbate visible tearing @@ -35,8 +40,7 @@ void user_loop(void) { heatSensor.find_focus(); // Set values for the new X and Y. - eyeTargetX = heatSensor.x; - eyeTargetY = -heatSensor.y; + eyesToCorner(heatSensor.x, -heatSensor.y, false); } #endif // 0 diff --git a/M4_Eyes/user_wiichuck.cpp b/M4_Eyes/user_wiichuck.cpp new file mode 100644 index 000000000..47d4ff7ce --- /dev/null +++ b/M4_Eyes/user_wiichuck.cpp @@ -0,0 +1,239 @@ +#if 0 // Change to 0 to disable this code (must enable ONE user*.cpp only!) + +// This user loop is designed to be used with a WiiChuck for command inputs and +// connects to a NeoPixel strip for additional output. It was used with a unicorn +// mask with the NeoPixel strip wrapped around the horn. Both the eyes and +// NeoPixel strip react to inputs from the WiiChuck. +// Wiichuck (AdaFruit PID: 342), Nunchucky (PID: 345), QWiiC cable +// NeoPixel strip (PID: 3919) + +// The user setup initializes the WiiChuck. +// The doc-based user setup controls whether there is a NeoPixel +// strip and, if so, initializes the NeoPixel strip based on where +// it is connected and how many pixels are being controlled. + +// The user loop does three things: +// 1) reads the WiiChuck state for command inputs +// 2) moves the eyes based on the WiiChuck state +// 3) selects which pattern is displayed on the NeoPixel strip. + +// The joystick's states: +// Neutral (no buttons pushed, joystick in center): +// a) eyes move randomly, NeoPixels are in a fading shimmer pattern +// Joystick left: eyes look left, NeoPixels rotate left with green +// Joystick right: eyes look right, NeoPixels rotate right with blue +// Joystick up: eyes look up, NeoPixels spiral up with green +// Joystick down: eyes look down, NeoPixels spiral down with blue +// Button C: eyes go wide (ala boop), NeoPixels sparkle +// Button Z: eyes blink +// BOTH buttons: eyes are both wide AND blinking, and the colors change to red + +#define ARDUINOJSON_ENABLE_COMMENTS 1 // ARDUINOJSON_ENABLE_COMMENTS must be set to 1 before including the library. + // The same value of ARDUINOJSON_ENABLE_COMMENTS must be set in each compilation unit. +#include // JSON config file functions +#include +#include +#include "globals.h" + +Accessory nunchuck1; + +static int low = 0, high = 255, divFactor = 86; +static int neoPixelPin = -1, neoPixelMax = 0; +Adafruit_NeoPixel strip; + +const int LUMINESCENT = 0; // shimmer between colors +const int CHASE_RIGHT = 1; +const int CHASE_LEFT = 2; +const int CHASE_UP = 3; +const int CHASE_DOWN = 4; +const int SPARKLE = 5; + +int neoPixelState = LUMINESCENT; +long firstPixelHue = 0; +long curChasePixel = 0; + +const int green = Adafruit_NeoPixel::Color(0,127,0); +const int blue = Adafruit_NeoPixel::Color(0,0,127); +const int red = Adafruit_NeoPixel::Color(127,0,0); + + +// Called once near the end of the setup() function. If your code requires +// a lot of time to initialize, make periodic calls to yield() to keep the +// USB mass storage filesystem alive. +void user_setup(void) { + // follow the WiiAccessory.ino example: + nunchuck1.begin(); + if(nunchuck1.type == Unknown) { + /** If the device isn't auto-detected, set the type explicitly: + NUNCHUCK, + WIICLASSIC, + GuitarHeroController, + GuitarHeroWorldTourDrums, + DrumController, + DrawsomeTablet, + Turntable + */ + nunchuck1.type = NUNCHUCK; + } +} + +// Called once after the processing of the configuration file. This allows +// user configuration to also be done via the config file. +void user_setup(StaticJsonDocument<2048> &doc) { + low = getDocInt(doc, "wiichuck", "min", 0); + high = getDocInt(doc, "wiichuck", "max", 255); + divFactor = (high - low) / 3 + 1; + Serial.println("user_setup(doc)"); + + const char *neopin = doc["wiichuck"]["neopixel"]["pin"]; + Serial.println("neopin=" + String(neopin ? neopin : "(null)")); + int32_t neomax = getDocInt(doc, "wiichuck", "neopixel", "max", -1); + Serial.println("neopin=" + String(neomax)); + + if(neopin && neomax > -1) { + if((strcmp(neopin, "d2") == 0) || (strcmp(neopin, "D2") == 0)) + neoPixelPin = 2; + else if((strcmp(neopin, "d3") == 0) || (strcmp(neopin, "D3") == 0)) + neoPixelPin = 3; + neoPixelMax = neomax; + + strip.setPin(neoPixelPin); + strip.updateLength(neoPixelMax); + strip.updateType(NEO_GRB + NEO_KHZ800); + strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255) + strip.show(); // Turn OFF all pixels ASAP + strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + } +} + +static void neoChase(int addend, int div, int color) { + curChasePixel += addend; + curChasePixel %= div; + + strip.clear(); + for (int c = curChasePixel; c < strip.numPixels(); c += 3) { + strip.setPixelColor(c, color); + } + strip.show(); +} + +static void neoUpDown(int color, int dir) { + int np = strip.numPixels(); + curChasePixel += np + dir; + curChasePixel %= np; + strip.clear(); + strip.setPixelColor(curChasePixel, color); + strip.setPixelColor((curChasePixel + np / 3) % np, color); + strip.setPixelColor((curChasePixel + np * 2 / 3) % np, color); + strip.show(); +} + +static void neoShine() { + // code from user_neopixel.cpp + for(int i=0; i 1) { + chaseColor = blue; + neoPixelState = CHASE_RIGHT; + } else if(cornerY > 1) { + neoPixelState = CHASE_UP; + chaseColor = green; + } else if(cornerY < 1) { + neoPixelState = CHASE_DOWN; + chaseColor = blue; + } + } + + bool buttonC = nunchuck1.getButtonC(); + bool buttonZ = nunchuck1.getButtonZ(); + + if(buttonC && buttonZ) { + Serial.println("buttonC & buttonZ"); + chaseColor = red; + if(neoPixelState == LUMINESCENT) + neoPixelState = CHASE_UP; + } else { + if(buttonC) { + Serial.println("buttonC"); + eyesWide(); + neoPixelState = SPARKLE; + } else if(buttonZ) { + Serial.println("buttonZ"); + eyesClose(); + } + } + + if(strip.numPixels() > 0) { + // Serial.println("neo state=" + String(neoPixelState)); + switch (neoPixelState) { + case LUMINESCENT: neoShine(); break; + case CHASE_RIGHT: neoChase(4, 3, chaseColor); break; + case CHASE_LEFT: neoChase(2, 3, chaseColor); break; + case CHASE_UP: neoUpDown(chaseColor, 1); break; + case CHASE_DOWN: neoUpDown(chaseColor, -1); break; + case SPARKLE: neoSparkle(); break; + } + } +} + +#endif // 0