From 79a228740b955fc52fd15038ee837a8e4c674526 Mon Sep 17 00:00:00 2001 From: eadmaster <925171+eadmaster@users.noreply.github.com> Date: Fri, 26 Jul 2024 02:29:40 +0200 Subject: [PATCH 1/3] added serial commands support () --- src/core/serialcmds.cpp | 313 ++++++++++++++++++++++++++++++++++++++++ src/core/serialcmds.h | 3 + src/main.cpp | 2 + 3 files changed, 318 insertions(+) create mode 100644 src/core/serialcmds.cpp create mode 100644 src/core/serialcmds.h diff --git a/src/core/serialcmds.cpp b/src/core/serialcmds.cpp new file mode 100644 index 00000000..205cf7ea --- /dev/null +++ b/src/core/serialcmds.cpp @@ -0,0 +1,313 @@ + +#include "serialcmds.h" +#include "globals.h" +#include +#include +#include "modules/others/TV-B-Gone.h" +#include "cJSON.h" +#include // for PRIu64 + +#include +#include +#include "sd_functions.h" +#include "settings.h" +#include "display.h" +#include "modules/rf/rf.h" + + +void SerialPrintHexString(uint64_t val) { + char s[18] = {0}; + //snprintf(s, 10, "%x", val); + //snprintf(s, sizeof(s), "%" PRIx64, val); + snprintf(s, sizeof(s), "%llx", val); + Serial.println(s); +} + +void handleSerialCommands() { + String cmd_str; + + /* + if (Serial.available() >= MIN_CMD_LEN ) { + size_t len = Serial.available(); + char sbuf[len] = {0}; + Serial.readBytes(sbuf, len); + Serial.print("received:"); + Serial.println(sbuf); + //log_d(sbuf); + cmd_str = String(sbuf); + } else { + //Serial.println("nothing received"); + //log_d("nothing received"); + return; + }*/ + + if (Serial.available() >= 1) { + cmd_str = Serial.readStringUntil('\n'); + } else { + // try again on next iteration + return; + } + + //log_d(cmd_str.c_str()); + cmd_str.trim(); + cmd_str.toLowerCase(); // case-insensitive matching + + // TODO: more commands https://docs.flipper.net/development/cli#0Z9fs + + if(cmd_str.startsWith("ir") ) { + + // ir tx
+ // : NEC, NECext, NEC42, NEC42ext, Samsung32, RC6, RC5, RC5X, SIRC, SIRC15, SIRC20, Kaseikyo, RCA + //
and must be in hex format + // e.g. ir tx NEC 04000000 08000000 + + if(cmd_str.startsWith("ir tx nec ")){ + String address = cmd_str.substring(10, 10+8); + String command = cmd_str.substring(19, 19+8); + sendNECCommand(address, command); // TODO: add arg for displayRedStripe optional + return; + } + if(cmd_str.startsWith("ir tx rc5 ")){ + String address = cmd_str.substring(10, 10+8); + String command = cmd_str.substring(19, 19+8); + sendRC5Command(address, command); + return; + } + if(cmd_str.startsWith("ir tx rc6 ")){ + String address = cmd_str.substring(10, 10+8); + String command = cmd_str.substring(19, 19+8); + sendRC6Command(address, command); + return; + } + // TODO: more protocols: Samsung32, SIRC + //if(cmd_str.startsWith("ir tx raw")){ + + if(cmd_str.startsWith("irsend")) { + // tasmota json command https://tasmota.github.io/docs/Tasmota-IR/#sending-ir-commands + // e.g. IRSend {"Protocol":"NEC","Bits":32,"Data":"0x20DF10EF"} + // TODO: rewrite using ArduinoJson parser? + // TODO: decode "data" into "address, command" and use existing "send*Command" funcs + if(IrTx==0) IrTx = LED; // quickfix init issue? CARDPUTER is 44 + + //IRsend irsend(IrTx); //inverted = false + //Serial.println(IrTx); + IRsend irsend(IrTx,true); // Set the GPIO to be used to sending the message. + //IRsend irsend(IrTx); //inverted = false + irsend.begin(); + cJSON *root = cJSON_Parse(cmd_str.c_str() + 6); + if (root == NULL) { + Serial.println("This is NOT json format"); + return; + } + uint16_t bits = 32; // defaults to 32 bits + const char *dataStr = ""; + String protocolStr = "nec"; // defaults to NEC protocol + + cJSON * protocolItem = cJSON_GetObjectItem(root,"protocol"); + cJSON * dataItem = cJSON_GetObjectItem(root, "data"); + cJSON * bitsItem = cJSON_GetObjectItem(root,"bits"); + + if(protocolItem && cJSON_IsString(protocolItem)) protocolStr = protocolItem->valuestring; + if(bitsItem && cJSON_IsNumber(bitsItem)) bits = bitsItem->valueint; + if(dataItem && cJSON_IsString(dataItem)) { + dataStr = dataItem->valuestring; + } else { + Serial.println("missing or invalid data to send"); + return; + } + //String dataStr = cmd_str.substring(36, 36+8); + uint64_t data = strtoul(dataStr, nullptr, 16); + //Serial.println(dataStr); + //SerialPrintHexString(data); + //Serial.println(bits); + //Serial.println(protocolItem->valuestring); + + cJSON_Delete(root); + + if(protocolStr == "nec"){ + // sendNEC(uint64_t data, uint16_t nbits, uint16_t repeat) + irsend.sendNEC(data, bits, 10); + } + // TODO: more protocols + } + + // turn off the led + digitalWrite(IrTx, LED_OFF); + //backToMenu(); + return; + } // end of ir commands + + if(cmd_str.startsWith("rf") || cmd_str.startsWith("subghz" )) { + if(RfTx==0) RfTx=GROVE_SDA; // quick fix + pinMode(RfTx, OUTPUT); + //Serial.println(RfTx); + + /* WIP: + if(cmd_str.startsWith("subghz tx")) { + // flipperzero-like cmd https://docs.flipper.net/development/cli/#wLVht + // e.g. subghz tx 0000000000200001 868250000 403 10 // https://forum.flipper.net/t/friedland-libra-48249sl-wireless-doorbell-request/4528/20 + // {hex_key} {frequency} {te} {count} + }*/ + if(cmd_str.startsWith("rfsend")) { + // tasmota json command https://tasmota.github.io/docs/Tasmota-IR/#sending-ir-commands + // e.g. RfSend {"Data":"0x447503","Bits":24,"Protocol":1,"Pulse":174,"Repeat":10} // on + // e.g. RfSend {"Data":"0x44750C","Bits":24,"Protocol":1,"Pulse":174,"Repeat":10} // off + + cJSON *root = cJSON_Parse(cmd_str.c_str() + 6); + if (root == NULL) { + Serial.println("This is NOT json format"); + return; + } + unsigned int bits = 32; // defaults to 32 bits + const char *dataStr = ""; + int protocol = 1; // defaults to 1 + int pulse = 0; // 0 leave the library use the default value depending on protocol + int repeat = 10; + + cJSON * protocolItem = cJSON_GetObjectItem(root,"protocol"); + cJSON * dataItem = cJSON_GetObjectItem(root, "data"); + cJSON * bitsItem = cJSON_GetObjectItem(root,"bits"); + cJSON * pulseItem = cJSON_GetObjectItem(root,"pulse"); + cJSON * repeatItem = cJSON_GetObjectItem(root,"repeat"); + + if(protocolItem && cJSON_IsNumber(protocolItem)) protocol = protocolItem->valueint; + if(bitsItem && cJSON_IsNumber(bitsItem)) bits = bitsItem->valueint; + if(pulseItem && cJSON_IsNumber(pulseItem)) pulse = pulseItem->valueint; + if(repeatItem && cJSON_IsNumber(repeatItem)) repeat = repeatItem->valueint; + if(dataItem && cJSON_IsString(dataItem)) { + dataStr = dataItem->valuestring; + } else { + Serial.println("missing or invalid data to send"); + cJSON_Delete(root); + return; + } + //String dataStr = cmd_str.substring(36, 36+8); + uint64_t data = strtoul(dataStr, nullptr, 16); + //Serial.println(dataStr); + //SerialPrintHexString(data); + //Serial.println(bits); + + RCSwitch_send(data, bits, pulse, protocol, repeat); + + cJSON_Delete(root); + return; + } + } // endof rf + + if(cmd_str.startsWith("music_player " ) || cmd_str.startsWith("ttf" ) ) { + // TODO: move in audio.cpp module + AudioOutputI2S *audioout = new AudioOutputI2S(); // https://github.com/earlephilhower/ESP8266Audio/blob/master/src/AudioOutputI2S.cpp#L32 + #ifdef CARDPUTER + audioout->SetPinout(41, 43, 42); + // TODO: other pinouts + #endif + AudioGenerator* generator = NULL; + AudioFileSource* source = NULL; + + if(cmd_str.startsWith("music_player " ) ) { // || cmd_str.startsWith("play " ) + String song = cmd_str.substring(13, cmd_str.length()); + if(song.indexOf(":") != -1) { + // RTTTL player + // music_player mario:d=4,o=5,b=100:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g,8p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,16p,8c6,16p,8g,16p,8e,16p,8a,8b,16a#,8a,16g.,16e6,16g6,8a6,16f6,8g6,8e6,16c6,16d6,8b,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16c7,16p,16c7,16c7,p,16g6,16f#6,16f6,16d#6,16p,16e6,16p,16g#,16a,16c6,16p,16a,16c6,16d6,8p,16d#6,8p,16d6,8p,16c6 + // derived from https://github.com/earlephilhower/ESP8266Audio/blob/master/examples/PlayRTTTLToI2SDAC/PlayRTTTLToI2SDAC.ino + generator = new AudioGeneratorRTTTL(); + source = new AudioFileSourcePROGMEM( song.c_str(), song.length() ); + } else if(song.indexOf(".") != -1) { + // try to open "song" as a file + // e.g. music_player audio/Axel-F.txt + if(!song.startsWith("/")) song = "/" + song; // add "/" if missing + // try opening on SD + //if(setupSdCard()) source = new AudioFileSourceFS(SD, song.c_str()); + if(setupSdCard()) source = new AudioFileSourceSD(song.c_str()); + // try opening on LittleFS + //if(!source) source = new AudioFileSourceFS(LittleFS, song.c_str()); + if(!source) source = new AudioFileSourceLittleFS(song.c_str()); + if(!source) { + Serial.print("audio file not found: "); + Serial.println(song); + return; + } + if(source){ + // switch on extension + song.toLowerCase(); // case-insensitive match + if(song.endsWith(".txt") || song.endsWith(".rtttl")) generator = new AudioGeneratorRTTTL(); + /* 2FIX: compilation issues + if(song.endsWith(".mid")) { + // need to load a soundfont + AudioFileSource* sf2 = NULL; + if(setupSdCard()) sf2 = new AudioFileSourceSD("audio/1mgm.sf2"); // TODO: make configurable + if(!sf2) sf2 = new AudioFileSourceLittleFS("audio/1mgm.sf2"); // TODO: make configurable + if(sf2) { + // a soundfount was found + AudioGeneratorMIDI* midi = new AudioGeneratorMIDI(); + generator->SetSoundfont(sf2); + generator = midi; + } + }*/ + if(song.endsWith(".wav")) generator = new AudioGeneratorWAV(); + if(song.endsWith(".mod")) generator = new AudioGeneratorMOD(); + if(song.endsWith(".mp3")) { + generator = new AudioGeneratorMP3(); + source = new AudioFileSourceID3(source); + } + if(song.endsWith(".opus")) generator = new AudioGeneratorOpus(); + // TODO: more formats + } + } + } + + //TODO: tone + // https://github.com/earlephilhower/ESP8266Audio/issues/643 + + //TODO: webradio + // https://github.com/earlephilhower/ESP8266Audio/tree/master/examples/WebRadio + + if(cmd_str.startsWith("tts " ) || cmd_str.startsWith("say " )) { + // https://github.com/earlephilhower/ESP8266SAM/blob/master/examples/Speak/Speak.ino + audioout->begin(); + ESP8266SAM *sam = new ESP8266SAM; + sam->Say(audioout, cmd_str.c_str() + strlen("tts ")); + delete sam; + return; + } + + if(generator && source && audioout) { + generator->begin(source, audioout); + // TODO async play + while (generator->isRunning()) { + if (!generator->loop()) generator->stop(); + } + delete generator; delete source, delete audioout; + return; + } + } // end of music_player + + // WIP: record | mic + // https://github.com/earlephilhower/ESP8266Audio/issues/70 + // https://github.com/earlephilhower/ESP8266Audio/pull/118 + +/* TODO: rewrite using the new screen dimmer feat. + if(cmd_str.startsWith("lcd " ) || cmd_str.startsWith("tft" ) ) { + String new_status = cmd_str.substring(strlen("lcd "), cmd_str.length()); + if(new_status=="off") { + analogWrite(BACKLIGHT, 0); + esp_timer_stop(screensaver_timer); + } else if(new_status=="on") { + getBrightness(); // reinit brightness + reset_screensaver_timer(); + } + return; + } +*/ + + if(cmd_str == "clock" ) { + //esp_timer_stop(screensaver_timer); // disable screensaver while the clock is running + runClockLoop(); + return; + } + + Serial.println("unsupported serial command: " + cmd_str); + + +} + diff --git a/src/core/serialcmds.h b/src/core/serialcmds.h new file mode 100644 index 00000000..8ffcac56 --- /dev/null +++ b/src/core/serialcmds.h @@ -0,0 +1,3 @@ + + +void handleSerialCommands(); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b3a10d1e..58eff0f3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,6 +56,7 @@ TFT_eSprite draw = TFT_eSprite(&tft); #include "core/sd_functions.h" #include "core/settings.h" #include "core/main_menu.h" +#include "core/serialcmds.h" /********************************************************************* @@ -253,6 +254,7 @@ void loop() { delay(200); } + handleSerialCommands(); checkShortcutPress(); // shortctus to quickly start apps without navigating the menus if (checkPrevPress()) { From fec4782ab81791b2ce8cb78564f3c4fb8fbf4887 Mon Sep 17 00:00:00 2001 From: eadmaster <925171+eadmaster@users.noreply.github.com> Date: Fri, 26 Jul 2024 02:30:23 +0200 Subject: [PATCH 2/3] updated platformio deps --- platformio.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index c6ed2aae..3e599765 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,6 +22,7 @@ build_flags = -DLH=8 -DLW=6 -DCONFIG_FILE='"/config.conf"' + -DARDUINO_USB_CDC_ON_BOOT=1 lib_deps = WireGuard-ESP32 IRremoteESP8266 @@ -35,7 +36,9 @@ lib_deps = bblanchon/ArduinoJson rc-switch ;https://github.com/Martin-Laclaustra/rc-switch.git#transmittimingsarray - + ESP8266Audio + FFat + ESP8266SAM [env:m5stack-cplus2] platform = espressif32 From 4744c3169b6750ce1cd2a042dbee9a343a176ec6 Mon Sep 17 00:00:00 2001 From: eadmaster <925171+eadmaster@users.noreply.github.com> Date: Fri, 26 Jul 2024 03:38:31 +0200 Subject: [PATCH 3/3] ARDUINO_USB_CDC_ON_BOOT is required for cardputer only (#64) --- platformio.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 3e599765..c8b7c936 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ build_flags = -DLH=8 -DLW=6 -DCONFIG_FILE='"/config.conf"' - -DARDUINO_USB_CDC_ON_BOOT=1 + lib_deps = WireGuard-ESP32 IRremoteESP8266 @@ -229,6 +229,8 @@ build_flags = -DGROVE_SDA=2 -DGROVE_SCL=1 + + -DARDUINO_USB_CDC_ON_BOOT=1 lib_deps = ${common.lib_deps} xylopyrographer/LiteLED@^1.2.0