/* Wemos8266RelaysLedDisplay/main.cpp */ #define COMPDATE __DATE__ __TIME__ // Button pin on the esp for selecting modes. 0 for Generic devices! #define MODEBUTTON D3 #define RELAY1_PIN D1 #define RELAY2_PIN D2 #define DISPLAY_CLK_PIN D5 #define DISPLAY_DATA_PIN D7 #define DISPLAY_CS_PIN D6 #define VERTICAL_BAR_STARTS_TOP false #define DEBUG_RELAYS false #define DEBUG_DISPLAY false #define STARTUP1_ANIMATION_DURATION_ms 15000 #define STARTUP2_ANIMATION_DURATION_ms 45000 #include #include #include #include #include #include #include #include #include "MD_RobotEyes.h" IOTAppStory IAS(COMPDATE, MODEBUTTON); String deviceName = "wemosMatrixDisplay"; String chipId; // Define the number of devices we have in the chain and the hardware interface // NOTE: These pin numbers will probably not work with your hardware and may // need to be adapted /* Mapper result, connector to ESP is at right (backside): Your responses produce these hardware parameters HW_DIG_ROWS 1 HW_REV_COLS 0 HW_REV_ROWS 0 Your hardware matches the setting for FC-16 modules. Please set FC16_HW. */ #define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4 // Hardware SPI connection // MD_Parola P = MD_Parola(HARDWARE_TYPE, DISPLAY_CS_PIN, MAX_DEVICES); // Arbitrary output pins MD_Parola P = MD_Parola(HARDWARE_TYPE, DISPLAY_DATA_PIN, DISPLAY_CLK_PIN, DISPLAY_CS_PIN, MAX_DEVICES); MD_RobotEyes E; WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000); // Field default values char *clockName = "FREMO"; char *clockSpeed_modelMsPerRealSec_String = "250"; int clockSpeed_modelMsPerRealSec = 250; char *relay1Pin_String = "D1"; char *relay2Pin_String = "D2"; int relay1Pin = D1, relay2Pin = D2; char *relayHoldTime_ms_String = "200"; unsigned int relayHoldTime_ms = 200; char *relayMinOffTime_ms_String = "100"; unsigned int relayMinOffTime_ms = 100; unsigned int displayRefresh_ms = 200; // Clock Display Config Parameter static char * displayClockNameEvery_ms_String = "16000"; static char * displayClockNameDuration_ms_String = "1200"; static uint32_t displayClockNameEvery_ms = 16000; static uint32_t displayClockNameDuration_ms = 1200; static uint32_t doNotShowClockNameBeforeAndAfterMinuteChange_s = 2; void setupIAS(void) { #if defined ESP8266 // creat a unique deviceName for classroom situations (deviceName-123) chipId = String(ESP.getChipId()); chipId = "-"+chipId.substring(chipId.length()-3); deviceName += chipId; #endif // preset deviceName this is also your MDNS responder: http://deviceName.local IAS.preSetDeviceName(deviceName); IAS.preSetAppName(F("Wemos2RelaysMatrixDisplays")); IAS.preSetAppVersion(F("0.2.1")); IAS.preSetAutoUpdate(true); // define fields IAS.addField(clockName, "Clock Name", 8, 'T'); IAS.addField(clockSpeed_modelMsPerRealSec_String, "Model MilliSec per Real Sec", 8, 'N'); IAS.addField(displayClockNameEvery_ms_String, "Display clock name every (ms)", 5, 'N'); IAS.addField(displayClockNameDuration_ms_String, "Display clock name duration (ms)", 5, 'N'); IAS.addField(relay1Pin_String, "Pin Relay 1", 2, 'P'); IAS.addField(relay2Pin_String, "Pin Relay 2", 2, 'P'); IAS.addField(relayHoldTime_ms_String, "Relay hold time (ms)", 3, 'N'); IAS.addField(relayMinOffTime_ms_String, "Relay min off time (ms)", 3, 'N'); IAS.onModeButtonShortPress([]() { Serial.println(F(" If mode button is released, I will enter firmware update mode.")); Serial.println(F("*----------------------------------------------------------------------*")); P.print("|updt"); }); IAS.onModeButtonLongPress([]() { Serial.println(F(" If mode button is released, I will enter configuration mode.")); Serial.println(F("*----------------------------------------------------------------------*")); P.print("|cfg"); }); IAS.onFirstBoot([]() { Serial.println(F(" Manual reset necessary after serial upload!")); Serial.println(F("*----------------------------------------------------------------------*")); P.print("|rst"); ESP.reset(); }); IAS.onConfigMode([]() { P.print(" WiFi"); delay(400); P.print(":" + chipId); Serial.print(F("Entered config mode for Wifi, device=")); Serial.println(chipId); }); IAS.onFirmwareUpdateCheck([]() { // P.print("chk upd"); Serial.println(F("Firmware update check")); }); IAS.onFirmwareUpdateDownload([]() { P.print("dl+instl"); Serial.println(F("Download and install new firmware")); }); IAS.onFirmwareUpdateError([]() { // P.print("Err fwu"); Serial.println(F("Firmware update error")); }); // Optional parameter: What to do with EEPROM on First boot of the app? // 'F' Fully erase | 'P' Partial erase(default) | 'L' Leave intact IAS.begin('L'); delay(500); // Set to true to enable calling home frequently (disabled by default) IAS.setCallHome(true); // Call home interval in seconds, use 60s only for development. // Please change it to at least 2 hours in production IAS.setCallHomeInterval(120); //IAS.callHome(false /*SPIFFS-check*/); clockSpeed_modelMsPerRealSec = atoi(clockSpeed_modelMsPerRealSec_String); relay1Pin = IAS.dPinConv(relay1Pin_String); relay2Pin = IAS.dPinConv(relay2Pin_String); relayHoldTime_ms = atoi(relayHoldTime_ms_String); relayMinOffTime_ms = atoi(relayMinOffTime_ms_String); displayClockNameEvery_ms = atoi(displayClockNameEvery_ms_String); displayClockNameDuration_ms = atoi(displayClockNameDuration_ms_String); Serial.println(F("Configuration used:")); Serial.print(F("Relay1 Pin: ")); Serial.println(relay1Pin); Serial.print(F("Relay2 Pin: ")); Serial.println(relay2Pin); Serial.print(F("Clock speed: ")); Serial.print(clockSpeed_modelMsPerRealSec); Serial.println(F(" model ms per real sec")); Serial.print(F("Relay hold time (ms): ")); Serial.println(relayHoldTime_ms); Serial.print(F("Relay min off time (ms): ")); Serial.println(relayMinOffTime_ms); Serial.print(F("Clock speed (model ms per real time s): ")); Serial.println(clockSpeed_modelMsPerRealSec); Serial.print(F("Show clock name every (ms): ")); Serial.println(displayClockNameEvery_ms); Serial.print(F("Show clock name for (ms): ")); Serial.println(displayClockNameDuration_ms); } void setupRelays(int relay1Pin, int relay2Pin) { pinMode(relay1Pin, OUTPUT); pinMode(relay2Pin, OUTPUT); digitalWrite(relay1Pin, LOW); digitalWrite(relay2Pin, LOW); } static MD_MAX72XX *graphicDisplay = NULL; void setupDisplay() { int charCode; #if VERTICAL_BAR_STARTS_TOP static uint8_t verticalBarFont[] = { 1, 0x00, /* blank */ 1, 0x01, /* 1 dot */ 1, 0x03, /* 2 dots */ 1, 0x07, 1, 0x0f, 1, 0x1f, 1, 0x3f, 1, 0x7f, 1, 0xff, /* vertical bar completely set */ }; // columns from right to left, each byte is a single column #else static uint8_t verticalBarFont[] = { 1, 0x00, /* blank */ 1, 0x80, /* 1 dot */ 1, 0xc0, /* 2 dots */ 1, 0xe0, 1, 0xf0, 1, 0xf8, 1, 0xfc, 1, 0xfe, 1, 0xff, /* vertical bar completely set */ }; // columns from right to left, each byte is a single column #endif static uint8_t newZero[] = {0x05, 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00}; P.begin(); // P.setZoneEffect(0, true, PA_FLIP_LR); graphicDisplay = P.getGraphicObject(); E.begin(graphicDisplay); P.setIntensity(1); for (charCode=1; charCode<=9; ++charCode) { P.addChar(charCode, verticalBarFont+2*(charCode-1)); } char intro[] = {':', '-', ')', ' ', 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00}; // replace the 0 characters, we do not like the "slash" P.addChar('0', newZero); P.print(intro); } void setup(void) { Serial.println(F("setup():")); setupDisplay(); setupIAS(); delay(200); setupRelays(relay1Pin, relay2Pin); timeClient.begin(); Serial.println(F("setup() finished")); } static bool timeClientInitialized = false; static unsigned long lastTimeOutput_ms = 0; #define TIME_BETWEEN_REALTIME_UPDATE_ms 60000 static unsigned long last_relay_off_ts=0, last_relay_hold_ts=0; enum RelayState { RELAY_STATE_OFF=0, RELAY_STATE_ON_EVEN_MINUTE, RELAY_STATE_ON_ODD_MINUTE }; static RelayState relaysState = RELAY_STATE_OFF; static RelayState lastRelayOnState = RELAY_STATE_ON_EVEN_MINUTE; static bool relayCanSwitch=true; void toggleRelays() { if (relayCanSwitch) { if (lastRelayOnState == RELAY_STATE_ON_EVEN_MINUTE) { digitalWrite(relay1Pin, HIGH); digitalWrite(relay2Pin, LOW); relaysState = RELAY_STATE_ON_ODD_MINUTE; // P.print("R-OEv"); } else { digitalWrite(relay1Pin, LOW); digitalWrite(relay2Pin, HIGH); relaysState = RELAY_STATE_ON_EVEN_MINUTE; // P.print("R-OOd"); } lastRelayOnState = relaysState; } // else P.print("R-OErr"); relayCanSwitch = false; last_relay_hold_ts = millis(); Serial.println(F("Toggle Relays")); } void relaysOff(void) { digitalWrite(relay1Pin, LOW); digitalWrite(relay2Pin, LOW); last_relay_off_ts = millis(); relaysState = RELAY_STATE_OFF; // P.print("R-Off"); } void loopRelays(void) { if (relaysState == RELAY_STATE_OFF) { if (millis() - last_relay_off_ts > relayMinOffTime_ms) { relayCanSwitch = true; } } else { if (millis() - last_relay_hold_ts > relayHoldTime_ms) { relaysOff(); } } } typedef struct { char name[7]; MD_RobotEyes::emotion_t e; uint16_t timePause; // in milliseconds } sampleItem_t; static const sampleItem_t eSeq[] = { { "Nutral", MD_RobotEyes::E_NEUTRAL, 1000 }, { "Blink" , MD_RobotEyes::E_BLINK, 1000 }, { "Wink" , MD_RobotEyes::E_WINK, 1000 }, { "Left" , MD_RobotEyes::E_LOOK_L, 1000 }, { "Right" , MD_RobotEyes::E_LOOK_R, 1000 }, { "Up" , MD_RobotEyes::E_LOOK_U, 1000 }, { "Down" , MD_RobotEyes::E_LOOK_D, 1000 }, { "Angry" , MD_RobotEyes::E_ANGRY, 1000 }, { "Sad" , MD_RobotEyes::E_SAD, 1000 }, { "Evil" , MD_RobotEyes::E_EVIL, 1000 }, { "Evil2" , MD_RobotEyes::E_EVIL2, 1000 }, { "Squint", MD_RobotEyes::E_SQUINT, 1000 }, { "Dead" , MD_RobotEyes::E_DEAD, 1000 }, { "ScanV" , MD_RobotEyes::E_SCAN_UD, 1000 }, { "ScanH" , MD_RobotEyes::E_SCAN_LR, 1000 }, }; #define DISPLAY_ANIM_NAME false void loopStartupAnimation() { // show startup animation boolean animationFinished = false; static uint32_t timeStartDelay; static uint8_t index = ARRAY_SIZE(eSeq); static enum { S_IDLE, S_TEXT, S_ANIM, S_PAUSE } state = S_IDLE; animationFinished = E.runAnimation(); switch (state) { case S_IDLE: index++; if (index >= ARRAY_SIZE(eSeq)) index = 0; #if DISPLAY_ANIM_NAME E.setText(eSeq[index].name); #endif state = S_TEXT; break; case S_TEXT: // wait for the text to finish if (animationFinished) // text animation is finished { E.setAnimation(eSeq[index].e, true); state = S_ANIM; } break; case S_ANIM: // checking animation is completed if (animationFinished) // animation is finished { timeStartDelay = millis(); state = S_PAUSE; } break; case S_PAUSE: // non blocking waiting for a period between animations if (millis() - timeStartDelay >= eSeq[index].timePause) state = S_IDLE; break; default: state = S_IDLE; break; } } void loop(void) { int currentDisplayState; int hours, minutes, seconds; char minuteProgressIndicator; static int lastMinutes = 0; static int lastSeconds = 0; static char timeBuffer[10]; #define MsgSize 10 static char debugMsg[MsgSize+1]; static int recentDisplayState = -1; static unsigned long firstLoop_ts = 0; if (firstLoop_ts == 0) firstLoop_ts = millis(); if (!timeClientInitialized && WiFi.status() == WL_CONNECTED) { timeClient.begin(); timeClientInitialized = true; timeClient.update(); Serial.println(timeClient.getFormattedTime()); lastTimeOutput_ms = millis(); } IAS.loop(); if (timeClientInitialized && millis()-lastTimeOutput_ms > TIME_BETWEEN_REALTIME_UPDATE_ms) { timeClient.update(); Serial.println(timeClient.getFormattedTime()); lastTimeOutput_ms = millis(); } if (millis() < firstLoop_ts + STARTUP1_ANIMATION_DURATION_ms) { // Startup phase 1: Constant display of content, created during setup() return; } if (millis() < firstLoop_ts + STARTUP1_ANIMATION_DURATION_ms + STARTUP2_ANIMATION_DURATION_ms) { loopStartupAnimation(); return; } if (timeClientInitialized) { hours = timeClient.getHours(); minutes = timeClient.getMinutes(); seconds = timeClient.getSeconds(); } else { hours = (millis() / 60 * 60 * 1000) % 24; minutes = (millis() / 60 * 1000) % 60; seconds = (millis() / 1000) % 60; } minuteProgressIndicator = seconds/6.7 + 1; // char code 1-8 show vertical bar if (minuteProgressIndicator > 9) minuteProgressIndicator = 9; snprintf(timeBuffer, 10, "%c %2d:%02d", minuteProgressIndicator, hours, minutes); //P.displayAnimate(); //P.displayClear(); /* DEBUG */ #if DEBUG_RELAYS if (seconds != lastSeconds) { switch (seconds % 4) { case 0: digitalWrite(relay1Pin, HIGH); break; case 1: digitalWrite(relay1Pin, LOW); break; case 2: digitalWrite(relay2Pin, HIGH); break; case 3: digitalWrite(relay2Pin, LOW); break; } Serial.print("Rel dbg: "); Serial.println(seconds, HEX); delay(5); } #endif /* END DEBUG */ // standard procedure to display static uint32_t last_clock_refresh = 0; static uint32_t lastTimeClockNameShown = 0; static boolean showingClockName = false; if (showingClockName) { if (millis() - lastTimeClockNameShown > displayClockNameDuration_ms) { // stop showingClockName showingClockName = false; } } else { if ((millis() - lastTimeClockNameShown > displayClockNameEvery_ms) && (seconds < 60-doNotShowClockNameBeforeAndAfterMinuteChange_s) && (seconds > doNotShowClockNameBeforeAndAfterMinuteChange_s)) { P.begin(); // re-initialize, that fixes display problems due to electrical relais feedbacks P.setIntensity(2); P.print(clockName); lastTimeClockNameShown = millis(); showingClockName = true; } else { // showing clock if (millis() - last_clock_refresh > displayRefresh_ms) { //P.displayClear(); P.setIntensity(1); P.print(timeBuffer); last_clock_refresh = millis(); } } } // toggle relays if (lastMinutes != minutes) { toggleRelays(); lastMinutes = minutes; } lastSeconds = seconds; loopRelays(); }