diff --git a/Firmware/GPAD_API/.gitignore b/Firmware/GPAD_API/.gitignore new file mode 100644 index 00000000..d71a84bf --- /dev/null +++ b/Firmware/GPAD_API/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +compile_commands.json diff --git a/Firmware/GPAD_API/DFPlayer.cpp b/Firmware/GPAD_API/GPAD_API/DFPlayer.cpp similarity index 53% rename from Firmware/GPAD_API/DFPlayer.cpp rename to Firmware/GPAD_API/GPAD_API/DFPlayer.cpp index 7b171e32..ba71fbfb 100644 --- a/Firmware/GPAD_API/DFPlayer.cpp +++ b/Firmware/GPAD_API/GPAD_API/DFPlayer.cpp @@ -4,36 +4,35 @@ #include DFRobotDFPlayerMini dfPlayer; -HardwareSerial mySerial1(2); // Use UART2 +HardwareSerial mySerial1(2); // Use UART2 -const int LED_PIN = 13; // Krake -//const int LED_PIN = 2; // ESP32 LED +const int LED_PIN = 13; // Krake +// const int LED_PIN = 2; // ESP32 LED -const int nDFPlayer_BUSY = 4; //not Busy from DFPLayer +const int nDFPlayer_BUSY = 4; // not Busy from DFPLayer bool isDFPlayerDetected = false; -int volumeDFPlayer = 20; // Set volume initial volume. Range is [0 to 30] -int numberFilesDF = 0; // Number of the audio files found. - - +int volumeDFPlayer = 20; // Set volume initial volume. Range is [0 to 30] +int numberFilesDF = 0; // Number of the audio files found. // The serial command system is from: https://www.dfrobot.com/blog-1462.html?srsltid=AfmBOoqm5pHrLswInrTwZ9vcYdxxPC_zH2zXnoro2FyTLEL4L57IW3Sn char command; int pausa = 0; - -void serialSplashDFP() { +void serialSplashDFP() +{ Serial.println("==================================="); Serial.println(DEVICE_UNDER_TEST); Serial.print(PROG_NAME); Serial.println(FIRMWARE_VERSION); Serial.print("Compiled at: "); - Serial.println(F(__DATE__ " " __TIME__)); //compile date that is used for a unique identifier + Serial.println(F(__DATE__ " " __TIME__)); // compile date that is used for a unique identifier Serial.println("==================================="); Serial.println(); } -void menu_opcoes() { +void menu_opcoes() +{ Serial.println(); Serial.println(F("==================================================================================================================================")); Serial.println(F("Commands:")); @@ -44,16 +43,18 @@ void menu_opcoes() { Serial.println(F(" [< or >] forwards or backwards the track")); Serial.println(); Serial.println(F("=================================================================================================================================")); -} //end menu_opcoes() - +} // end menu_opcoes() -void checkSerial(void) { +void checkSerial(void) +{ - //Waits for data entry via serial - while (Serial.available() > 0) { + // Waits for data entry via serial + while (Serial.available() > 0) + { command = Serial.read(); - if ((command >= '1') && (command <= '9')) { + if ((command >= '1') && (command <= '9')) + { Serial.print("Music reproduction"); Serial.println(command); command = command - 48; @@ -61,24 +62,28 @@ void checkSerial(void) { menu_opcoes(); } - //Reproduction - //Stop + // Reproduction + // Stop - if (command == 's') { + if (command == 's') + { dfPlayer.stop(); Serial.println("Music Stopped!"); menu_opcoes(); } - //Pausa/Continua a musica - if (command == 'p') { + // Pausa/Continua a musica + if (command == 'p') + { pausa = !pausa; - if (pausa == 0) { + if (pausa == 0) + { Serial.println("Continue..."); dfPlayer.start(); } - if (pausa == 1) { + if (pausa == 1) + { Serial.println("Music Paused!"); dfPlayer.pause(); } @@ -86,16 +91,17 @@ void checkSerial(void) { menu_opcoes(); } - - //Increases volume - if (command == '+') { + // Increases volume + if (command == '+') + { dfPlayer.volumeUp(); Serial.print("Current volume:"); Serial.println(dfPlayer.readVolume()); menu_opcoes(); } - if (command == '<') { + if (command == '<') + { dfPlayer.previous(); Serial.println("Previous:"); Serial.print("Current track:"); @@ -103,7 +109,8 @@ void checkSerial(void) { menu_opcoes(); } - if (command == '>') { + if (command == '>') + { dfPlayer.next(); Serial.println("next:"); Serial.print("Current track:"); @@ -111,228 +118,248 @@ void checkSerial(void) { menu_opcoes(); } - //Decreases volume - if (command == '-') { + // Decreases volume + if (command == '-') + { dfPlayer.volumeDown(); Serial.print("Current Volume:"); Serial.println(dfPlayer.readVolume()); menu_opcoes(); } } -} // end checkSerial +} // end checkSerial -//Functions -void setupDFPlayer() { -// Debatably, this this should be in the GPAD_HAL, not here....but -// it is modular to place it here. +// Functions +void setupDFPlayer() +{ + // Debatably, this this should be in the GPAD_HAL, not here....but + // it is modular to place it here. pinMode(nDFPlayer_BUSY, INPUT_PULLUP); - // Setup UART for DFPlayer Serial.println("UART2 Begin"); mySerial1.begin(BAUD_DFPLAYER, SERIAL_8N1, RXD2, TXD2); - while (!mySerial1) { + while (!mySerial1) + { Serial.println("UART2 inot initilaized."); delay(100); - ; // wait for DFPlayer serial port to connect. + ; // wait for DFPlayer serial port to connect. } Serial.println("Begin DFPlayer: isACK true, doReset false."); - if (!dfPlayer.begin(mySerial1, true, false)) { + if (!dfPlayer.begin(mySerial1, true, false)) + { Serial.println("DFPlayer Mini not detected or not working."); Serial.println("Check for missing SD Card."); isDFPlayerDetected = false; - } else { + } + else + { isDFPlayerDetected = true; Serial.println("DFPlayer Mini detected!"); } - dfPlayer.volume(volumeDFPlayer); // Set initial volume - dfPlayer.setTimeOut(500); // Set serial communictaion time out 500ms + dfPlayer.volume(volumeDFPlayer); // Set initial volume + dfPlayer.setTimeOut(500); // Set serial communictaion time out 500ms // delay(100); - dfPlayer.start(); //Todo, ?? necessary for DFPlayer processing + dfPlayer.start(); // Todo, ?? necessary for DFPlayer processing delay(1000); -// dfPlayer.play(11); //DFPlayer Splash + // dfPlayer.play(11); //DFPlayer Splash dfPlayer.play(9); delay(100); dfPlayer.stop(); delay(1000); dfPlayer.previous(); delay(1500); - dfPlayer.play(); //DFPlayer Splash + dfPlayer.play(); // DFPlayer Splash displayDFPlayerStats(); -} //setupDFPLayer +} // setupDFPLayer - -void setVolume(int zeroToThirty) { +void setVolume(int zeroToThirty) +{ dfPlayer.volume(zeroToThirty); } - -void displayDFPlayerStats() { +void displayDFPlayerStats() +{ Serial.print("================="); Serial.print("dfPlayer State: "); - Serial.println(dfPlayer.readState()); //read mp3 state + Serial.println(dfPlayer.readState()); // read mp3 state Serial.print("dfPlayer Volume: "); - Serial.println(dfPlayer.readVolume()); //read current volume + Serial.println(dfPlayer.readVolume()); // read current volume Serial.print("dfPlayer EQ: "); - Serial.println(dfPlayer.readEQ()); //read EQ setting + Serial.println(dfPlayer.readEQ()); // read EQ setting Serial.print("SD Card FileCounts: "); - Serial.println(dfPlayer.readFileCounts()); //read all file counts in SD card + Serial.println(dfPlayer.readFileCounts()); // read all file counts in SD card Serial.print("Current File Number: "); numberFilesDF = dfPlayer.readCurrentFileNumber(); - Serial.println(numberFilesDF); //Display the read current play file number + Serial.println(numberFilesDF); // Display the read current play file number Serial.print("File Counts In Folder: "); - Serial.println(dfPlayer.readFileCountsInFolder(3)); //read file counts in folder SD:/03 + Serial.println(dfPlayer.readFileCountsInFolder(3)); // read file counts in folder SD:/03 // dfPlayer.EQ(0); // Normal equalization //Causes program lock up Serial.print("================="); } -void printDetail(uint8_t type, int value) { - switch (type) { - case TimeOut: - Serial.println(F("Time Out!")); - break; - case WrongStack: - Serial.println(F("Stack Wrong!")); +void printDetail(uint8_t type, int value) +{ + switch (type) + { + case TimeOut: + Serial.println(F("Time Out!")); + break; + case WrongStack: + Serial.println(F("Stack Wrong!")); + break; + case DFPlayerCardInserted: + Serial.println(F("Card Inserted!")); + break; + case DFPlayerCardRemoved: + Serial.println(F("Card Removed!")); + break; + case DFPlayerCardOnline: + Serial.println(F("Card Online!")); + break; + case DFPlayerUSBInserted: + Serial.println("USB Inserted!"); + break; + case DFPlayerUSBRemoved: + Serial.println("USB Removed!"); + break; + case DFPlayerPlayFinished: + Serial.print(F("Number:")); + Serial.print(value); + Serial.println(F(" Play Finished!")); + break; + case DFPlayerError: + Serial.print(F("DFPlayerError:")); + switch (value) + { + case Busy: + Serial.println(F("Card not found")); break; - case DFPlayerCardInserted: - Serial.println(F("Card Inserted!")); + case Sleeping: + Serial.println(F("Sleeping")); break; - case DFPlayerCardRemoved: - Serial.println(F("Card Removed!")); + case SerialWrongStack: + Serial.println(F("Get Wrong Stack")); break; - case DFPlayerCardOnline: - Serial.println(F("Card Online!")); + case CheckSumNotMatch: + Serial.println(F("Check Sum Not Match")); break; - case DFPlayerUSBInserted: - Serial.println("USB Inserted!"); + case FileIndexOut: + Serial.println(F("File Index Out of Bound")); break; - case DFPlayerUSBRemoved: - Serial.println("USB Removed!"); + case FileMismatch: + Serial.println(F("Cannot Find File")); break; - case DFPlayerPlayFinished: - Serial.print(F("Number:")); - Serial.print(value); - Serial.println(F(" Play Finished!")); - break; - case DFPlayerError: - Serial.print(F("DFPlayerError:")); - switch (value) { - case Busy: - Serial.println(F("Card not found")); - break; - case Sleeping: - Serial.println(F("Sleeping")); - break; - case SerialWrongStack: - Serial.println(F("Get Wrong Stack")); - break; - case CheckSumNotMatch: - Serial.println(F("Check Sum Not Match")); - break; - case FileIndexOut: - Serial.println(F("File Index Out of Bound")); - break; - case FileMismatch: - Serial.println(F("Cannot Find File")); - break; - case Advertise: - Serial.println(F("In Advertise")); - break; - default: - break; - } + case Advertise: + Serial.println(F("In Advertise")); break; default: break; + } + break; + default: + break; } -} //end printDetail for DFPlayer +} // end printDetail for DFPlayer - -void dfPlayerUpdate(void) { - unsigned long timePlay = 3000; //Plays 3 seconds of all files. +void dfPlayerUpdate(void) +{ + unsigned long timePlay = 3000; // Plays 3 seconds of all files. static unsigned long timer = millis(); - if (millis() - timer > timePlay) { + if (millis() - timer > timePlay) + { // if (millis() - timer > 10000) { timer = millis(); - dfPlayer.next(); //Play next mp3 every 3 second. + dfPlayer.next(); // Play next mp3 every 3 second. } - if (dfPlayer.available()) { - printDetail(dfPlayer.readType(), dfPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + if (dfPlayer.available()) + { + printDetail(dfPlayer.readType(), dfPlayer.read()); // Print the detail message from DFPlayer to handle different errors and states. } -} // end - - - +} // end // Functions to learn DFPlayer behavior -void playNotBusy() { - //Plays all files succsivly. +void playNotBusy() +{ + // Plays all files succsivly. Serial.println("PlayNotBusy"); - if (HIGH == digitalRead(nDFPlayer_BUSY)) { - //mp3_next (); + if (HIGH == digitalRead(nDFPlayer_BUSY)) + { + // mp3_next (); dfPlayer.next(); - } - if (dfPlayer.available()) { - printDetail(dfPlayer.readType(), dfPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + if (dfPlayer.available()) + { + printDetail(dfPlayer.readType(), dfPlayer.read()); // Print the detail message from DFPlayer to handle different errors and states. } - delay(1000); //This should be removed from code. We set a time for the next allowed message. + delay(1000); // This should be removed from code. We set a time for the next allowed message. // Or better yet use interupt to find the end of BUSY and set time for the next allowed DFPlayer message -} // end playNotBusy +} // end playNotBusy -void playNotBusyLevel(int level) { - //Plays all files succsivly. +void playNotBusyLevel(int level) +{ + // Plays all files succsivly. Serial.println("PlayNotBusyLevel"); - if (HIGH == digitalRead(nDFPlayer_BUSY)) { - //mp3_next (); - // Note....we should in fact use file names, not numbers here, - // or must at least build a data structure to associate the two. - // that shoulde be future work. - dfPlayer.play(level+1); + if (HIGH == digitalRead(nDFPlayer_BUSY)) + { + // mp3_next (); + // Note....we should in fact use file names, not numbers here, + // or must at least build a data structure to associate the two. + // that shoulde be future work. + dfPlayer.play(level + 1); Serial.println("HIGH .next called! ================="); - } - if (dfPlayer.available()) { - printDetail(dfPlayer.readType(), dfPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. } - delay(1000); //This should be removed from code. We set a time for the next allowed message. + if (dfPlayer.available()) + { + printDetail(dfPlayer.readType(), dfPlayer.read()); // Print the detail message from DFPlayer to handle different errors and states. + } + delay(1000); // This should be removed from code. We set a time for the next allowed message. // Or better yet use interupt to find the end of BUSY and set time for the next allowed DFPlayer message } - // Play a track but not if the DFPlayer is busy -bool playAlarmLevel(int alarmNumberToPlay) { +bool playAlarmLevel(int alarmNumberToPlay) +{ static unsigned long timer = millis(); const unsigned long delayPlayLevel = 20; // const int MAX_ALARM_NUMBER = 6; int tracNumber = alarmNumberToPlay; - if (millis() - timer > delayPlayLevel) { + if (millis() - timer > delayPlayLevel) + { // if (millis() - timer > 10000) { timer = millis(); - //If not busy play the alarm message - if ((0 > tracNumber < 0) || (numberFilesDF < tracNumber)) { + // If not busy play the alarm message + if ((0 > tracNumber < 0) || (numberFilesDF < tracNumber)) + { return false; - } else //Valid track number - if (HIGH == digitalRead(nDFPlayer_BUSY)) { // Should the test for busy be at the start of this function??? - //mp3_next (); + } + else // Valid track number + if (HIGH == digitalRead(nDFPlayer_BUSY)) + { // Should the test for busy be at the start of this function??? + // mp3_next (); dfPlayer.play(tracNumber); - } else { + } + else + { Serial.println("Not done playing previous file"); } - if (dfPlayer.available()) { - printDetail(dfPlayer.readType(), dfPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states. + if (dfPlayer.available()) + { + printDetail(dfPlayer.readType(), dfPlayer.read()); // Print the detail message from DFPlayer to handle different errors and states. } return true; - } else { + } + else + { return false; } -} // end of playAlarmLevel - +} // end of playAlarmLevel // void setupDFP() { // pinMode(LED_PIN, OUTPUT); @@ -362,4 +389,3 @@ bool playAlarmLevel(int alarmNumberToPlay) { // Serial.println("End of setup"); // digitalWrite(LED_PIN, LOW); // } // end of setup() - diff --git a/Firmware/GPAD_API/DFPlayer.h b/Firmware/GPAD_API/GPAD_API/DFPlayer.h similarity index 89% rename from Firmware/GPAD_API/DFPlayer.h rename to Firmware/GPAD_API/GPAD_API/DFPlayer.h index b6d2debd..f8f2866c 100644 --- a/Firmware/GPAD_API/DFPlayer.h +++ b/Firmware/GPAD_API/GPAD_API/DFPlayer.h @@ -2,7 +2,7 @@ #define DFPLAYER 1 #include -#define BAUD_DFPLAYER 9600 //for UART2 to the DFPlayer +#define BAUD_DFPLAYER 9600 // for UART2 to the DFPlayer #define TXD2 17 #define RXD2 16 diff --git a/Firmware/GPAD_API/GPAD_API.ino b/Firmware/GPAD_API/GPAD_API/GPAD_API.ino similarity index 76% rename from Firmware/GPAD_API/GPAD_API.ino rename to Firmware/GPAD_API/GPAD_API/GPAD_API.ino index a8cfa37b..259efe20 100644 --- a/Firmware/GPAD_API/GPAD_API.ino +++ b/Firmware/GPAD_API/GPAD_API/GPAD_API.ino @@ -54,19 +54,19 @@ #include "GPAD_HAL.h" #include "gpad_utility.h" #include "gpad_serial.h" -#include "wink.h" -#include +#include "Wink.h" +#include #include #include -#include // From library https://github.com/knolleary/ +#include // From library https://github.com/knolleary/ -#include // WiFi Manager for ESP32 -#include "LittleFS.h" +#include // WiFi Manager for ESP32 +#include #include -#include // File System Support -#include // req for i2c comm +#include // File System Support +#include // req for i2c comm #include "WiFiManagerOTA.h" #include @@ -76,7 +76,6 @@ #include "DFPlayer.h" #include "GPAD_menu.h" - AsyncWebServer server(80); AsyncWebSocket ws("/ws"); @@ -84,7 +83,6 @@ AsyncWebSocket ws("/ws"); WiFiClient espClient; PubSubClient client(espClient); - /* SPI_PERIPHERAL From: https://circuitdigest.com/microcontroller-projects/arduino-spi-communication-tutorial Modified by Forrest Lee Erickson 20220523 @@ -99,9 +97,9 @@ PubSubClient client(espClient); 20220927 Change back for GPAD nCS on Pin 10 */ -//SPI PERIPHERAL (ARDUINO UNO) -//SPI COMMUNICATION BETWEEN TWO ARDUINO UNOs -//CIRCUIT DIGEST +// SPI PERIPHERAL (ARDUINO UNO) +// SPI COMMUNICATION BETWEEN TWO ARDUINO UNOs +// CIRCUIT DIGEST /* Hardware Notes Peripheral SPI Line Pin in Arduino, IO setup @@ -113,12 +111,11 @@ PubSubClient client(espClient); #define GPAD_VERSION1 - #define DEBUG_SPI 0 -//#define DEBUG 0 +// #define DEBUG 0 #define DEBUG 1 -//#define DEBUG 4 +// #define DEBUG 4 unsigned long last_command_ms; @@ -127,32 +124,31 @@ unsigned long last_command_ms; const unsigned long DELAY_BEFORE_NEW_COMMAND_ALLOWED = 10000; const unsigned int NUM_WIFI_RECONNECT_RETRIES = 3; -const int LED_PINS[] = { LIGHT0, LIGHT1, LIGHT2, LIGHT3, LIGHT4 }; +const int LED_PINS[] = {LIGHT0, LIGHT1, LIGHT2, LIGHT3, LIGHT4}; // const int SWITCH_PINS[] = { SW1, SW2, SW3, SW4 }; // SW1, SW2, SW3, SW4 const int LED_COUNT = sizeof(LED_PINS) / sizeof(LED_PINS[0]); // const int SWITCH_COUNT = sizeof(SWITCH_PINS) / sizeof(SWITCH_PINS[0]); -//Aley network -// const char* ssid = "Home"; -// const char* password = "adt@1963#"; +// Aley network +// const char* ssid = "Home"; +// const char* password = "adt@1963#"; -//Maryville network -// const char* ssid = "VRX"; -// const char* password = "textinsert"; +// Maryville network +// const char* ssid = "VRX"; +// const char* password = "textinsert"; -//Houstin network -// const char* ssid = "DOS_WIFI"; -// const char* password = "$Suve07$$"; +// Houstin network +// const char* ssid = "DOS_WIFI"; +// const char* password = "$Suve07$$"; // Austin network -const char* ssid = "readfamilynetwork"; -const char* password = "magicalsparrow96"; - +const char *ssid = "readfamilynetwork"; +const char *password = "magicalsparrow96"; // MQTT Broker -const char* mqtt_broker_name = "public.cloud.shiftr.io"; -const char* mqtt_user = "public"; -const char* mqtt_password = "public"; +const char *mqtt_broker_name = "public.cloud.shiftr.io"; +const char *mqtt_user = "public"; +const char *mqtt_password = "public"; // MQTT Topics, MAC plus an extention // A MAC addresss treated as a string has 12 chars. @@ -180,18 +176,19 @@ char macAddressString[13]; // #define SERIAL_TIMEOUT_MS 600000 #define SERIAL_TIMEOUT_MS 1000 -//Set LED wink parameters -const int HIGH_TIME_LED_MS = 800; //time in milliseconds +// Set LED wink parameters +const int HIGH_TIME_LED_MS = 800; // time in milliseconds const int LOW_TIME_LED_MS = 200; unsigned long lastLEDtime_ms = 0; // unsigned long nextLEDchangee_ms = 100; //time in ms. -unsigned long nextLEDchangee_ms = 5000; //time in ms. +unsigned long nextLEDchangee_ms = 5000; // time in ms. // extern int LIGHT[]; // extern int NUM_LIGHTS; -void serialSplash() { - //Serial splash +void serialSplash() +{ + // Serial splash Serial.println(F("===================================")); Serial.println(COMPANY_NAME); Serial.println(MODEL_NAME); @@ -206,18 +203,20 @@ void serialSplash() { Serial.print(F("Broker: ")); Serial.println(mqtt_broker_name); Serial.print(F("Compiled at: ")); - Serial.println(F(__DATE__ " " __TIME__)); //compile date that is used for a unique identifier + Serial.println(F(__DATE__ " " __TIME__)); // compile date that is used for a unique identifier Serial.println(LICENSE); Serial.println(F("===================================")); Serial.println(); } // A periodic message identifying the subscriber (Krake) is on line. -void publishOnLineMsg(void) { +void publishOnLineMsg(void) +{ const unsigned long MESSAGE_PERIOD = 10000; - static unsigned long lastMillis = 0; // Sets timing for periodic MQTT publish message + static unsigned long lastMillis = 0; // Sets timing for periodic MQTT publish message // publish a message roughly every second. - if ((millis() - lastMillis > MESSAGE_PERIOD) || (millis() < lastMillis)) { //Check for role over. + if ((millis() - lastMillis > MESSAGE_PERIOD) || (millis() < lastMillis)) + { // Check for role over. lastMillis = lastMillis + MESSAGE_PERIOD; float rssi = WiFi.RSSI(); @@ -238,15 +237,15 @@ void publishOnLineMsg(void) { // Serial.println(WiFi.localIP()); //FLE #if defined(HMWK) - digitalWrite(LED_D9, !digitalRead(LED_D9)); // Toggle + digitalWrite(LED_D9, !digitalRead(LED_D9)); // Toggle #endif } } - - -bool connect_to_wifi() { - if (WiFi.status() != WL_CONNECTED) { +bool connect_to_wifi() +{ + if (WiFi.status() != WL_CONNECTED) + { delay(10); Serial.println(); @@ -254,34 +253,42 @@ bool connect_to_wifi() { Serial.println(ssid); WiFi.begin(ssid, password); - if (WiFi.status() != WL_CONNECTED) { + if (WiFi.status() != WL_CONNECTED) + { Serial.println("Failed to connect WiFi."); return false; - } else { + } + else + { Serial.print("WiFi connected"); #if (DEBUG > 1) delay(100); - Serial.print("Device connected at IPaddress: "); //FLE - Serial.println(WiFi.localIP()); //FLE + Serial.print("Device connected at IPaddress: "); // FLE + Serial.println(WiFi.localIP()); // FLE #endif return true; } } return true; -} // end connect_to_wifi() +} // end connect_to_wifi() // TODO: have this return a success or failure status and move // the delay up. -void reconnect() { +void reconnect() +{ int n = 0; - while (!client.connected() && n < NUM_WIFI_RECONNECT_RETRIES) { + while (!client.connected() && n < NUM_WIFI_RECONNECT_RETRIES) + { n++; Serial.print("Attempting MQTT connection..."); - if (client.connect(COMPANY_NAME, mqtt_user, mqtt_password)) { + if (client.connect(COMPANY_NAME, mqtt_user, mqtt_password)) + { Serial.println("success!"); - client.subscribe(subscribe_Alarm_Topic); // Subscribe to GPAD API alarms - } else { + client.subscribe(subscribe_Alarm_Topic); // Subscribe to GPAD API alarms + } + else + { Serial.print("failed, rc="); Serial.print(client.state()); delay(1000); @@ -290,7 +297,8 @@ void reconnect() { Serial.println((client.connected()) ? "connected!" : "failed to reconnect!"); } // Function to turn on all lamps -void turnOnAllLamps() { +void turnOnAllLamps() +{ #if defined(HMWK) digitalWrite(LED_D9, HIGH); #endif @@ -300,7 +308,8 @@ void turnOnAllLamps() { digitalWrite(LIGHT3, HIGH); digitalWrite(LIGHT4, HIGH); } -void turnOffAllLamps() { +void turnOffAllLamps() +{ #if defined(HMWK) digitalWrite(LED_D9, LOW); #endif @@ -311,21 +320,22 @@ void turnOffAllLamps() { digitalWrite(LIGHT4, LOW); } - - // Handeler for MQTT subscribed messages -void callback(char* topic, byte* payload, unsigned int length) { +void callback(char *topic, byte *payload, unsigned int length) +{ // todo, remove use of String here.... // Note: We will check for topic or topics in the future... - if (strcmp(topic, subscribe_Alarm_Topic) == 0) { + if (strcmp(topic, subscribe_Alarm_Topic) == 0) + { char mbuff[121]; Serial.print("Topic arrived ["); Serial.print(topic); Serial.print("] "); - //Put payload into mbuff[] a character array + // Put payload into mbuff[] a character array int m = min((unsigned int)length, (unsigned int)120); - for (int i = 0; i < m; i++) { + for (int i = 0; i < m; i++) + { mbuff[i] = (char)payload[i]; } mbuff[m] = '\0'; @@ -337,49 +347,53 @@ void callback(char* topic, byte* payload, unsigned int length) { #endif Serial.println("Received MQTT Msg."); - interpretBuffer(mbuff, m, &Serial, &client); //Process the MQTT message + interpretBuffer(mbuff, m, &Serial, &client); // Process the MQTT message annunciateAlarmLevel(&Serial); } -} //end call back +} // end call back -bool readMacAddress(uint8_t* baseMac) { +bool readMacAddress(uint8_t *baseMac) +{ // uint8_t baseMac[6]; esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac); - if (ret == ESP_OK) { + if (ret == ESP_OK) + { // Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n", // baseMac[0], baseMac[1], baseMac[2], // baseMac[3], baseMac[4], baseMac[5]); return true; - } else { + } + else + { // Serial.println("Failed to read MAC address"); return false; } } -//Elegant OTA Setup +// Elegant OTA Setup -void setupOTA() { +void setupOTA() +{ // Route for root / web page - server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { - request->send(LittleFS, "/index.html", "text/html", false, processor); - }); + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(LittleFS, "/index.html", "text/html", false, processor); }); server.serveStatic("/", LittleFS, "/"); - // End of ELegant OTA Setup } - -void setup() { - pinMode(LED_BUILTIN, OUTPUT); // set the LED pin mode +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); // set the LED pin mode digitalWrite(LED_BUILTIN, HIGH); - //Serial setup + // Serial setup delay(100); Serial.begin(BAUDRATE); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB + while (!Serial) + { + ; // wait for serial port to connect. Needed for native USB } serialSplash(); // We call this a second time to get the MAC on the screen @@ -398,7 +412,7 @@ void setup() { // Turn off all LEDs initially turnOnAllLamps(); - //Init arrays. + // Init arrays. subscribe_Alarm_Topic[0] = '\0'; publish_Ack_Topic[0] = '\0'; macAddressString[0] = '\0'; @@ -407,25 +421,26 @@ void setup() { Serial.println("Call: GPAD_HAL_setup(&Serial)"); #endif - //Setup and present LCD splash screen - //Setup the SWITCH_MUTE - //Setup the SWITCH_ENCODER + // Setup and present LCD splash screen + // Setup the SWITCH_MUTE + // Setup the SWITCH_ENCODER GPAD_HAL_setup(&Serial); + const gpad_hal::GPAD_API gpadApi = gpad_hal::GPAD_API(&Serial); + #if (DEBUG > 0) Serial.println("MAC: "); Serial.println(macAddressString); #endif Serial.setTimeout(SERIAL_TIMEOUT_MS); - client.setServer(mqtt_broker_name, 1883); //Default MQTT port + client.setServer(mqtt_broker_name, 1883); // Default MQTT port client.setCallback(callback); #if (DEBUG > 0) Serial.println("Starting WiFi as STA"); #endif - // Note: On Krake SN#3 only, performing this // while the Splash is on causes a reset, presumably // because too much power is drawn. I am using a conditional @@ -438,7 +453,6 @@ void setup() { #endif WiFi.mode(WIFI_STA); - WiFi.STA.begin(); #if (LIMIT_POWER_DRAW) splashLCD(); @@ -478,76 +492,77 @@ void setup() { // req for Wifi Man and OTA WiFiMan(); initLittleFS(); - server.begin(); // Start server web socket to render pages + server.begin(); // Start server web socket to render pages ElegantOTA.begin(&server); setupOTA(); // Need this to work here: printInstructions(serialport); Serial.println(F("Done With Setup!")); turnOnAllLamps(); - digitalWrite(LED_BUILTIN, LOW); // turn the LED off at end of setup + digitalWrite(LED_BUILTIN, LOW); // turn the LED off at end of setup initRotator(); splashLCD(); + setupDFPlayer(); + setup_GPAD_menu(); - setupDFPlayer(); - setup_GPAD_menu(); - -} // end of setup() +} // end of setup() unsigned long last_ms = 0; -void toggle(int pin) { +void toggle(int pin) +{ digitalWrite(pin, digitalRead(pin) ? LOW : HIGH); } const unsigned long LOW_FREQ_DEBUG_MS = 20000; unsigned long time_since_LOW_FREQ_ms = 0; -//IPAddress myIP(0, 0, 0, 0); // declare for global and initialize -IPAddress myIP(); // declare for global +// IPAddress myIP(0, 0, 0, 0); // declare for global and initialize +IPAddress myIP(); // declare for global int cnt_actions = 0; bool running_menu = false; bool menu_just_exited = false; -void loop() { - +void loop() +{ bool is_WIFIconnected = false; unsigned long ms = millis(); - if (ms - time_since_LOW_FREQ_ms > LOW_FREQ_DEBUG_MS) { + if (ms - time_since_LOW_FREQ_ms > LOW_FREQ_DEBUG_MS) + { time_since_LOW_FREQ_ms = ms; - //If WiFi was not connected and becomes connected then print IP address - if (!is_WIFIconnected && connect_to_wifi()) { + // If WiFi was not connected and becomes connected then print IP address + if (!is_WIFIconnected && connect_to_wifi()) + { is_WIFIconnected = true; - //Get the IP address into a variable I can make global + // Get the IP address into a variable I can make global IPAddress myIP = WiFi.localIP(); - const char* ipString = myIP.toString().c_str(); + const char *ipString = myIP.toString().c_str(); // strcat(onInfoMsg, *getCurrentMessage()); Produced error error: invalid conversion from 'char' to 'const char*' [-fpermissive] - Serial.print("Device connected at IPaddress: "); //FLE - // Serial.println(WiFi.localIP()); //FLE - Serial.println(myIP); //FLE + Serial.print("Device connected at IPaddress: "); // FLE + // Serial.println(WiFi.localIP()); //FLE + Serial.println(myIP); // FLE } - // Serial.println(subscribe_Alarm_Topic); // Serial.println(publish_Ack_Topic); #if defined HMWK || defined KRAKE - if (!client.connected()) { + if (!client.connected()) + { reconnect(); } #endif } - #if defined HMWK || defined KRAKE client.loop(); publishOnLineMsg(); - wink(); //The builtin LED + wink(); // The builtin LED #endif unchanged_anunicateAlarmLevel(&Serial); @@ -568,13 +583,15 @@ void loop() { updateRotator(); - if (menu_just_exited) { + if (menu_just_exited) + { lcd.clear(); lcd.noBacklight(); restoreAlarmLevel(&Serial); menu_just_exited = false; } - if (running_menu) { + if (running_menu) + { lcd.backlight(); poll_GPAD_menu(); } diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp new file mode 100644 index 00000000..061fd144 --- /dev/null +++ b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp @@ -0,0 +1,790 @@ +/* GPAD_HAL.cpp + The Hardware Abstraction Layer (HAL) (low-level hardware) api + + Copyright (C) 2022 Robert Read + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +// #include +#include "GPAD_HAL.h" +#include "alarm_api.h" +#include "gpad_utility.h" +#include +#include "WiFiManagerOTA.h" +#include "GPAD_menu.h" + +using namespace gpad_hal; + +extern IPAddress myIP; + +// Use Serial1 for UART communication +HardwareSerial uartSerial1(1); // For user Serial Port +HardwareSerial uartSerial2(2); // For DFPLayer, audio + +#include +// Time in ms you need to hold down the button to be considered a long press +unsigned int longPressTime = 1000; +// How many times you need to hit the button to be considered a multi-hit +byte multiHitTarget = 2; +// How fast you need to hit all buttons to be considered a multi-hit +unsigned int multiHitTime = 400; + +DailyStruggleButton muteButton; +DailyStruggleButton encoderSwitchButton; + +extern const char *AlarmNames[]; +extern AlarmLevel currentLevel; +extern bool currentlyMuted; +extern char AlarmMessageBuffer[81]; + +extern char macAddressString[13]; + +// TODO: Remove this; for explanation only +extern char publish_Ack_Topic[17]; + +#include // From library https://github.com/knolleary/pubsubclient + +extern PubSubClient client; + +// For LCD +// #include + +// https://github.com/johnrickman/LiquidCrystal_I2C +LiquidCrystal_I2C lcd(LCD_ADDRESS, 20, 4); + +#include "DFPlayer.h" + +// Setup for buzzer. +// const int BUZZER_TEST_FREQ = 130; // One below middle C3. About 67 db, 3" x 4.875" 8 Ohm speakers no cabinet at 1 Meter. +// const int BUZZER_TEST_FREQ = 260; // Middle C4. About ?? db, 3" x 4.875" 8 Ohm speakers no cabinet at 1 Meter. +// const int BUZZER_TEST_FREQ = 1000; //About 76 db, 3" x 4.875" 8 Ohm speakers no cabinet at 1 Meter. +const int BUZZER_TEST_FREQ = 4000; // Buzzers, 3 V 4kHz 60dB @ 3V, 10 cm. The specified frequencey for the Version 1 buzzer. + +const int BUZZER_LVL_FREQ_HZ[] = {0, 128, 256, 512, 1024, 2048}; + +// This as an attempt to program recognizable "songs" for each alarm level that accomplish +// both informativeness and urgency mapping. The is is to use an index into the buzzer +// level frequencies above, so we can use an unsigned char. We can break the whole +// sequence into 100ms chunks. A 0 will make a "rest" or a silence.a length of 60 will +// give us a 6-second repeat. + +const unsigned int NUM_NOTES = 20; +const int SONGS[][NUM_NOTES] = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, + {2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, 2, 0, 0, 0, 0}, + {3, 3, 3, 0, 3, 3, 3, 3, 0, 3, 3, 3, 0, 3, 3, 3, 0, 0, 0, 0}, + {4, 0, 4, 0, 4, 0, 4, 0, 0, 0, 4, 0, 4, 0, 4, 0, 4, 0, 0, 0}, + {4, 4, 2, 0, 4, 4, 2, 0, 4, 4, 2, 0, 4, 4, 2, 0, 4, 4, 2, 0}}; + +const int LIGHT_LEVEL[][NUM_NOTES] = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, + {2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, 2, 0, 0, 0, 0}, + {3, 3, 3, 0, 3, 3, 3, 3, 0, 3, 3, 3, 0, 3, 3, 3, 0, 0, 0, 0}, + {4, 4, 4, 0, 4, 4, 4, 0, 0, 0, 4, 4, 4, 0, 4, 4, 4, 0, 0, 0}, + {0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5}}; + +const unsigned LEN_OF_NOTE_MS = 500; + +unsigned long start_of_song = 0; + +// in general, we want tones to last forever, although +// I may implement blinking later. +const unsigned long INF_DURATION = 4294967295; + +// Allow indexing to LIGHT[] by symbolic name. So LIGHT0 is first and so on. +int LIGHT[] = {LIGHT0, LIGHT1, LIGHT2, LIGHT3, LIGHT4}; +int NUM_LIGHTS = sizeof(LIGHT) / sizeof(LIGHT[0]); + +Stream *local_ptr_to_serial; + +volatile boolean isReceived_SPI; +volatile byte peripheralReceived; + +volatile bool procNewPacket = false; +volatile byte indx = 0; +volatile boolean process; + +byte received_signal_raw_bytes[MAX_BUFFER_SIZE]; + +// Local DEBUG defines, GPAD_HAL +#define DEBUG 0 +// #define DEBUG 1 + +#if (DEBUG > 0) +Serial.println("Debug defined >0") +#endif + + const int NUM_PREFICES = 5; +char legal_prefices[NUM_PREFICES] = {'h', 's', 'a', 'u', 'i'}; + +void setup_spi() +{ + Serial.println(F("Starting SPI Peripheral.")); + Serial.print(F("Pin for SS: ")); + Serial.println(SS); + + pinMode(BUTTON_PIN, INPUT); // Setting pin 2 as INPUT + pinMode(LED_PIN, OUTPUT); // Setting pin 7 as OUTPUT + + // SPI.begin(); // IMPORTANT. Do not set SPI.begin for a peripherial device. + pinMode(SS, INPUT_PULLUP); // Sets SS as input for peripherial + // Why is this not input? + pinMode(MOSI, INPUT); // This works for Peripheral + pinMode(MISO, OUTPUT); // try this. + pinMode(SCK, INPUT); // Sets clock as input +#if defined(GPAD) + SPCR |= _BV(SPE); // Turn on SPI in Peripheral Mode + // turn on interrupts + SPCR |= _BV(SPIE); + + isReceived_SPI = false; + SPI.attachInterrupt(); // Interuupt ON is set for SPI commnucation +#else +#endif + +} // end setup_SPI() + +// ISRs +// This is the original... +// I plan to add an index to this to handle the full message that we intend to receive. +// However, I think this also needs a timeout to handle the problem of getting out of synch. +const int SPI_BYTE_TIMEOUT_MS = 200; // we don't get the next byte this fast, we reset. +volatile unsigned long last_byte_ms = 0; + +#if defined(HMWK) +// void IRAM_ATTR ISR() { +// receive_byte(SPDR); +// } +#elif defined(GPAD) // compile for an UNO, for example... +ISR(SPI_STC_vect) // Inerrrput routine function +{ + receive_byte(SPDR); +} // end ISR +#endif + +void receive_byte(byte c) +{ + last_byte_ms = millis(); + // byte c = SPDR; // read byte from SPI Data Register + if (indx < sizeof received_signal_raw_bytes) + { + received_signal_raw_bytes[indx] = c; // save data in the next index in the array received_signal_raw_bytes + indx = indx + 1; + } + if (indx >= sizeof received_signal_raw_bytes) + { + process = true; + } +} + +void updateFromSPI() +{ + if (DEBUG > 0) + { + if (process) + { + Serial.println("process true!"); + } + } + if (process) + { + + AlarmEvent event; + event.lvl = (AlarmLevel)received_signal_raw_bytes[0]; + for (int i = 0; i < MAX_MSG_LEN; i++) + { + event.msg[i] = (char)received_signal_raw_bytes[1 + i]; + } + + if (DEBUG > 1) + { + Serial.print(F("LVL: ")); + Serial.println(event.lvl); + Serial.println(event.msg); + } + int prevLevel = alarm((AlarmLevel)event.lvl, event.msg, &Serial); + if (prevLevel != event.lvl) + { + annunciateAlarmLevel(&Serial); + } + else + { + unchanged_anunicateAlarmLevel(&Serial); + } + + indx = 0; + process = false; + } +} + +// Have to get a serialport here + +// void myCallback(byte buttonEvent) { +void GPAD_API::encoderSwitchCallback(byte buttonEvent) const +{ + switch (buttonEvent) + { + case onPress: + // Do something... + this->serial->println(F("ENCODER_SWITCH onPress")); + // currentlyMuted = !currentlyMuted; + // start_of_song = millis(); + // annunciateAlarmLevel(local_ptr_to_serial); + // printAlarmState(local_ptr_to_serial); + + registerRotaryEncoderPress(); + break; + case onRelease: + // Do nothing... + this->serial->println(F("ENCODER_SWITCH onRelease")); + break; + case onHold: + // Do nothing... + // local_ptr_to_serial->println(F("ENCODER_SWITCH onHold")); + break; + // onLongPress is indidcated when you hold onto the button + // more than longPressTime in milliseconds + case onLongPress: + Serial.print("ENCODER_SWITCH Button Long Pressed For "); + Serial.print(longPressTime); + Serial.println("ms"); + break; + + // onMultiHit is indicated when you hit the button + // multiHitTarget times within multihitTime in milliseconds + case onMultiHit: + Serial.print("Encoder Switch Button Pressed "); + Serial.print(multiHitTarget); + Serial.print(" times in "); + Serial.print(multiHitTime); + Serial.println("ms"); + break; + default: + Serial.print("Encoder Switch buttonEvent but not reckognized case: "); + Serial.println(buttonEvent); + break; + } +} + +// Have to get a serialport here +// void myCallback(byte buttonEvent) { +void GPAD_API::muteButtonCallback(byte buttonEvent) +{ + switch (buttonEvent) + { + case onPress: + // Do something... + serial->println(F("SWITCH_MUTE onPress")); + currentlyMuted = !currentlyMuted; + start_of_song = millis(); + annunciateAlarmLevel(serial); + printAlarmState(serial); + break; + case onRelease: + // Do nothing... + serial->println(F("SWITCH_MUTE onRelease")); + break; + case onHold: + // Do nothing... + serial->println(F("SWITCH_MUTE onHold")); + break; + // onLongPress is indidcated when you hold onto the button + // more than longPressTime in milliseconds + case onLongPress: + Serial.print("SWITCH_MUTE Long Pressed For "); + Serial.print(longPressTime); + Serial.println("ms"); + break; + + // onMultiHit is indicated when you hit the button + // multiHitTarget times within multihitTime in milliseconds + case onMultiHit: + Serial.print("Button Pressed "); + Serial.print(multiHitTarget); + Serial.print(" times in "); + Serial.print(multiHitTime); + Serial.println("ms"); + break; + default: + Serial.print("Mute buttonEvent but not reckognized case: "); + Serial.println(buttonEvent); + break; + } +} + +// This routine should be refactored so that it only "interprets" +// the character buffer and returns an "abstract" command to be acted on +// elseshere. This will allow us to remove the PubSubClient from the this file, +// the Hardware Abstraction Layer. +void interpretBuffer(char *buf, int rlen, Stream *serialport, PubSubClient *client) +{ + if (rlen < 1) + { + printError(serialport); + return; // no action + } + + bool found = false; + for (int i = 0; i < NUM_PREFICES; i++) + { + if (buf[0] == legal_prefices[i]) + found = true; + } + if (!found) + { + printError(serialport); + return; + } + Command command = static_cast(buf[0]); + + serialport->print(F("Command: ")); + serialport->println(F(command)); + switch (command) + { + case Command::MUTE: + serialport->println(F("Muting Case!")); + currentlyMuted = true; + break; + case Command::UNMUTE: + serialport->println(F("UnMuting Case!")); + currentlyMuted = false; + break; + case Command::HELP: // help + printInstructions(serialport); + break; + case Command::ALARM: + { + // In the case of an alarm state, the rest of the buffer is a message. + // we will read up to 60 characters from this buffer for display on our + // Arguably when we support mulitple states this will become more complicated. + char D = buf[1]; + int N = D - '0'; + serialport->println(N); + // WARNING: Shouldn't this be MAX_BUFFER_SIZE? + char msg[61]; + msg[0] = '\0'; + strncat(msg, buf, 60); + // This copy loooks uncessary, but is not...we want "alarm" + // to be a completely independent and abstract function. + // it should copy the msg buffer + serialport->print("The MQTT Alarm Message: "); + serialport->println(msg); + alarm((AlarmLevel)N, msg, serialport); // Makes Lamps indicate alarm. + + break; + } + case Command::INFO: // Information. Firmware Version, Mute Status, + { + // Firmware Version + // 81+23 = Maximum string length + // char onInfoMsg[32] = "Firmware Version: "; + // static char onInfoMsg[81+24] = "Firmware Version: "; //This does not have the bug. + char onInfoMsg[81 + 24] = "Firmware Version: "; // This + char str[20]; + + strcat(onInfoMsg, FIRMWARE_VERSION); + client->publish(publish_Ack_Topic, onInfoMsg); + serialport->println(onInfoMsg); + onInfoMsg[0] = '\0'; + + // Report API version + strcat(onInfoMsg, "GPAD API Version: "); + strcat(onInfoMsg, gpadApi.getVersion().toString().c_str()); + client->publish(publish_Ack_Topic, onInfoMsg); + serialport->println(onInfoMsg); + onInfoMsg[0] = '\0'; + + // Up time + onInfoMsg[0] = '\0'; + + str[0] = '\0'; + strcat(onInfoMsg, "System up time (mills): "); + sprintf(str, "%d", millis()); + strcat(onInfoMsg, str); + client->publish(publish_Ack_Topic, onInfoMsg); + serialport->println(onInfoMsg); + + // Mute status + onInfoMsg[0] = '\0'; + // onInfoMsg[32] = "Mute Status: "; + strcat(onInfoMsg, "Mute Status: "); + if (currentlyMuted) + { + strcat(onInfoMsg, "MUTED"); + } + else + { + strcat(onInfoMsg, "NOT MUTED"); + } + client->publish(publish_Ack_Topic, onInfoMsg); + serialport->println(onInfoMsg); + + // Alarm level + onInfoMsg[0] = '\0'; + str[0] = '\0'; + strcat(onInfoMsg, "Current alarm Level: "); + sprintf(str, "%d", getCurrentAlarmLevel()); + strcat(onInfoMsg, str); + client->publish(publish_Ack_Topic, onInfoMsg); + serialport->println(onInfoMsg); + + // Alarm message + onInfoMsg[0] = '\0'; + strcat(onInfoMsg, "Current alarm message: "); + // strcat(onInfoMsg, *getCurrentMessage()); Produced error error: invalid conversion from 'char' to 'const char*' [-fpermissive] + strcat(onInfoMsg, getCurrentMessage()); + client->publish(publish_Ack_Topic, onInfoMsg); + serialport->println(onInfoMsg); + + // IP Address + // Serial.println(WiFi.localIP()); + + onInfoMsg[0] = '\0'; + strcat(onInfoMsg, "IP Address: "); + // //strcat(onInfoMsg, myIP.toString()); //This returns Compilation error: request for member 'toString' in 'myIP', which is of non-class type 'IPAddress()' + + char ipString[] = "(0,0,0,0)"; + // // sprintf(ipString, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + // sprintf(ipString, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); + + strcat(onInfoMsg, ipString); // This returns Compilation error: request for member 'toString' in 'myIP', which is of non-class type 'IPAddress()' + + client->publish(publish_Ack_Topic, onInfoMsg); + serialport->println(onInfoMsg); + + // serialport->print("myIP ="); + // serialport->println(myIP); // Caused Error Multiple libraries were found for "WiFiManager.h" + + break; // end of 'i' + } + default: + serialport->println(F("Unknown Command")); + break; + } + serialport->print(F("currentlyMuted : ")); + serialport->println(currentlyMuted); + serialport->println(F("interpret Done")); + // FLE delay(3000); +} // end interpretBuffer() + +// This has to be called periodically, at a minimum to handle the mute_button +void GPAD_HAL_loop() +{ + muteButton.poll(); + encoderSwitchButton.poll(); +#if defined(GPAD) // FLE??? Why is this conditional compile? + muteButton.poll(); +#endif +} + +/* Assumes LCD has been initilized + Turns off Back Light + Clears display + Turns on back light. +*/ +void clearLCD(void) +{ + lcd.noBacklight(); + lcd.clear(); +} + +// Splash a message so we can tell the LCD is working +void splashLCD(void) +{ + lcd.init(); // initialize the lcd + // Print a message to the LCD. + // #if (!LIMIT_POWER_DRAW) + lcd.backlight(); + // #else + // lcd.noBacklight(); + // #endif + + // Line 0 + lcd.setCursor(0, 0); + lcd.print(MODEL_NAME); + // lcd.print(DEVICE_UNDER_TEST); + // lcd.setCursor(3, 1); + lcd.print(PROG_NAME); + lcd.print(" "); + lcd.print(FIRMWARE_VERSION); + + // Line 1 + lcd.setCursor(0, 1); + lcd.print("IP: " + WiFi.localIP().toString()); + + // Line 2 + lcd.setCursor(0, 2); + lcd.print(F(__DATE__ " " __TIME__)); + + // Line 3 + lcd.setCursor(0, 3); + lcd.print("MAC: "); + lcd.print(macAddressString); +} +bool printable(char c) +{ + return isPrintable(c) || (c == ' '); +} +// Remove unwanted characters.... +void filter_control_chars(char *msg) +{ + size_t len = strlen(msg); + char buff[MAX_BUFFER_SIZE]; + strcpy(buff, msg); + int k = 0; + for (int i = 0; i < len; i++) + { + char c = buff[i]; + if (printable(c)) + { + msg[k] = c; + k++; + } + } + msg[k] = '\0'; +} +// TODO: We need to break the message up into strings to render properly +// on the display +void showStatusLCD(AlarmLevel level, bool muted, char *msg) +{ + lcd.init(); + lcd.clear(); + // Possibly we don't need the backlight if the level is zero! + if (level != 0) + { + // #if (!LIMIT_POWER_DRAW) + lcd.backlight(); + // #endif + } + else + { + lcd.noBacklight(); + } + + lcd.print("LVL: "); + lcd.print(level); + lcd.print(" - "); + lcd.print(AlarmNames[level]); + + int msgLineStart = 1; + lcd.setCursor(0, msgLineStart); + int len = strlen(AlarmMessageBuffer); + if (len < 9) + { + if (muted) + { + lcd.print("MUTED! MSG:"); + } + else + { + lcd.print("MSG: "); + } + msgLineStart = 2; + } + if (strlen(AlarmMessageBuffer) == 0) + { + lcd.print("None."); + } + else + { + + char buffer[21] = {0}; // note space for terminator + // filter unmeaningful characters from msg buffer + filter_control_chars(msg); + + size_t len = strlen(msg); // doesn't count terminator + size_t blen = sizeof(buffer) - 1; // doesn't count terminator + size_t i = 0; + // the actual loop that enumerates your buffer + for (i = 0; i < (len / blen + 1) && i + msgLineStart < 4; ++i) + { + memcpy(buffer, msg + (i * blen), blen); + local_ptr_to_serial->println(buffer); + lcd.setCursor(0, i + msgLineStart); + lcd.print(buffer); + } + } +} + +// This operation is idempotent if there is no change in the abstract state. +void set_light_level(int lvl) +{ + for (int i = 0; i < lvl; i++) + { + digitalWrite(LIGHT[i], HIGH); + } + for (int i = lvl; i < NUM_LIGHTS; i++) + { + digitalWrite(LIGHT[i], LOW); + } +} +void unchanged_anunicateAlarmLevel(Stream *serialport) +{ + unsigned long m = millis(); + unsigned long time_in_song = m - start_of_song; + unsigned char note = time_in_song / (unsigned long)LEN_OF_NOTE_MS; + // serialport->print("note: "); + // serialport->println(note); + if (note >= NUM_NOTES) + { + note = 0; + start_of_song = m; + } + unsigned char light_lvl = LIGHT_LEVEL[currentLevel][note]; + set_light_level(light_lvl); + // TODO: Change this to our device types +// #if !defined(HMWK) +#if defined(GPAD) + if (!currentlyMuted) + { + unsigned char note_lvl = SONGS[currentLevel][note]; + + // serialport->print("note lvl"); + // serialport->println(note_lvl); + tone(TONE_PIN, BUZZER_LVL_FREQ_HZ[note_lvl], INF_Dt->printlnURATION); + } + else + { + noTone(TONE_PIN); + } +#endif +} +void restoreAlarmLevel(Stream *serialport) +{ + showStatusLCD(currentLevel, currentlyMuted, AlarmMessageBuffer); +} + +void annunciateAlarmLevel(Stream *serialport) +{ + start_of_song = millis(); + unchanged_anunicateAlarmLevel(serialport); + showStatusLCD(currentLevel, currentlyMuted, AlarmMessageBuffer); + // here is the new call + serialport->println("dfPlayer.play"); + serialport->println(currentLevel); + if (!currentlyMuted) + { + playNotBusyLevel(currentLevel); + } +} + +GPAD_API::GPAD_API(Stream *serial) + : version(SemanticVersion(API_MAJOR_VERSION, API_MINOR_VERSION, API_PATCH_VERSION)), + serial(serial) +{ + + using namespace std::placeholders; + // Setup and present LCD splash screen + // Setup the SWITCH_MUTE + // Setup the SWITCH_ENCODER + // Print instructions on DEBUG serial port + + Wire.begin(); + lcd.init(); +#if (DEBUG > 0) + this->serial->println(F("Clear LCD")); +#endif + clearLCD(); + delay(100); +#if (DEBUG > 0) + this->serial->println(F("Start LCD splash")); +#endif + + splashLCD(); + +#if (DEBUG > 0) + this->serial->println(F("EndLCD splash")); +#endif + + // Setup GPIO pins, Mute and lights + pinMode(SWITCH_MUTE, INPUT_PULLUP); // The SWITCH_MUTE is different on Atmega vs ESP32. Is this redundant? + pinMode(SWITCH_ENCODER, INPUT_PULLUP); // The SWITCH_ENCODER is new to Krake. Is this redundant? + + for (int i = 0; i < NUM_LIGHTS; i++) + { +#if (DEBUG > 0) + this->serial->print(LIGHT[i]); + this->serial->print(", "); +#endif + pinMode(LIGHT[i], OUTPUT); + // Rob trying to prevent resets + // This is necessary on SN#3 + digitalWrite(LIGHT[i], LOW); + } + this->serial->println(""); + + std::function f = std::bind(&GPAD_API::muteButtonCallback, this, _1); + + auto f1 = (void (*)(byte))(&f); + + muteButton.set(SWITCH_MUTE, f1); + muteButton.enableLongPress(longPressTime); + muteButton.enableMultiHit(multiHitTime, multiHitTarget); + + // SW4.set(GPIO_SW4, SendEmergMessage, INT_PULL_UP); + // encoderSwitchButton.set(SWITCH_ENCODER, encoderSwitchCallback, INT_PULL_UP); + // encoderSwitchButton.set(SWITCH_ENCODER, encoderSwitchCallback); + encoderSwitchButton.enableLongPress(longPressTime); + encoderSwitchButton.enableMultiHit(multiHitTime, multiHitTarget); + + printInstructions(this->serial); + AlarmMessageBuffer[0] = '\0'; + + // digitalWrite(LED_BUILTIN, LOW); // turn the LED off at end of setup + +#if !defined(HMWK) // On Homework2, LCD goes blank early + // Here initialize the UART1 + // FLE the Serial1 is faliing to terminate + // pinMode(RXD1, INPUT_PULLUP); + uartSerial1.begin(UART1_BAUD_RATE, SERIAL_8N1, RXD1, TXD1); // UART setup. On Homework2, LCD goes blank early + uartSerial1.flush(); // Clear any Serial1 crud at reset. + +#if (DEBUG > 0) + this->serial->println(F("uartSerial1 Setup")); +#endif +#endif + + // Here initialize the UART2 + pinMode(RXD2, INPUT_PULLUP); + uartSerial2.begin(UART2_BAUD_RATE, SERIAL_8N1, RXD2, TXD2); // UART setup + uartSerial2.flush(); +#if (DEBUG > 0) + this->serial->println(F("uartSerial2 Setup")); +#endif +} + +const SemanticVersion &GPAD_API::getVersion() const +{ + return this->version; +} + +SemanticVersion::SemanticVersion(uint8_t major, uint8_t minor, uint8_t patch) + : major(major), + minor(minor), + patch(patch) +{ +} + +std::string SemanticVersion::toString() const +{ + std::string versionString = std::to_string(this->major); + versionString.push_back('.'); + versionString.append(std::to_string(this->minor)); + versionString.push_back('.'); + versionString.append(std::to_string(this->patch)); + + return versionString; +} \ No newline at end of file diff --git a/Firmware/GPAD_API/GPAD_HAL.h b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.h similarity index 53% rename from Firmware/GPAD_API/GPAD_HAL.h rename to Firmware/GPAD_API/GPAD_API/GPAD_HAL.h index 70c15a44..0db23147 100644 --- a/Firmware/GPAD_API/GPAD_HAL.h +++ b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.h @@ -18,12 +18,11 @@ */ - -#ifndef GPAD_HAL -#define GPAD_HAL 1 -#include -//#include -#include +#ifndef GPAD_HAL_H +#define GPAD_HAL_H +#include +// #include +#include #include // On Nov. 5th, 2024, we image 3 different hardware platforms. @@ -34,32 +33,32 @@ // Define only ONE of these hardware options. // #define GPAD 1 -//#define HMWK 1 +// #define HMWK 1 #define KRAKE 1 // Use these to choose the I2C address of LCD // GPAD device (earlier version of the Krake) -#define LCD_ADDRESS 0x38 +#define LCD_ADDRESS 0x38 // Maryville version and Austin version -// #define LCD_ADDRESS 0x27 +// #define LCD_ADDRESS 0x27 // General (Lebanon) Version -// #define LCD_ADDRESS 0x3F +// #define LCD_ADDRESS 0x3F -//Pin definitions. Assign symbolic constant to Arduino pin numbers. -//For more information see: https://www.arduino.cc/en/Tutorial/Foundations/DigitalPins -// This should be done with an "#elif", but I can't get it to work +// Pin definitions. Assign symbolic constant to Arduino pin numbers. +// For more information see: https://www.arduino.cc/en/Tutorial/Foundations/DigitalPins +// This should be done with an "#elif", but I can't get it to work #if defined(KRAKE) #define SWITCH_MUTE 35 -//#define TONE_PIN 8 +// #define TONE_PIN 8 #define LIGHT0 12 #define LIGHT1 14 #define LIGHT2 27 #define LIGHT3 26 #define LIGHT4 25 #define LED_BUILTIN 13 -#define SWITCH_ENCODER 34 //Center switch aka button Normaly high. +#define SWITCH_ENCODER 34 // Center switch aka button Normaly high. #endif #if defined(GPAD) @@ -77,8 +76,8 @@ // This should be done with an "#elif", but I can't get it to work #if defined(HMWK) -//#define SWITCH_MUTE 34 -#define SWITCH_MUTE 0 //Boot button +// #define SWITCH_MUTE 34 +#define SWITCH_MUTE 0 // Boot button #define LED_D9 23 #define LIGHT0 15 #define LIGHT1 4 @@ -90,28 +89,26 @@ #endif - -#ifdef GPAD_VERSION1 //The Version 1 PCB. -//#define SS 7 // nCS aka /SS Input on GPAD Version 1 PCB. +#ifdef GPAD_VERSION1 // The Version 1 PCB. +// #define SS 7 // nCS aka /SS Input on GPAD Version 1 PCB. #if defined(HMWK) // const int LED_D9 = 23; // Mute1 LED on PMD -#define LED_PIN 23 // for GPAD LIGHT0 -#define BUTTON_PIN 2 //GPAD Button to GND, 10K Resistor to +5V. -#else // compile for an UNO, for example... -#define LED_PIN PD3 // for GPAD LIGHT0 -#define BUTTON_PIN PD2 //GPAD Button to GND, 10K Resistor to +5V. +#define LED_PIN 23 // for GPAD LIGHT0 +#define BUTTON_PIN 2 // GPAD Button to GND, 10K Resistor to +5V. +#else // compile for an UNO, for example... +#define LED_PIN PD3 // for GPAD LIGHT0 +#define BUTTON_PIN PD2 // GPAD Button to GND, 10K Resistor to +5V. #endif -#else //The proof of concept wiring. +#else // The proof of concept wiring. #define LED_PIN 7 -#define BUTTON_PIN 2 //Button to GND, 10K Resistor to +5V. +#define BUTTON_PIN 2 // Button to GND, 10K Resistor to +5V. #endif - -// Define TX and RX pins for UART1 +// Define TX and RX pins for UART1 // For PCB and for Mocking Krake Maryville -//See also Issue# 94 +// See also Issue# 94 #define TXD1 2 #define RXD1 15 #define UART1_BAUD_RATE 115200 @@ -125,6 +122,65 @@ extern HardwareSerial uartSerial1; #define UART2_BAUD_RATE 9600 extern HardwareSerial uartSerial1; +namespace gpad_hal +{ + + static const uint8_t API_MAJOR_VERSION = 0; + static const uint8_t API_MINOR_VERSION = 1; + static const uint8_t API_PATCH_VERSION = 0; + + enum class Command : char + { + MUTE = 's', + UNMUTE = 'u', + HELP = 'h', + ALARM = 'a', + INFO = 'i', + }; + + /** + * SemanticVersion stores a version following the "semantic versioning" convention + * defined here: https://semver.org/ + * + * In summary: + * - Major version defines breaking an incompatible changes with different versions + * - Minor version adds new functionality in a backwards capability + * ie v2.4.1 and v2.5.1 are still compatible but v2.5.1 may have additional functionality + * - Patch version simply addresses bugs with no new features again in a backwards + * compatible way + */ + class SemanticVersion + { + public: + SemanticVersion(uint8_t major, uint8_t minor, uint8_t patch); + + std::string toString() const; + + private: + uint8_t major; + uint8_t minor; + uint8_t patch; + }; + + class GPAD_API + { + public: + GPAD_API(Stream *serial); + + const SemanticVersion &getVersion() const; + + private: + const SemanticVersion version; + Stream *serial; + + void muteButtonCallback(byte buttonEvent); + void encoderSwitchCallback(byte buttonEvent) const; + void showStatusLCD(AlarmLevel level, bool muted, char *msg) const; + }; + + const GPAD_API gpadApi = GPAD_API(SemanticVersion(API_MAJOR_VERSION, API_MINOR_VERSION, API_PATCH_VERSION)); +} + // SPI Functions.... void setup_spi(); void receive_byte(byte c); @@ -142,6 +198,5 @@ void interpretBuffer(char *buf, int rlen, Stream *serialport, PubSubClient *clie void GPAD_HAL_setup(Stream *serialport); void GPAD_HAL_loop(); - extern LiquidCrystal_I2C lcd; #endif diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp b/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp new file mode 100644 index 00000000..79728d58 --- /dev/null +++ b/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include "GPAD_HAL.h" +#include "RickmanLiquidCrystal_I2C.h" +#include "DFPlayer.h" + +using namespace Menu; + +extern bool running_menu; +extern bool menu_just_exited; + +#define LEDPIN 12 +#define MAX_DEPTH 1 + +// This needs to be cleaned up; the PubSublcient and WiFi stuff needs to put into a +// a module that is not in GPAD_API.ino +extern PubSubClient client; +extern char publish_Ack_Topic[17]; + +result action1(eventMask e) +{ + if (e == eventMask::enterEvent) + { + Serial.println(F("Yes, I will take that action #1 !")); + } + char onLineMsg[32] = "Acknowledging!"; + client.publish(publish_Ack_Topic, onLineMsg); + Serial.print("Messgage sent to topic: "); + Serial.println(publish_Ack_Topic); + return proceed; +} +result action2(eventMask e) +{ + if (e == eventMask::enterEvent) + { + Serial.println(F("Yes, I will take that action #2 !")); + } + return proceed; +} +result action3(eventMask e) +{ + if (e == eventMask::enterEvent) + { + Serial.println(F("Yes, I will take that action #3 !")); + } + return proceed; +} +result action4(eventMask e) +{ + if (e == eventMask::enterEvent) + { + Serial.println(F("Yes, I will take that action #3 !")); + } + Serial.print(F("volume value: ")); + Serial.println(volumeDFPlayer); + setVolume(volumeDFPlayer); + return proceed; +} +result action5(eventMask e) +{ + Serial.println("exiting menu"); + running_menu = false; + menu_just_exited = true; + Menu::doExit(); + return proceed; +} + +MENU(mainMenu, "Krake Menu", Menu::doNothing, Menu::noEvent, Menu::wrapStyle, OP("Acknowledge", action1, anyEvent), OP("Dismiss", action2, anyEvent), OP("Shelve", action3, anyEvent), FIELD(volumeDFPlayer, "Volume", "%", 0, 30, 10, 1, action4, anyEvent, wrapStyle), OP("Exit Menu", action5, enterEvent)); + +RotaryEventIn reIn( + RotaryEventIn::EventType::BUTTON_CLICKED | // select + RotaryEventIn::EventType::BUTTON_DOUBLE_CLICKED | // back + RotaryEventIn::EventType::BUTTON_LONG_PRESSED | // also back + RotaryEventIn::EventType::ROTARY_CCW | // up + RotaryEventIn::EventType::ROTARY_CW // down +); // register capabilities, see AndroidMenu MenuIO/RotaryEventIn.h file +MENU_INPUTS(in, &reIn); + +// serialIn serial(Serial); +// MENU_INPUTS(in,&serial); + +MENU_OUTPUTS(out, MAX_DEPTH + // ,SERIAL_OUT(Serial) + , + LCD_OUT(lcd, {0, 0, 20, 4}), NONE // must have 2 items at least +); + +NAVROOT(nav, mainMenu, MAX_DEPTH, in, out); + +void registerRotationEvent(bool CW) +{ + Serial.print("CW: "); + Serial.println(CW); + // Note: Rob believes it is more "natural" for clockwise to mean "up". + // Apparently, whoever wrote the "MENU_INPUTS" believes the opposite, + // so I am changing this hear to reverse the sense. + reIn.registerEvent(CW ? RotaryEventIn::EventType::ROTARY_CCW + : RotaryEventIn::EventType::ROTARY_CW); +} + +void registerRotaryEncoderPress() +{ + reIn.registerEvent(RotaryEventIn::EventType::BUTTON_CLICKED); +} + +void setup_GPAD_menu() +{ +} + +void poll_GPAD_menu() +{ + nav.poll(); +} + +void navigate_to_n_and_execute(int n) +{ + Serial.println("moving to zero and executing!"); + nav.doNav(navCmd(idxCmd, n)); // hilite second option + // nav.doNav(navCmd(enterCmd)); //execute option +} + +void reset_menu_navigation() +{ + running_menu = true; + nav.reset(); +} diff --git a/Firmware/GPAD_API/GPAD_menu.h b/Firmware/GPAD_API/GPAD_API/GPAD_menu.h similarity index 80% rename from Firmware/GPAD_API/GPAD_menu.h rename to Firmware/GPAD_API/GPAD_API/GPAD_menu.h index f8ff20db..3b93b724 100644 --- a/Firmware/GPAD_API/GPAD_menu.h +++ b/Firmware/GPAD_API/GPAD_API/GPAD_menu.h @@ -1,4 +1,5 @@ - +#ifndef GPAD_MENU_H +#define GPAD_MENU_H void setup_GPAD_menu(); @@ -10,3 +11,5 @@ void registerRotationEvent(bool CW); void registerRotaryEncoderPress(); void reset_menu_navigation(); + +#endif diff --git a/Firmware/GPAD_API/InterruptRotator.cpp b/Firmware/GPAD_API/GPAD_API/InterruptRotator.cpp similarity index 68% rename from Firmware/GPAD_API/InterruptRotator.cpp rename to Firmware/GPAD_API/GPAD_API/InterruptRotator.cpp index a0c96e56..88c989eb 100644 --- a/Firmware/GPAD_API/InterruptRotator.cpp +++ b/Firmware/GPAD_API/GPAD_API/InterruptRotator.cpp @@ -1,16 +1,17 @@ #include "InterruptRotator.h" #include "GPAD_menu.h" -static RotaryEncoder* encoder = nullptr; +static RotaryEncoder *encoder = nullptr; // This global variable represents the state of the menu; -// we are either running the menu (true) or displaying other +// we are either running the menu (true) or displaying other // information (false) extern bool running_menu; -void initRotator() { - // Serial.begin(115200); - // while (!Serial); +void initRotator() +{ + // Serial.begin(115200); + // while (!Serial); Serial.println("InterruptRotator example for the RotaryEncoder library."); encoder = new RotaryEncoder(PIN_IN1, PIN_IN2, RotaryEncoder::LatchMode::TWO03); @@ -19,29 +20,32 @@ void initRotator() { attachInterrupt(digitalPinToInterrupt(PIN_IN2), checkPositionISR, CHANGE); } -void updateRotator() { +void updateRotator() +{ static int pos = 0; encoder->tick(); int newPos = encoder->getPosition(); - if (pos != newPos) { - + if (pos != newPos) + { Serial.print("pos: "); Serial.print(newPos); Serial.print(" dir: "); // If we have rotated the encoder, then we enter the menu... if (!running_menu) - reset_menu_navigation(); + reset_menu_navigation(); int d = (int)(encoder->getDirection()); - // Serial.println(d); + // Serial.println(d); - // int d = (int)(encoder->getDirection()); + // int d = (int)(encoder->getDirection()); bool CW; - if (d == (int) RotaryEncoder::Direction::CLOCKWISE) CW = true; - else CW = false; + if (d == (int)RotaryEncoder::Direction::CLOCKWISE) + CW = true; + else + CW = false; // Serial.print("d : "); // Serial.println(d); // Serial.println((int) RotaryEncoder::Direction::CLOCKWISE); @@ -52,8 +56,10 @@ void updateRotator() { } } -void IRAM_ATTR checkPositionISR() { - if (encoder != nullptr) { +void IRAM_ATTR checkPositionISR() +{ + if (encoder != nullptr) + { encoder->tick(); } } diff --git a/Firmware/GPAD_API/InterruptRotator.h b/Firmware/GPAD_API/GPAD_API/InterruptRotator.h similarity index 64% rename from Firmware/GPAD_API/InterruptRotator.h rename to Firmware/GPAD_API/GPAD_API/InterruptRotator.h index 3823bdd1..a092c7d7 100644 --- a/Firmware/GPAD_API/InterruptRotator.h +++ b/Firmware/GPAD_API/GPAD_API/InterruptRotator.h @@ -1,15 +1,14 @@ #ifndef INTERRUPT_ROTATOR_H #define INTERRUPT_ROTATOR_H -//#include +// #include #include - // GPIO definitions for ESP32 #if defined(ESP32) -constexpr int CLK = 36; // Rotary encoder CLK pin -constexpr int DT = 39; // Rotary encoder DT pin -constexpr int SW = 34; // Rotary encoder Switch pin +constexpr int CLK = 36; // Rotary encoder CLK pin +constexpr int DT = 39; // Rotary encoder DT pin +constexpr int SW = 34; // Rotary encoder Switch pin #define PIN_IN1 CLK #define PIN_IN2 DT #endif @@ -19,6 +18,4 @@ void initRotator(); void updateRotator(); void IRAM_ATTR checkPositionISR(); - - #endif // INTERRUPT_ROTATOR_H diff --git a/Firmware/GPAD_API/GPAD_API/README.md b/Firmware/GPAD_API/GPAD_API/README.md new file mode 100644 index 00000000..44659388 --- /dev/null +++ b/Firmware/GPAD_API/GPAD_API/README.md @@ -0,0 +1,119 @@ +# GPAD_API +Description of the GPAD API, the Application Programming Interface. +Version 0.07 +Updated on Date: 20221104 + +This is the description of the API from the point of view of the firmware within the General Purpose Alarm Device, aka the GPAD. + +As of Version 0.07, the interface is through the USB serial and SPI, both. +The serial BAUD rate is fixed at 115200 +Serial messages are terminated with a single **Line Feed**, here after LF character (aka '/n' or New Line, which is ASCII 0x0A or DEC 10). +The GPAD recognizes a limited set of commands. +Some commands are one character only. +Some commands are two characters followed by the LF. +Some commands take messages by concatenating a message of up to 80 additional ASCII characters. + +## The SPI Interface + +The SPI interface was added in version 0.07, and example code for it is available in the GPAD_API_SPI_CONTROLLER directory. Please see the [README](https://github.com/PubInv/general-alarm-device/tree/main/Firmware/GPAD_API_SPI_CONTROLLER) there for more information. + +This SPI-implemented is implemented with two entrypoints, the single funcation ```alarm```, and the function ```alarm_vent``` which takes and event structure. These may contain a null-terminated message string of up to +80 characters. +```C++ +enum AlarmLevel { silent, informational, problem, warning, critical, panic }; +// const char *AlarmNames[] = { "OK ","INFO.","PROB.","WARN ","CRIT.","PANIC" }; +const int NUM_LEVELS = 6; + +const int MAX_MSG_LEN = 80; +const int MAX_BUFFER_SIZE = MAX_MSG_LEN + 1; +typedef struct { + uint8_t lvl; + // we will use a null-terminated string! + char msg[MAX_MSG_LEN+1]; + } AlarmEvent; + +int alarm_event(AlarmEvent& event,Stream &serialport); +int alarm(AlarmLevel level,char *str,Stream &serialport); +``` + +It is our intention to create additional entry points in what we call the "robotic_api" which will eventually explose all of the GPAD hardware to control by SPI. + +## Display Description +The LCD is organized as four rows of twenty characters. +The first row displays the alarm level by number and name { "OK ","INFO.","PROB.","WARN ","CRIT.","PANIC" } +The remaining rows display the message sent by the controller with commands detailed below. + +## Command List +Summary: +Commands fall into categories of **Alarm** and **Mute** and a **Help** message. +Optional arguments are in []. +Commands are case-insensitive. **A0** and **a0** are the same. + +### Alarm Messages +These have the form of a letter "A" and a digit 0-5 inclusive followed optionally by text for the message. +* A0[message for alarm level 0] +* A1[message for alarm level 1] +* A2[message for alarm level 2] +* A3[message for alarm level 3] +* A4[message for alarm level 4] +* A5[message for alarm level 5] + +In addition to Alarm messages writing text to the LCD, the illumination of the five LEDs is also managed. A0 lights no LEDs, A1 through A5 light successively more LEDS vertically up the GPAD. +There are fixed buzzer tones associated with each alarm level, A0-A5 +The "A0" alarm level turns off the LCD back light. All other levels turn on the LCD back light. +Some example alarm messages: +> a0 pseudoSerialVent Testing. All is OK. +> a0Uh, everything's under control. Situation normal. +> a5LUKE, WE'RE GONNA HAVE COMPANY! + +### Mute Messages +The GPAD has a buzzer which can be silenced or Muted by the API. (The user can also press a button to manage the mute state). +Mute messages are single character messages terminated by LF. +* S +* U + +The S command **Silences** or mutes the buzzer. +The U command **Unmutes** the buzzer. + +### Help +* H + +The **H** message is single character messages terminated by LF. +The device will return a message out the serial port with help instructions. +Screen shot of the help message. +![image](https://user-images.githubusercontent.com/5836181/200066531-264861f6-eaba-42e5-be05-d8b6f6640e94.png) + + +## Command Response and Status +After the GPAD receives a command, it returns text string with a response indicating status. +Example of response to the command "a0 pseudoSerialVent Testing. All is OK." +![image](https://user-images.githubusercontent.com/5836181/200065137-465a2ade-5cc2-4c08-925f-df86810f21c1.png) + + +## GPAD MUTE BUTTON +The GPAD has a mute button. Pressing the button will toggle the buzzer on and off. +Text is returned out the serial port indicating the MUTE status as OFF or ON. +![image](https://user-images.githubusercontent.com/5836181/200072832-7efc77ac-50da-4c15-8be6-abd9bafb60cb.png) + + +# Software Organization + +The files in the directory are organized to create as much modularity as possible +to make future enhancement easy. At present these are: + +> alarm_api.cpp +> alarm_api.h +> gpad_serial.cpp +> gpad_serial.h +> gpad_utility.cpp +> gpad_utility.h +> GPAD_HAL.cpp +> GPAD_HAL.h + +The "alarm_api" module is main application programmers interface; it is a very +high-level abstract alarm module. +The "gpad_serial" module handles serial communication of the alarm commands. +The "gpad_utility" contains mostly debugging routines needed by all other modules. +The "GPAD_HAL" is a low-level api concerning the specific API. It is means to +change more rapidly as the hardware evolves than the "alarm_api". +At the time of this writing I will shortly add an "spi" module diff --git a/Firmware/GPAD_API/GPAD_API/RickmanLiquidCrystal_I2C.h b/Firmware/GPAD_API/GPAD_API/RickmanLiquidCrystal_I2C.h new file mode 100644 index 00000000..dc382580 --- /dev/null +++ b/Firmware/GPAD_API/GPAD_API/RickmanLiquidCrystal_I2C.h @@ -0,0 +1,58 @@ +/* -*- C++ -*- */ + +// Note: this file copied and slightly modified from + +#ifndef RSITE_ARDUINO_MENU_RICKMAN_OUT +#define RSITE_ARDUINO_MENU_RICKMAN_OUT + +// #ifndef ARDUINO_SAM_DUE +#include + +#include +// #include +#include + +namespace Menu +{ + + class lcdOut : public cursorOut + { + public: + LiquidCrystal_I2C *device; + inline lcdOut(LiquidCrystal_I2C *o, idx_t *t, panelsList &p, menuOut::styles s = menuOut::minimalRedraw) + : cursorOut(t, p, s), device(o) {} + size_t write(uint8_t ch) override { return device->write(ch); } + void clear() override + { + device->clear(); + panels.reset(); + } + void setCursor(idx_t x, idx_t y, idx_t panelNr = 0) override + { + const panel p = panels[panelNr]; + device->setCursor(p.x + x, p.y + y); + } + idx_t startCursor(navRoot &root, idx_t x, idx_t y, bool charEdit, idx_t panelNr = 0) override { return 0; } + idx_t endCursor(navRoot &root, idx_t x, idx_t y, bool charEdit, idx_t panelNr = 0) override { return 0; } + idx_t editCursor(navRoot &root, idx_t x, idx_t y, bool editing, bool charEdit, idx_t panelNr = 0) override + { + trace(MENU_DEBUG_OUT << "lcdOut::editCursor " << x << "," << y << endl); + // text editor cursor + device->noBlink(); + device->noCursor(); + if (editing) + { + device->setCursor(x, y); + if (charEdit) + device->cursor(); + else + device->blink(); + } + return 0; + } + }; + +} // namespace Menu + +#endif +// #endif \ No newline at end of file diff --git a/Firmware/GPAD_API/WiFiManagerOTA.cpp b/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.cpp similarity index 69% rename from Firmware/GPAD_API/WiFiManagerOTA.cpp rename to Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.cpp index 6b024443..97315851 100644 --- a/Firmware/GPAD_API/WiFiManagerOTA.cpp +++ b/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.cpp @@ -1,27 +1,33 @@ #include "WiFiManagerOTA.h" #include -const char* default_ssid = "ESP32-Setup"; // Default AP Name +const char *default_ssid = "ESP32-Setup"; // Default AP Name String ssid_wf = ""; String password_wf = ""; String ledState = ""; -int WiFiLed = 2; // Modify based on actual LED pin +int WiFiLed = 2; // Modify based on actual LED pin -void saveCredentials(const char* ssid, const char* password) { +void saveCredentials(const char *ssid, const char *password) +{ File file = LittleFS.open("/wifi.txt", "w"); - if (file) { + if (file) + { file.println(ssid); file.println(password); file.close(); Serial.println("WiFi credentials saved."); - } else { + } + else + { Serial.println("Failed to save WiFi credentials."); } } -bool loadCredentials() { +bool loadCredentials() +{ File file = LittleFS.open("/wifi.txt", "r"); - if (!file) { + if (!file) + { Serial.println("No saved WiFi credentials found."); return false; } @@ -36,11 +42,14 @@ bool loadCredentials() { return true; } -void WiFiMan() { +void WiFiMan() +{ WiFiManager wifiManager; - if (!loadCredentials()) { - if (!wifiManager.autoConnect(default_ssid)) { + if (!loadCredentials()) + { + if (!wifiManager.autoConnect(default_ssid)) + { Serial.println("Failed to connect. Restarting..."); ESP.restart(); } @@ -52,32 +61,43 @@ void WiFiMan() { Serial.println("Connected to WiFi!"); } -void initLittleFS() { - if (!LittleFS.begin(true)) { +void initLittleFS() +{ + if (!LittleFS.begin(true)) + { Serial.println("An error occurred while mounting LittleFS."); - } else { - #if (DEBUG >1) + } + else + { +#if (DEBUG > 1) Serial.println("LittleFS mounted successfully."); - #endif +#endif } } -void initWiFi() { +void initWiFi() +{ WiFi.mode(WIFI_STA); WiFi.begin(ssid_wf.c_str(), password_wf.c_str()); Serial.print("Connecting to WiFi .."); - while (WiFi.status() != WL_CONNECTED) { + while (WiFi.status() != WL_CONNECTED) + { Serial.print('.'); delay(1000); } Serial.println("\nConnected. IP Address: " + WiFi.localIP().toString()); } -String processor(const String& var) { - if (var == "STATE") { - if (digitalRead(WiFiLed)) { +String processor(const String &var) +{ + if (var == "STATE") + { + if (digitalRead(WiFiLed)) + { ledState = "ON"; - } else { + } + else + { ledState = "OFF"; } return ledState; diff --git a/Firmware/GPAD_API/WiFiManagerOTA.h b/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.h similarity index 62% rename from Firmware/GPAD_API/WiFiManagerOTA.h rename to Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.h index 96a7cb99..2fc09723 100644 --- a/Firmware/GPAD_API/WiFiManagerOTA.h +++ b/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.h @@ -1,23 +1,23 @@ #ifndef WIFI_MANAGER_H #define WIFI_MANAGER_H -//#include +// #include #include #include #include #include -extern const char* default_ssid; +extern const char *default_ssid; // extern String ssid; // extern String password; extern String ledState; extern int WiFiLed; -void saveCredentials(const char* ssid, const char* password); +void saveCredentials(const char *ssid, const char *password); bool loadCredentials(); void WiFiMan(); void initLittleFS(); void initWiFi(); -String processor(const String& var); +String processor(const String &var); -#endif // WIFI_MANAGER_H +#endif // WIFI_MANAGER_H diff --git a/Firmware/GPAD_API/Wink.cpp b/Firmware/GPAD_API/GPAD_API/Wink.cpp similarity index 57% rename from Firmware/GPAD_API/Wink.cpp rename to Firmware/GPAD_API/GPAD_API/Wink.cpp index 3241c55c..85b7433d 100644 --- a/Firmware/GPAD_API/Wink.cpp +++ b/Firmware/GPAD_API/GPAD_API/Wink.cpp @@ -3,32 +3,34 @@ // Date: 20241013 // LICENSE "GNU Affero General Public License, version 3 " - - // Heart beat aka activity indicator LED. -//Set LED for Uno or ESP32 Dev Kit on board blue LED. -#include "Arduino.h" +// Set LED for Uno or ESP32 Dev Kit on board blue LED. +#include - -//Wink the LED -void wink(void) { +// Wink the LED +void wink(void) +{ // TODO - const int LED_BUILTIN = 13; //ESP32 Kit//const int LED_BUILTIN = 2; HWK2 // Not really needed for Arduino UNO it is defined in library + const int LED_BUILTIN = 13; // ESP32 Kit//const int LED_BUILTIN = 2; HWK2 // Not really needed for Arduino UNO it is defined in library pinMode(LED_BUILTIN, OUTPUT); // const int HIGH_TIME_LED = 900; // const int LOW_TIME_LED = 100; const int HIGH_TIME_LED = 1400; const int LOW_TIME_LED = 500; static unsigned long lastLEDtime = 0; - static unsigned long nextLEDchange = 500; //time in ms. - if (((millis() - lastLEDtime) > nextLEDchange) || (millis() < lastLEDtime)) { - if (digitalRead(LED_BUILTIN) == LOW) { - digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) + static unsigned long nextLEDchange = 500; // time in ms. + if (((millis() - lastLEDtime) > nextLEDchange) || (millis() < lastLEDtime)) + { + if (digitalRead(LED_BUILTIN) == LOW) + { + digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) nextLEDchange = HIGH_TIME_LED; - } else { - digitalWrite(LED_BUILTIN, LOW); // turn the LED on (HIGH is the voltage level) + } + else + { + digitalWrite(LED_BUILTIN, LOW); // turn the LED on (HIGH is the voltage level) nextLEDchange = LOW_TIME_LED; } lastLEDtime = millis(); } -} //end LED wink +} // end LED wink diff --git a/Firmware/GPAD_API/GPAD_API/Wink.h b/Firmware/GPAD_API/GPAD_API/Wink.h new file mode 100644 index 00000000..3cd1b025 --- /dev/null +++ b/Firmware/GPAD_API/GPAD_API/Wink.h @@ -0,0 +1,6 @@ +#ifndef WINK_H +#define WINK_H + +void wink(void); + +#endif diff --git a/Firmware/GPAD_API/alarm_api.cpp b/Firmware/GPAD_API/GPAD_API/alarm_api.cpp similarity index 86% rename from Firmware/GPAD_API/alarm_api.cpp rename to Firmware/GPAD_API/GPAD_API/alarm_api.cpp index 855f0510..5ac2c27a 100644 --- a/Firmware/GPAD_API/alarm_api.cpp +++ b/Firmware/GPAD_API/GPAD_API/alarm_api.cpp @@ -20,7 +20,6 @@ #include "alarm_api.h" #include "gpad_utility.h" - // here is the abstract "state" of the machine, // completely independent of hardware. // This is very simple version of what is probably needed. @@ -31,17 +30,20 @@ AlarmLevel currentLevel = silent; bool currentlyMuted = false; char AlarmMessageBuffer[MAX_BUFFER_SIZE]; -const char *AlarmNames[] = { "OK ", "INFO.", "PROB.", "WARN ", "CRIT.", "PANIC" }; +const char *AlarmNames[] = {"OK ", "INFO.", "PROB.", "WARN ", "CRIT.", "PANIC"}; // This is the abstract alarm function. It CANNOT // assume the msg buffer will exist after this call. // str must be null-terminated string! // It returns the PREVIOUS ALARM LEVEL -int alarm_event(AlarmEvent &event, Stream *serialport) { +int alarm_event(AlarmEvent &event, Stream *serialport) +{ return alarm((AlarmLevel)event.lvl, event.msg, serialport); } -int alarm(AlarmLevel level, char *str, Stream *serialport) { - if (!(level >= 0 && level < NUM_LEVELS)) { +int alarm(AlarmLevel level, char *str, Stream *serialport) +{ + if (!(level >= 0 && level < NUM_LEVELS)) + { serialport->println(F("Bad Level!")); printError(serialport); return -1; @@ -54,11 +56,13 @@ int alarm(AlarmLevel level, char *str, Stream *serialport) { return previousLevel; } -AlarmLevel getCurrentAlarmLevel() { +AlarmLevel getCurrentAlarmLevel() +{ return currentLevel; } -char *getCurrentMessage() { +char *getCurrentMessage() +{ return AlarmMessageBuffer; } diff --git a/Firmware/GPAD_API/alarm_api.h b/Firmware/GPAD_API/GPAD_API/alarm_api.h similarity index 87% rename from Firmware/GPAD_API/alarm_api.h rename to Firmware/GPAD_API/GPAD_API/alarm_api.h index 53784bc2..83d8deb2 100644 --- a/Firmware/GPAD_API/alarm_api.h +++ b/Firmware/GPAD_API/GPAD_API/alarm_api.h @@ -22,18 +22,22 @@ #define ALARM_API #include -enum AlarmLevel { silent, - informational, - problem, - warning, - critical, - panic }; +enum AlarmLevel +{ + silent, + informational, + problem, + warning, + critical, + panic +}; // const char *AlarmNames[] = { "OK ","INFO.","PROB.","WARN ","CRIT.","PANIC" }; const int NUM_LEVELS = 6; const int MAX_MSG_LEN = 80; const int MAX_BUFFER_SIZE = MAX_MSG_LEN + 1; -typedef struct { +typedef struct +{ uint8_t lvl; // we will use a null-terminated string! char msg[MAX_MSG_LEN + 1]; diff --git a/Firmware/GPAD_API/gpad_serial.cpp b/Firmware/GPAD_API/GPAD_API/gpad_serial.cpp similarity index 89% rename from Firmware/GPAD_API/gpad_serial.cpp rename to Firmware/GPAD_API/GPAD_API/gpad_serial.cpp index bb014751..ad974d14 100644 --- a/Firmware/GPAD_API/gpad_serial.cpp +++ b/Firmware/GPAD_API/GPAD_API/gpad_serial.cpp @@ -22,12 +22,10 @@ #include "gpad_utility.h" #include "alarm_api.h" #include "GPAD_HAL.h" -//#include +// #include extern bool currentlyMuted; - - // We accept maessages up to 128 characters, with 2 characters in front, // and an end-of-string delimiter makes 131 characters! const int COMMAND_BUFFER_SIZE = 131; @@ -41,14 +39,16 @@ CD\n where C is an character, and D is a single digit. */ -// Note: The buffer "buf" used here might be more safely made +// Note: The buffer "buf" used here might be more safely made // a parameter passed in from the caller. -void processSerial(Stream *debugPort, Stream *inputPort, PubSubClient *client) { +void processSerial(Stream *debugPort, Stream *inputPort, PubSubClient *client) +{ // Now see if we have a serial command int rlen; // TODO: This code can probably hang; it needs to have // timeouts added! - if (inputPort->available() > 0) { + if (inputPort->available() > 0) + { // TODO: MAKE NON-BLOCKING // read the incoming bytes: int rlen = inputPort->readBytesUntil('\n', buf, COMMAND_BUFFER_SIZE); @@ -64,8 +64,8 @@ void processSerial(Stream *debugPort, Stream *inputPort, PubSubClient *client) { // Now "light and scream"appropriately... // This does not work on HMWK2 device annunciateAlarmLevel(debugPort); -// removing in an attempt to make faster; reason for adding unknown - rlr -// delay(3000); + // removing in an attempt to make faster; reason for adding unknown - rlr + // delay(3000); printAlarmState(debugPort); } } diff --git a/Firmware/GPAD_API/gpad_serial.h b/Firmware/GPAD_API/GPAD_API/gpad_serial.h similarity index 88% rename from Firmware/GPAD_API/gpad_serial.h rename to Firmware/GPAD_API/GPAD_API/gpad_serial.h index d6f00751..a0faa117 100644 --- a/Firmware/GPAD_API/gpad_serial.h +++ b/Firmware/GPAD_API/GPAD_API/gpad_serial.h @@ -21,10 +21,8 @@ #ifndef GPAD_SERIAL #define GPAD_SERIAL 1 #include -#include - - -void processSerial(Stream *debugPort,Stream *inputPort, PubSubClient *client); +#include +void processSerial(Stream *debugPort, Stream *inputPort, PubSubClient *client); #endif diff --git a/Firmware/GPAD_API/gpad_utility.cpp b/Firmware/GPAD_API/GPAD_API/gpad_utility.cpp similarity index 88% rename from Firmware/GPAD_API/gpad_utility.cpp rename to Firmware/GPAD_API/GPAD_API/gpad_utility.cpp index 652db2dd..ca6ecd46 100644 --- a/Firmware/GPAD_API/gpad_utility.cpp +++ b/Firmware/GPAD_API/GPAD_API/gpad_utility.cpp @@ -25,22 +25,27 @@ extern AlarmLevel currentLevel; extern bool currentlyMuted; extern char AlarmMessageBuffer[81]; - -void printError(Stream *serialport) { +void printError(Stream *serialport) +{ serialport->println(F("bad format of command!")); printInstructions(serialport); } -void printInstructions(Stream *serialport) { +void printInstructions(Stream *serialport) +{ serialport->println(F("PubInv GPAD: enter command in form CDa (C is a char, D is a digit)")); } -void printAlarmState(Stream *serialport) { +void printAlarmState(Stream *serialport) +{ serialport->print(F("Muted: ")); serialport->println(currentlyMuted ? "YES" : "NO"); serialport->print(F("LVL: ")); serialport->println(currentLevel); - if (strlen(AlarmMessageBuffer) == 0) { + if (strlen(AlarmMessageBuffer) == 0) + { serialport->println(F("No Message.")); - } else { + } + else + { serialport->print(F("Msg: ")); serialport->println(AlarmMessageBuffer); } diff --git a/Firmware/GPAD_API/gpad_utility.h b/Firmware/GPAD_API/GPAD_API/gpad_utility.h similarity index 75% rename from Firmware/GPAD_API/gpad_utility.h rename to Firmware/GPAD_API/GPAD_API/gpad_utility.h index 328d2ff8..8cd6fcec 100644 --- a/Firmware/GPAD_API/gpad_utility.h +++ b/Firmware/GPAD_API/GPAD_API/gpad_utility.h @@ -21,14 +21,26 @@ #ifndef GPAD_UTILITY #define GPAD_UTILITY 1 #include -#define COMPANY_NAME "PubInv " // For the Broker ID for MQTT -#define PROG_NAME "GPAD_API " // This program -#define FIRMWARE_VERSION "0.44 " // Initial Menu implementation -//#define HARDWARE_VERSION "V0.0.1 " -#define MODEL_NAME "KRAKE_" +#ifndef COMPANY_NAME +#define COMPANY_NAME "" +#endif +#ifndef PROG_NAME +#define PROG_NAME "" +#endif +#ifndef FIRMWARE_VERSION +#define FIRMWARE_VERSION "" +#endif +// #define HARDWARE_VERSION "V0.0.1 " +#ifndef MODEL_NAME +#define MODEL_NAME "" +#endif +#ifndef LICENSE #define LICENSE "GNU Affero General Public License, version 3 " -#define ORIGIN "US" -#define DEVICE_UNDER_TEST "Krake: DFPlayer" //This is GPAD code, but if it is used in testing... +#endif +#ifndef ORIGIN +#define ORIGIN "" +#endif +#define DEVICE_UNDER_TEST "Krake: DFPlayer" // This is GPAD code, but if it is used in testing... // THIS IS FOR DEBUGGING #define LIMIT_POWER_DRAW 1 diff --git a/Firmware/GPAD_API/GPAD_HAL.cpp b/Firmware/GPAD_API/GPAD_HAL.cpp deleted file mode 100644 index f142d064..00000000 --- a/Firmware/GPAD_API/GPAD_HAL.cpp +++ /dev/null @@ -1,703 +0,0 @@ -/* GPAD_HAL.cpp - The Hardware Abstraction Layer (HAL) (low-level hardware) api - - Copyright (C) 2022 Robert Read - - This program includes free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - See the GNU Affero General Public License for more details. - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -*/ - -//#include -#include "GPAD_HAL.h" -#include "alarm_api.h" -#include "gpad_utility.h" -#include -#include "WiFiManagerOTA.h" -#include "GPAD_menu.h" - -extern IPAddress myIP; - -// Use Serial1 for UART communication -HardwareSerial uartSerial1(1); //For user Serial Port -HardwareSerial uartSerial2(2); //For DFPLayer, audio - - -#include -// Time in ms you need to hold down the button to be considered a long press -unsigned int longPressTime = 1000; -// How many times you need to hit the button to be considered a multi-hit -byte multiHitTarget = 2; -// How fast you need to hit all buttons to be considered a multi-hit -unsigned int multiHitTime = 400; - -DailyStruggleButton muteButton; -DailyStruggleButton encoderSwitchButton; - - -extern const char *AlarmNames[]; -extern AlarmLevel currentLevel; -extern bool currentlyMuted; -extern char AlarmMessageBuffer[81]; - -extern char macAddressString[13]; - -// TODO: Remove this; for explanation only -extern char publish_Ack_Topic[17]; - -#include // From library https://github.com/knolleary/pubsubclient - -extern PubSubClient client; - -//For LCD -// #include - -// https://github.com/johnrickman/LiquidCrystal_I2C -LiquidCrystal_I2C lcd(LCD_ADDRESS, 20, 4); - -#include "DFPlayer.h" - -//Setup for buzzer. -//const int BUZZER_TEST_FREQ = 130; // One below middle C3. About 67 db, 3" x 4.875" 8 Ohm speakers no cabinet at 1 Meter. -//const int BUZZER_TEST_FREQ = 260; // Middle C4. About ?? db, 3" x 4.875" 8 Ohm speakers no cabinet at 1 Meter. -//const int BUZZER_TEST_FREQ = 1000; //About 76 db, 3" x 4.875" 8 Ohm speakers no cabinet at 1 Meter. -const int BUZZER_TEST_FREQ = 4000; // Buzzers, 3 V 4kHz 60dB @ 3V, 10 cm. The specified frequencey for the Version 1 buzzer. - -const int BUZZER_LVL_FREQ_HZ[] = { 0, 128, 256, 512, 1024, 2048 }; - -// This as an attempt to program recognizable "songs" for each alarm level that accomplish -// both informativeness and urgency mapping. The is is to use an index into the buzzer -// level frequencies above, so we can use an unsigned char. We can break the whole -// sequence into 100ms chunks. A 0 will make a "rest" or a silence.a length of 60 will -// give us a 6-second repeat. - -const unsigned int NUM_NOTES = 20; -const int SONGS[][NUM_NOTES] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, - { 2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, 2, 0, 0, 0, 0 }, - { 3, 3, 3, 0, 3, 3, 3, 3, 0, 3, 3, 3, 0, 3, 3, 3, 0, 0, 0, 0 }, - { 4, 0, 4, 0, 4, 0, 4, 0, 0, 0, 4, 0, 4, 0, 4, 0, 4, 0, 0, 0 }, - { 4, 4, 2, 0, 4, 4, 2, 0, 4, 4, 2, 0, 4, 4, 2, 0, 4, 4, 2, 0 } }; - -const int LIGHT_LEVEL[][NUM_NOTES] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, - { 2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, 2, 0, 0, 0, 0 }, - { 3, 3, 3, 0, 3, 3, 3, 3, 0, 3, 3, 3, 0, 3, 3, 3, 0, 0, 0, 0 }, - { 4, 4, 4, 0, 4, 4, 4, 0, 0, 0, 4, 4, 4, 0, 4, 4, 4, 0, 0, 0 }, - { 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5 } }; - -const unsigned LEN_OF_NOTE_MS = 500; - -unsigned long start_of_song = 0; - - - -// in general, we want tones to last forever, although -// I may implement blinking later. -const unsigned long INF_DURATION = 4294967295; - -//Allow indexing to LIGHT[] by symbolic name. So LIGHT0 is first and so on. -int LIGHT[] = { LIGHT0, LIGHT1, LIGHT2, LIGHT3, LIGHT4 }; -int NUM_LIGHTS = sizeof(LIGHT) / sizeof(LIGHT[0]); - -Stream *local_ptr_to_serial; - -volatile boolean isReceived_SPI; -volatile byte peripheralReceived; - -volatile bool procNewPacket = false; -volatile byte indx = 0; -volatile boolean process; - -byte received_signal_raw_bytes[MAX_BUFFER_SIZE]; - -// Local DEBUG defines, GPAD_HAL -#define DEBUG 0 -//#define DEBUG 1 - -#if (DEBUG > 0) -Serial.println("Debug defined >0") -#endif - - const int NUM_PREFICES = 5; -char legal_prefices[NUM_PREFICES] = { 'h', 's', 'a', 'u', 'i' }; - - -void setup_spi() { - Serial.println(F("Starting SPI Peripheral.")); - Serial.print(F("Pin for SS: ")); - Serial.println(SS); - - pinMode(BUTTON_PIN, INPUT); // Setting pin 2 as INPUT - pinMode(LED_PIN, OUTPUT); // Setting pin 7 as OUTPUT - - // SPI.begin(); // IMPORTANT. Do not set SPI.begin for a peripherial device. - pinMode(SS, INPUT_PULLUP); //Sets SS as input for peripherial - // Why is this not input? - pinMode(MOSI, INPUT); //This works for Peripheral - pinMode(MISO, OUTPUT); //try this. - pinMode(SCK, INPUT); //Sets clock as input -#if defined(GPAD) - SPCR |= _BV(SPE); //Turn on SPI in Peripheral Mode - // turn on interrupts - SPCR |= _BV(SPIE); - - isReceived_SPI = false; - SPI.attachInterrupt(); //Interuupt ON is set for SPI commnucation -#else -#endif - -} //end setup_SPI() - -//ISRs -// This is the original... -// I plan to add an index to this to handle the full message that we intend to receive. -// However, I think this also needs a timeout to handle the problem of getting out of synch. -const int SPI_BYTE_TIMEOUT_MS = 200; // we don't get the next byte this fast, we reset. -volatile unsigned long last_byte_ms = 0; - -#if defined(HMWK) -// void IRAM_ATTR ISR() { -// receive_byte(SPDR); -// } -#elif defined(GPAD) // compile for an UNO, for example... -ISR(SPI_STC_vect) //Inerrrput routine function -{ - receive_byte(SPDR); -} //end ISR -#endif - - - -void receive_byte(byte c) { - last_byte_ms = millis(); - // byte c = SPDR; // read byte from SPI Data Register - if (indx < sizeof received_signal_raw_bytes) { - received_signal_raw_bytes[indx] = c; // save data in the next index in the array received_signal_raw_bytes - indx = indx + 1; - } - if (indx >= sizeof received_signal_raw_bytes) { - process = true; - } -} - - -void updateFromSPI() { - if (DEBUG > 0) { - if (process) { - Serial.println("process true!"); - } - } - if (process) { - - AlarmEvent event; - event.lvl = (AlarmLevel)received_signal_raw_bytes[0]; - for (int i = 0; i < MAX_MSG_LEN; i++) { - event.msg[i] = (char)received_signal_raw_bytes[1 + i]; - } - - if (DEBUG > 1) { - Serial.print(F("LVL: ")); - Serial.println(event.lvl); - Serial.println(event.msg); - } - int prevLevel = alarm((AlarmLevel)event.lvl, event.msg, &Serial); - if (prevLevel != event.lvl) { - annunciateAlarmLevel(&Serial); - } else { - unchanged_anunicateAlarmLevel(&Serial); - } - - indx = 0; - process = false; - } -} - -// Have to get a serialport here - -//void myCallback(byte buttonEvent) { -void encoderSwitchCallback(byte buttonEvent) { - switch (buttonEvent) { - case onPress: - // Do something... - local_ptr_to_serial->println(F("ENCODER_SWITCH onPress")); - // currentlyMuted = !currentlyMuted; - // start_of_song = millis(); - // annunciateAlarmLevel(local_ptr_to_serial); - // printAlarmState(local_ptr_to_serial); - - registerRotaryEncoderPress(); - break; - case onRelease: - // Do nothing... - local_ptr_to_serial->println(F("ENCODER_SWITCH onRelease")); - break; - case onHold: - // Do nothing... - //local_ptr_to_serial->println(F("ENCODER_SWITCH onHold")); - break; - // onLongPress is indidcated when you hold onto the button - // more than longPressTime in milliseconds - case onLongPress: - Serial.print("ENCODER_SWITCH Button Long Pressed For "); - Serial.print(longPressTime); - Serial.println("ms"); - break; - - // onMultiHit is indicated when you hit the button - // multiHitTarget times within multihitTime in milliseconds - case onMultiHit: - Serial.print("Encoder Switch Button Pressed "); - Serial.print(multiHitTarget); - Serial.print(" times in "); - Serial.print(multiHitTime); - Serial.println("ms"); - break; - default: - Serial.print("Encoder Switch buttonEvent but not reckognized case: "); - Serial.println(buttonEvent); - break; - } -} - -// Have to get a serialport here -//void myCallback(byte buttonEvent) { -void muteButtonCallback(byte buttonEvent) { - switch (buttonEvent) { - case onPress: - // Do something... - local_ptr_to_serial->println(F("SWITCH_MUTE onPress")); - currentlyMuted = !currentlyMuted; - start_of_song = millis(); - annunciateAlarmLevel(local_ptr_to_serial); - printAlarmState(local_ptr_to_serial); - break; - case onRelease: - // Do nothing... - local_ptr_to_serial->println(F("SWITCH_MUTE onRelease")); - break; - case onHold: - // Do nothing... - local_ptr_to_serial->println(F("SWITCH_MUTE onHold")); - break; - // onLongPress is indidcated when you hold onto the button - // more than longPressTime in milliseconds - case onLongPress: - Serial.print("SWITCH_MUTE Long Pressed For "); - Serial.print(longPressTime); - Serial.println("ms"); - break; - - // onMultiHit is indicated when you hit the button - // multiHitTarget times within multihitTime in milliseconds - case onMultiHit: - Serial.print("Button Pressed "); - Serial.print(multiHitTarget); - Serial.print(" times in "); - Serial.print(multiHitTime); - Serial.println("ms"); - break; - default: - Serial.print("Mute buttonEvent but not reckognized case: "); - Serial.println(buttonEvent); - break; - } -} - -void GPAD_HAL_setup(Stream *serialport) { - //Setup and present LCD splash screen - //Setup the SWITCH_MUTE - //Setup the SWITCH_ENCODER - //Print instructions on DEBUG serial port - - local_ptr_to_serial = serialport; - Wire.begin(); - lcd.init(); -#if (DEBUG > 0) - serialport->println(F("Clear LCD")); -#endif - clearLCD(); - delay(100); -#if (DEBUG > 0) - serialport->println(F("Start LCD splash")); -#endif - - splashLCD(); - - -#if (DEBUG > 0) - serialport->println(F("EndLCD splash")); -#endif - - //Setup GPIO pins, Mute and lights - pinMode(SWITCH_MUTE, INPUT_PULLUP); //The SWITCH_MUTE is different on Atmega vs ESP32. Is this redundant? - pinMode(SWITCH_ENCODER, INPUT_PULLUP); //The SWITCH_ENCODER is new to Krake. Is this redundant? - - for (int i = 0; i < NUM_LIGHTS; i++) { -#if (DEBUG > 0) - serialport->print(LIGHT[i]); - serialport->print(", "); -#endif - pinMode(LIGHT[i], OUTPUT); - // Rob trying to prevent resets - // This is necessary on SN#3 - digitalWrite(LIGHT[i],LOW); - } - serialport->println(""); - - muteButton.set(SWITCH_MUTE, muteButtonCallback); - muteButton.enableLongPress(longPressTime); - muteButton.enableMultiHit(multiHitTime, multiHitTarget); - - //SW4.set(GPIO_SW4, SendEmergMessage, INT_PULL_UP); - // encoderSwitchButton.set(SWITCH_ENCODER, encoderSwitchCallback, INT_PULL_UP); - encoderSwitchButton.set(SWITCH_ENCODER, encoderSwitchCallback); - encoderSwitchButton.enableLongPress(longPressTime); - encoderSwitchButton.enableMultiHit(multiHitTime, multiHitTarget); - - printInstructions(serialport); - AlarmMessageBuffer[0] = '\0'; - - // digitalWrite(LED_BUILTIN, LOW); // turn the LED off at end of setup - -#if !defined(HMWK) //On Homework2, LCD goes blank early - // Here initialize the UART1 - //FLE the Serial1 is faliing to terminate - // pinMode(RXD1, INPUT_PULLUP); - uartSerial1.begin(UART1_BAUD_RATE, SERIAL_8N1, RXD1, TXD1); // UART setup. On Homework2, LCD goes blank early - uartSerial1.flush(); //Clear any Serial1 crud at reset. - -#if (DEBUG > 0) - serialport->println(F("uartSerial1 Setup")); -#endif -#endif - - // Here initialize the UART2 - pinMode(RXD2, INPUT_PULLUP); - uartSerial2.begin(UART2_BAUD_RATE, SERIAL_8N1, RXD2, TXD2); // UART setup - uartSerial2.flush(); -#if (DEBUG > 0) - serialport->println(F("uartSerial2 Setup")); -#endif -} // end GPAD_HAL_setup() - -// This routine should be refactored so that it only "interprets" -// the character buffer and returns an "abstract" command to be acted on -// elseshere. This will allow us to remove the PubSubClient from the this file, -// the Hardware Abstraction Layer. -void interpretBuffer(char *buf, int rlen, Stream *serialport, PubSubClient *client) { - if (rlen < 1) { - printError(serialport); - return; // no action - } - - bool found = false; - for (int i = 0; i < NUM_PREFICES; i++) { - if (buf[0] == legal_prefices[i]) found = true; - } - if (!found) { - printError(serialport); - return; - } - char C = buf[0]; - - serialport->print(F("Command: ")); - serialport->println(C); - switch (C) { - case 's': - serialport->println(F("Muting Case!")); - currentlyMuted = true; - break; - case 'u': - serialport->println(F("UnMuting Case!")); - currentlyMuted = false; - break; - case 'h': // help - printInstructions(serialport); - break; - case 'a': - { - // In the case of an alarm state, the rest of the buffer is a message. - // we will read up to 60 characters from this buffer for display on our - // Arguably when we support mulitple states this will become more complicated. - char D = buf[1]; - int N = D - '0'; - serialport->println(N); - // WARNING: Shouldn't this be MAX_BUFFER_SIZE? - char msg[61]; - msg[0] = '\0'; - strncat(msg, buf, 60); - // This copy loooks uncessary, but is not...we want "alarm" - // to be a completely independent and abstract function. - // it should copy the msg buffer - serialport->print("The MQTT Alarm Message: "); - serialport->println(msg); - alarm((AlarmLevel)N, msg, serialport); //Makes Lamps indicate alarm. - - break; - } - case 'i': //Information. Firmware Version, Mute Status, - { - //Firmware Version - // 81+23 = Maximum string length - // char onInfoMsg[32] = "Firmware Version: "; - // static char onInfoMsg[81+24] = "Firmware Version: "; //This does not have the bug. - char onInfoMsg[81 + 24] = "Firmware Version: "; //This - char str[20]; - - strcat(onInfoMsg, FIRMWARE_VERSION); - client->publish(publish_Ack_Topic, onInfoMsg); - serialport->println(onInfoMsg); - - //Up time - onInfoMsg[0] = '\0'; - - str[0] = '\0'; - strcat(onInfoMsg, "System up time (mills): "); - sprintf(str, "%d", millis()); - strcat(onInfoMsg, str); - client->publish(publish_Ack_Topic, onInfoMsg); - serialport->println(onInfoMsg); - - // Mute status - onInfoMsg[0] = '\0'; - //onInfoMsg[32] = "Mute Status: "; - strcat(onInfoMsg, "Mute Status: "); - if (currentlyMuted) { - strcat(onInfoMsg, "MUTED"); - } else { - strcat(onInfoMsg, "NOT MUTED"); - } - client->publish(publish_Ack_Topic, onInfoMsg); - serialport->println(onInfoMsg); - - //Alarm level - onInfoMsg[0] = '\0'; - str[0] = '\0'; - strcat(onInfoMsg, "Current alarm Level: "); - sprintf(str, "%d", getCurrentAlarmLevel()); - strcat(onInfoMsg, str); - client->publish(publish_Ack_Topic, onInfoMsg); - serialport->println(onInfoMsg); - - //Alarm message - onInfoMsg[0] = '\0'; - strcat(onInfoMsg, "Current alarm message: "); - // strcat(onInfoMsg, *getCurrentMessage()); Produced error error: invalid conversion from 'char' to 'const char*' [-fpermissive] - strcat(onInfoMsg, getCurrentMessage()); - client->publish(publish_Ack_Topic, onInfoMsg); - serialport->println(onInfoMsg); - - //IP Address - //Serial.println(WiFi.localIP()); - - onInfoMsg[0] = '\0'; - strcat(onInfoMsg, "IP Address: "); - // //strcat(onInfoMsg, myIP.toString()); //This returns Compilation error: request for member 'toString' in 'myIP', which is of non-class type 'IPAddress()' - - char ipString[] = "(0,0,0,0)"; - // // sprintf(ipString, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - // sprintf(ipString, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); - - strcat(onInfoMsg, ipString); //This returns Compilation error: request for member 'toString' in 'myIP', which is of non-class type 'IPAddress()' - - client->publish(publish_Ack_Topic, onInfoMsg); - serialport->println(onInfoMsg); - - // serialport->print("myIP ="); - // serialport->println(myIP); // Caused Error Multiple libraries were found for "WiFiManager.h" - - break; //end of 'i' - } - default: - serialport->println(F("Unknown Command")); - break; - } - serialport->print(F("currentlyMuted : ")); - serialport->println(currentlyMuted); - serialport->println(F("interpret Done")); - //FLE delay(3000); -} //end interpretBuffer() - - -// This has to be called periodically, at a minimum to handle the mute_button -void GPAD_HAL_loop() { - muteButton.poll(); - encoderSwitchButton.poll(); -#if defined(GPAD) //FLE??? Why is this conditional compile? - muteButton.poll(); -#endif -} - -/* Assumes LCD has been initilized - Turns off Back Light - Clears display - Turns on back light. -*/ -void clearLCD(void) { - lcd.noBacklight(); - lcd.clear(); -} - -//Splash a message so we can tell the LCD is working -void splashLCD(void) { - lcd.init(); // initialize the lcd - // Print a message to the LCD. -//#if (!LIMIT_POWER_DRAW) - lcd.backlight(); -//#else -// lcd.noBacklight(); -//#endif - - //Line 0 - lcd.setCursor(0, 0); - lcd.print(MODEL_NAME); - // lcd.print(DEVICE_UNDER_TEST); - // lcd.setCursor(3, 1); - lcd.print(PROG_NAME); - lcd.print(" "); - lcd.print(FIRMWARE_VERSION); - - //Line 1 - lcd.setCursor(0, 1); - lcd.print("IP: " + WiFi.localIP().toString()); - - //Line 2 - lcd.setCursor(0, 2); - lcd.print(F(__DATE__ " " __TIME__)); - - //Line 3 - lcd.setCursor(0, 3); - lcd.print("MAC: "); - lcd.print(macAddressString); -} -bool printable(char c) { - return isPrintable(c) || (c == ' '); -} -// Remove unwanted characters.... -void filter_control_chars(char *msg) { - size_t len = strlen(msg); - char buff[MAX_BUFFER_SIZE]; - strcpy(buff, msg); - int k = 0; - for (int i = 0; i < len; i++) { - char c = buff[i]; - if (printable(c)) { - msg[k] = c; - k++; - } - } - msg[k] = '\0'; -} -// TODO: We need to break the message up into strings to render properly -// on the display -void showStatusLCD(AlarmLevel level, bool muted, char *msg) { - lcd.init(); - lcd.clear(); - // Possibly we don't need the backlight if the level is zero! - if (level != 0) { -// #if (!LIMIT_POWER_DRAW) - lcd.backlight(); -// #endif - } else { - lcd.noBacklight(); - } - - lcd.print("LVL: "); - lcd.print(level); - lcd.print(" - "); - lcd.print(AlarmNames[level]); - - int msgLineStart = 1; - lcd.setCursor(0, msgLineStart); - int len = strlen(AlarmMessageBuffer); - if (len < 9) { - if (muted) { - lcd.print("MUTED! MSG:"); - } else { - lcd.print("MSG: "); - } - msgLineStart = 2; - } - if (strlen(AlarmMessageBuffer) == 0) { - lcd.print("None."); - } else { - - char buffer[21] = { 0 }; // note space for terminator - // filter unmeaningful characters from msg buffer - filter_control_chars(msg); - - size_t len = strlen(msg); // doesn't count terminator - size_t blen = sizeof(buffer) - 1; // doesn't count terminator - size_t i = 0; - // the actual loop that enumerates your buffer - for (i = 0; i < (len / blen + 1) && i + msgLineStart < 4; ++i) { - memcpy(buffer, msg + (i * blen), blen); - local_ptr_to_serial->println(buffer); - lcd.setCursor(0, i + msgLineStart); - lcd.print(buffer); - } - } -} - -// This operation is idempotent if there is no change in the abstract state. -void set_light_level(int lvl) { - for (int i = 0; i < lvl; i++) { - digitalWrite(LIGHT[i], HIGH); - } - for (int i = lvl; i < NUM_LIGHTS; i++) { - digitalWrite(LIGHT[i], LOW); - } -} -void unchanged_anunicateAlarmLevel(Stream *serialport) { - unsigned long m = millis(); - unsigned long time_in_song = m - start_of_song; - unsigned char note = time_in_song / (unsigned long)LEN_OF_NOTE_MS; - // serialport->print("note: "); - // serialport->println(note); - if (note >= NUM_NOTES) { - note = 0; - start_of_song = m; - } - unsigned char light_lvl = LIGHT_LEVEL[currentLevel][note]; - set_light_level(light_lvl); - // TODO: Change this to our device types -//#if !defined(HMWK) -#if defined(GPAD) - if (!currentlyMuted) { - unsigned char note_lvl = SONGS[currentLevel][note]; - - // serialport->print("note lvl"); - // serialport->println(note_lvl); - tone(TONE_PIN, BUZZER_LVL_FREQ_HZ[note_lvl], INF_DURATION); - - } else { - noTone(TONE_PIN); - } -#endif -} -void restoreAlarmLevel(Stream *serialport) { - showStatusLCD(currentLevel, currentlyMuted, AlarmMessageBuffer); -} - -void annunciateAlarmLevel(Stream *serialport) { - start_of_song = millis(); - unchanged_anunicateAlarmLevel(serialport); - showStatusLCD(currentLevel, currentlyMuted, AlarmMessageBuffer); - // here is the new call - serialport->println("dfPlayer.play"); - serialport->println(currentLevel); - if (!currentlyMuted) { - playNotBusyLevel(currentLevel); - } -} diff --git a/Firmware/GPAD_API/GPAD_menu.cpp b/Firmware/GPAD_API/GPAD_menu.cpp deleted file mode 100644 index 12bc8d11..00000000 --- a/Firmware/GPAD_API/GPAD_menu.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include -#include -#include -#include -#include -#include "GPAD_hal.h" -#include "RickmanLiquidCrystal_I2C.h" -#include "DFPlayer.h" - -using namespace Menu; - -extern bool running_menu; -extern bool menu_just_exited; - -#define LEDPIN 12 -#define MAX_DEPTH 1 - -// This needs to be cleaned up; the PubSublcient and WiFi stuff needs to put into a -// a module that is not in GPAD_API.ino -extern PubSubClient client; -extern char publish_Ack_Topic[17]; - -result action1(eventMask e) { - if (e == eventMask::enterEvent) { - Serial.println(F("Yes, I will take that action #1 !")); - } - char onLineMsg[32] = "Acknowledging!"; - client.publish(publish_Ack_Topic, onLineMsg); - Serial.print("Messgage sent to topic: "); - Serial.println(publish_Ack_Topic); - return proceed; -} -result action2(eventMask e) { - if (e == eventMask::enterEvent) { - Serial.println(F("Yes, I will take that action #2 !")); - } - return proceed; -} -result action3(eventMask e) { - if (e == eventMask::enterEvent) { - Serial.println(F("Yes, I will take that action #3 !")); - } - return proceed; -} -result action4(eventMask e) { - if (e == eventMask::enterEvent) { - Serial.println(F("Yes, I will take that action #3 !")); - } - Serial.print(F("volume value: ")); - Serial.println(volumeDFPlayer); - setVolume(volumeDFPlayer); - return proceed; -} -result action5(eventMask e) { - Serial.println("exiting menu"); - running_menu = false; - menu_just_exited = true; - Menu::doExit(); - return proceed; -} - - -MENU(mainMenu, "Krake Menu", Menu::doNothing, Menu::noEvent, Menu::wrapStyle - ,OP("Acknowledge",action1,anyEvent) - ,OP("Dismiss",action2,anyEvent) - ,OP("Shelve",action3,anyEvent) - ,FIELD(volumeDFPlayer,"Volume","%",0,30,10,1,action4,anyEvent,wrapStyle) - ,OP("Exit Menu", action5, enterEvent) - ); - -RotaryEventIn reIn( - RotaryEventIn::EventType::BUTTON_CLICKED | // select - RotaryEventIn::EventType::BUTTON_DOUBLE_CLICKED | // back - RotaryEventIn::EventType::BUTTON_LONG_PRESSED | // also back - RotaryEventIn::EventType::ROTARY_CCW | // up - RotaryEventIn::EventType::ROTARY_CW // down -); // register capabilities, see AndroidMenu MenuIO/RotaryEventIn.h file -MENU_INPUTS(in,&reIn); - -// serialIn serial(Serial); -// MENU_INPUTS(in,&serial); - - -MENU_OUTPUTS(out,MAX_DEPTH -// ,SERIAL_OUT(Serial) - ,LCD_OUT(lcd,{0,0,20,4}) - ,NONE//must have 2 items at least -); - -NAVROOT(nav,mainMenu,MAX_DEPTH,in,out); - - -void registerRotationEvent(bool CW) { - Serial.print("CW: "); - Serial.println(CW); - // Note: Rob believes it is more "natural" for clockwise to mean "up". - // Apparently, whoever wrote the "MENU_INPUTS" believes the opposite, - // so I am changing this hear to reverse the sense. - reIn.registerEvent(CW ? RotaryEventIn::EventType::ROTARY_CCW - : RotaryEventIn::EventType::ROTARY_CW); -} - -void registerRotaryEncoderPress() { - reIn.registerEvent(RotaryEventIn::EventType::BUTTON_CLICKED); -} - - -void setup_GPAD_menu() { - -} - - -void poll_GPAD_menu() { - nav.poll(); -} - -void navigate_to_n_and_execute(int n) { - Serial.println("moving to zero and executing!"); - nav.doNav(navCmd(idxCmd,n)); //hilite second option - //nav.doNav(navCmd(enterCmd)); //execute option -} - -void reset_menu_navigation() { - running_menu = true; - nav.reset(); - } diff --git a/Firmware/GPAD_API/README.md b/Firmware/GPAD_API/README.md index 44659388..6695e6f9 100644 --- a/Firmware/GPAD_API/README.md +++ b/Firmware/GPAD_API/README.md @@ -1,119 +1,60 @@ -# GPAD_API -Description of the GPAD API, the Application Programming Interface. -Version 0.07 -Updated on Date: 20221104 +# Building -This is the description of the API from the point of view of the firmware within the General Purpose Alarm Device, aka the GPAD. +## PlatformIO IDE (Visual Studio Code Extension) +1. Install the Visual Studio Code (VS Code) text editor + - [Website](https://code.visualstudio.com/) +2. Install the [PlatformIO](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide) VS Code extension + - The provided link has instructions on how install the extension in VS Code +3. The PlatformIO project needs to be opened + - Click the PlatformIO logo/extension on the left tool bar + - Under the `QUICK ACCESS > PIO Home` section, selection `Projects & Configuration` + - At the top, select `Add Existing` and select this directory +4. To compile, select the PlatformIO logo/extension on the left again if it is not still selected + - Under `esp32dev > General` select `Build` and the firmware image will be built + - There are additional options for uploading the firmware to the device and monitoring -As of Version 0.07, the interface is through the USB serial and SPI, both. -The serial BAUD rate is fixed at 115200 -Serial messages are terminated with a single **Line Feed**, here after LF character (aka '/n' or New Line, which is ASCII 0x0A or DEC 10). -The GPAD recognizes a limited set of commands. -Some commands are one character only. -Some commands are two characters followed by the LF. -Some commands take messages by concatenating a message of up to 80 additional ASCII characters. +## PlatformIO from Command Line -## The SPI Interface +Some users prefer not to use VSCode (those who prefer Emacs, Vim, subtext, etc.) -The SPI interface was added in version 0.07, and example code for it is available in the GPAD_API_SPI_CONTROLLER directory. Please see the [README](https://github.com/PubInv/general-alarm-device/tree/main/Firmware/GPAD_API_SPI_CONTROLLER) there for more information. +### PlatformIO Core (CLI) Installation +The official [documentation](https://docs.platformio.org/en/latest/core/installation/shell-commands.html#piocore-install-shell-commands) +has great guide on how to install the CLI tooling. [Here](https://docs.platformio.org/en/latest/core/userguide/index.html#piocore-userguide) +is PlatformIO's guide on how to use the CLI command `pio`. The full guide for PlatformIO Core installation is available [here](https://docs.platformio.org/en/latest/core/installation/index.html). -This SPI-implemented is implemented with two entrypoints, the single funcation ```alarm```, and the function ```alarm_vent``` which takes and event structure. These may contain a null-terminated message string of up to -80 characters. -```C++ -enum AlarmLevel { silent, informational, problem, warning, critical, panic }; -// const char *AlarmNames[] = { "OK ","INFO.","PROB.","WARN ","CRIT.","PANIC" }; -const int NUM_LEVELS = 6; +As the documentation says, you **do not** have to follow all steps in the CLI guide if you have **also** installed the VS Code extension, PlatformIO IDE. It will be advised to follow the directions under the documentation for "Install Shell Commands" to be able to run the commands `pio` and `piodebuggdb` anywhere in the terminal. For the `Makefile` +mentioned below, it is required to install the shell commands for the `make` command to function correctly. -const int MAX_MSG_LEN = 80; -const int MAX_BUFFER_SIZE = MAX_MSG_LEN + 1; -typedef struct { - uint8_t lvl; - // we will use a null-terminated string! - char msg[MAX_MSG_LEN+1]; - } AlarmEvent; +### Build +In the this directory, there is a "make" file. If you have make installed, you can run "make". There are two targets: -int alarm_event(AlarmEvent& event,Stream &serialport); -int alarm(AlarmLevel level,char *str,Stream &serialport); -``` - -It is our intention to create additional entry points in what we call the "robotic_api" which will eventually explose all of the GPAD hardware to control by SPI. - -## Display Description -The LCD is organized as four rows of twenty characters. -The first row displays the alarm level by number and name { "OK ","INFO.","PROB.","WARN ","CRIT.","PANIC" } -The remaining rows display the message sent by the controller with commands detailed below. - -## Command List -Summary: -Commands fall into categories of **Alarm** and **Mute** and a **Help** message. -Optional arguments are in []. -Commands are case-insensitive. **A0** and **a0** are the same. - -### Alarm Messages -These have the form of a letter "A" and a digit 0-5 inclusive followed optionally by text for the message. -* A0[message for alarm level 0] -* A1[message for alarm level 1] -* A2[message for alarm level 2] -* A3[message for alarm level 3] -* A4[message for alarm level 4] -* A5[message for alarm level 5] - -In addition to Alarm messages writing text to the LCD, the illumination of the five LEDs is also managed. A0 lights no LEDs, A1 through A5 light successively more LEDS vertically up the GPAD. -There are fixed buzzer tones associated with each alarm level, A0-A5 -The "A0" alarm level turns off the LCD back light. All other levels turn on the LCD back light. -Some example alarm messages: -> a0 pseudoSerialVent Testing. All is OK. -> a0Uh, everything's under control. Situation normal. -> a5LUKE, WE'RE GONNA HAVE COMPANY! - -### Mute Messages -The GPAD has a buzzer which can be silenced or Muted by the API. (The user can also press a button to manage the mute state). -Mute messages are single character messages terminated by LF. -* S -* U - -The S command **Silences** or mutes the buzzer. -The U command **Unmutes** the buzzer. - -### Help -* H + 1. run --- this does a compile and upload, and begins running a "monitor", which prints the output of the serial monitor and allows commands to be typed in, just as they are typically done in the Arduino IDE. + 2. monitor --- this does a reset and runs the monitor without doing a fully reset. -The **H** message is single character messages terminated by LF. -The device will return a message out the serial port with help instructions. -Screen shot of the help message. -![image](https://user-images.githubusercontent.com/5836181/200066531-264861f6-eaba-42e5-be05-d8b6f6640e94.png) +These commands are implemented on the command line as: +``` + make run + make monitor +``` -## Command Response and Status -After the GPAD receives a command, it returns text string with a response indicating status. -Example of response to the command "a0 pseudoSerialVent Testing. All is OK." -![image](https://user-images.githubusercontent.com/5836181/200065137-465a2ade-5cc2-4c08-925f-df86810f21c1.png) - - -## GPAD MUTE BUTTON -The GPAD has a mute button. Pressing the button will toggle the buzzer on and off. -Text is returned out the serial port indicating the MUTE status as OFF or ON. -![image](https://user-images.githubusercontent.com/5836181/200072832-7efc77ac-50da-4c15-8be6-abd9bafb60cb.png) - - -# Software Organization - -The files in the directory are organized to create as much modularity as possible -to make future enhancement easy. At present these are: - -> alarm_api.cpp -> alarm_api.h -> gpad_serial.cpp -> gpad_serial.h -> gpad_utility.cpp -> gpad_utility.h -> GPAD_HAL.cpp -> GPAD_HAL.h - -The "alarm_api" module is main application programmers interface; it is a very -high-level abstract alarm module. -The "gpad_serial" module handles serial communication of the alarm commands. -The "gpad_utility" contains mostly debugging routines needed by all other modules. -The "GPAD_HAL" is a low-level api concerning the specific API. It is means to -change more rapidly as the hardware evolves than the "alarm_api". -At the time of this writing I will shortly add an "spi" module +# Building LittleFS Filesystem +With PlatformIO, the creation of the `LittleFS` is a separate step from building the firmware image. However, it only has to be built if +there have been changes to the `/data` directory and it only has to be uploaded to the device if the the image has changed or the flash +pages containing the file system have been erased. + +## PlatformIO IDE (VS Code Extension) +In the extension side panel, below the `General` section with the `Build` and `Upload` targets, there is another section, `Platform`. Under +the `Platform` section, there are the `Build Filesystem Image` and `Upload Filesystem Image`. Using the `Upload Filesystem Image` target will +build and upload the file system if the file system needs building. + +## PlatformIO Core (CLI) +The full command is `pio run --target uploadfs` but the `Make` target `make uploadfs` can be used. + +# Krake Installation +Installing/flashing a new firmware image requires the device, Krake, to be set into a state for it to allow a new firmware image. That can be accomplished with the following steps: +1. Connect Krake to power and to the computer +2. Press and hold the BOOT button. +3. While still holding BOOT, press and release the RESET button once. +4. Release the BOOT button. +5. The chip is now in boot mode. You can run your flasher and the port should respond. diff --git a/Firmware/GPAD_API/RickmanLiquidCrystal_I2C.h b/Firmware/GPAD_API/RickmanLiquidCrystal_I2C.h deleted file mode 100644 index d22f9c03..00000000 --- a/Firmware/GPAD_API/RickmanLiquidCrystal_I2C.h +++ /dev/null @@ -1,51 +0,0 @@ -/* -*- C++ -*- */ - -// Note: this file copied and slightly modified from - -#ifndef RSITE_ARDUINO_MENU_RICKMAN_OUT - #define RSITE_ARDUINO_MENU_RICKMAN_OUT - - // #ifndef ARDUINO_SAM_DUE - #include - - #include -// #include - #include - - namespace Menu { - - class lcdOut:public cursorOut { - public: - LiquidCrystal_I2C* device; - inline lcdOut(LiquidCrystal_I2C* o,idx_t *t,panelsList &p,menuOut::styles s=menuOut::minimalRedraw) - :cursorOut(t,p,s),device(o) {} - size_t write(uint8_t ch) override {return device->write(ch);} - void clear() override { - device->clear(); - panels.reset(); - } - void setCursor(idx_t x,idx_t y,idx_t panelNr=0) override { - const panel p=panels[panelNr]; - device->setCursor(p.x+x,p.y+y); - } - idx_t startCursor(navRoot& root,idx_t x,idx_t y,bool charEdit,idx_t panelNr=0) override {return 0;} - idx_t endCursor(navRoot& root,idx_t x,idx_t y,bool charEdit,idx_t panelNr=0) override {return 0;} - idx_t editCursor(navRoot& root,idx_t x,idx_t y,bool editing,bool charEdit,idx_t panelNr=0) override { - trace(MENU_DEBUG_OUT<<"lcdOut::editCursor "<noBlink(); - device->noCursor(); - if (editing) { - device->setCursor(x, y); - if (charEdit) device->cursor(); - else device->blink(); - } - return 0; - } - - }; - - }//namespace Menu - - #endif -// #endif \ No newline at end of file diff --git a/Firmware/GPAD_API/Wink.h b/Firmware/GPAD_API/Wink.h deleted file mode 100644 index c50fb79b..00000000 --- a/Firmware/GPAD_API/Wink.h +++ /dev/null @@ -1,4 +0,0 @@ - - - -void wink(void); diff --git a/Firmware/GPAD_API/include/README b/Firmware/GPAD_API/include/README new file mode 100644 index 00000000..49819c0d --- /dev/null +++ b/Firmware/GPAD_API/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/Firmware/GPAD_API/lib/README b/Firmware/GPAD_API/lib/README new file mode 100644 index 00000000..93793971 --- /dev/null +++ b/Firmware/GPAD_API/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/Firmware/GPAD_API/makefile b/Firmware/GPAD_API/makefile new file mode 100644 index 00000000..75f7ff91 --- /dev/null +++ b/Firmware/GPAD_API/makefile @@ -0,0 +1,26 @@ +# Copyright 2025, Robert Read +# +# This program includes free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# See the GNU Affero General Public License for more details. +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# + + +run: + pio run -t upload \ + && pio device monitor -b 115200 + +monitor: + pio device monitor -b 115200 + +uploadfs: + pio run --target uploadfs diff --git a/Firmware/GPAD_API/platformio.ini b/Firmware/GPAD_API/platformio.ini new file mode 100644 index 00000000..9246147c --- /dev/null +++ b/Firmware/GPAD_API/platformio.ini @@ -0,0 +1,33 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +src_dir = GPAD_API + +[env:esp32dev] +extra_scripts = + pre:pre_extra_script.py +build_flags = + -DELEGANTOTA_USE_ASYNC_WEBSERVER=1 +platform = espressif32@^6.12.0 +board = esp32dev +board_build.filesystem = littlefs +monitor_speed = 115200 +framework = arduino +lib_deps = + mathertel/RotaryEncoder@^1.5.3 + knolleary/PubSubClient@^2.8 + neu-rah/ArduinoMenu library@^4.21.5 + cygig/DailyStruggleButton@^0.5.1 + neu-rah/streamFlow@0.0.0-alpha+sha.bf16ce8926 + dfrobot/DFRobotDFPlayerMini@^1.0.6 + tzapu/WiFiManager@^2.0.17 + ayushsharma82/ElegantOTA@^3.1.7 + marcoschwartz/LiquidCrystal_I2C@^1.1.4 diff --git a/Firmware/GPAD_API/pre_extra_script.py b/Firmware/GPAD_API/pre_extra_script.py new file mode 100644 index 00000000..023a37ef --- /dev/null +++ b/Firmware/GPAD_API/pre_extra_script.py @@ -0,0 +1,14 @@ +Import("env") + +cpp_defines = [ + ("COMPANY_NAME", "PubInv "), # For the Broker ID for MQTT + ("PROG_NAME", "GPAD_API "), # This program + ("FIRMWARE_VERSION", "0.45 "), # Initial Menu implementation + ("MODEL_NAME", "KRAKE_"), + ("LICENSE", "GNU Affero General Public License, version 3 "), + ("ORIGIN", "US"), +] + +stringify_lambda = lambda macro: (macro[0], env.StringifyMacro(macro[1])) + +env.Append(CPPDEFINES=map(stringify_lambda, cpp_defines)) diff --git a/Firmware/GPAD_API/test/README b/Firmware/GPAD_API/test/README new file mode 100644 index 00000000..9b1e87bc --- /dev/null +++ b/Firmware/GPAD_API/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html