|
| 1 | + |
| 2 | +#include "serialcmds.h" |
| 3 | +#include "globals.h" |
| 4 | +#include <IRsend.h> |
| 5 | +#include <string> |
| 6 | +#include "modules/others/TV-B-Gone.h" |
| 7 | +#include "cJSON.h" |
| 8 | +#include <inttypes.h> // for PRIu64 |
| 9 | + |
| 10 | +#include <ESP8266Audio.h> |
| 11 | +#include <ESP8266SAM.h> |
| 12 | +#include "sd_functions.h" |
| 13 | +#include "settings.h" |
| 14 | +#include "display.h" |
| 15 | +#include "modules/rf/rf.h" |
| 16 | + |
| 17 | + |
| 18 | +void SerialPrintHexString(uint64_t val) { |
| 19 | + char s[18] = {0}; |
| 20 | + //snprintf(s, 10, "%x", val); |
| 21 | + //snprintf(s, sizeof(s), "%" PRIx64, val); |
| 22 | + snprintf(s, sizeof(s), "%llx", val); |
| 23 | + Serial.println(s); |
| 24 | +} |
| 25 | + |
| 26 | +void handleSerialCommands() { |
| 27 | + String cmd_str; |
| 28 | + |
| 29 | + /* |
| 30 | + if (Serial.available() >= MIN_CMD_LEN ) { |
| 31 | + size_t len = Serial.available(); |
| 32 | + char sbuf[len] = {0}; |
| 33 | + Serial.readBytes(sbuf, len); |
| 34 | + Serial.print("received:"); |
| 35 | + Serial.println(sbuf); |
| 36 | + //log_d(sbuf); |
| 37 | + cmd_str = String(sbuf); |
| 38 | + } else { |
| 39 | + //Serial.println("nothing received"); |
| 40 | + //log_d("nothing received"); |
| 41 | + return; |
| 42 | + }*/ |
| 43 | + |
| 44 | + if (Serial.available() >= 1) { |
| 45 | + cmd_str = Serial.readStringUntil('\n'); |
| 46 | + } else { |
| 47 | + // try again on next iteration |
| 48 | + return; |
| 49 | + } |
| 50 | + |
| 51 | + //log_d(cmd_str.c_str()); |
| 52 | + cmd_str.trim(); |
| 53 | + cmd_str.toLowerCase(); // case-insensitive matching |
| 54 | + |
| 55 | + // TODO: more commands https://docs.flipper.net/development/cli#0Z9fs |
| 56 | + |
| 57 | + if(cmd_str.startsWith("ir") ) { |
| 58 | + |
| 59 | + // ir tx <protocol> <address> <command> |
| 60 | + // <protocol>: NEC, NECext, NEC42, NEC42ext, Samsung32, RC6, RC5, RC5X, SIRC, SIRC15, SIRC20, Kaseikyo, RCA |
| 61 | + // <address> and <command> must be in hex format |
| 62 | + // e.g. ir tx NEC 04000000 08000000 |
| 63 | + |
| 64 | + if(cmd_str.startsWith("ir tx nec ")){ |
| 65 | + String address = cmd_str.substring(10, 10+8); |
| 66 | + String command = cmd_str.substring(19, 19+8); |
| 67 | + sendNECCommand(address, command); // TODO: add arg for displayRedStripe optional |
| 68 | + return; |
| 69 | + } |
| 70 | + if(cmd_str.startsWith("ir tx rc5 ")){ |
| 71 | + String address = cmd_str.substring(10, 10+8); |
| 72 | + String command = cmd_str.substring(19, 19+8); |
| 73 | + sendRC5Command(address, command); |
| 74 | + return; |
| 75 | + } |
| 76 | + if(cmd_str.startsWith("ir tx rc6 ")){ |
| 77 | + String address = cmd_str.substring(10, 10+8); |
| 78 | + String command = cmd_str.substring(19, 19+8); |
| 79 | + sendRC6Command(address, command); |
| 80 | + return; |
| 81 | + } |
| 82 | + // TODO: more protocols: Samsung32, SIRC |
| 83 | + //if(cmd_str.startsWith("ir tx raw")){ |
| 84 | + |
| 85 | + if(cmd_str.startsWith("irsend")) { |
| 86 | + // tasmota json command https://tasmota.github.io/docs/Tasmota-IR/#sending-ir-commands |
| 87 | + // e.g. IRSend {"Protocol":"NEC","Bits":32,"Data":"0x20DF10EF"} |
| 88 | + // TODO: rewrite using ArduinoJson parser? |
| 89 | + // TODO: decode "data" into "address, command" and use existing "send*Command" funcs |
| 90 | + if(IrTx==0) IrTx = LED; // quickfix init issue? CARDPUTER is 44 |
| 91 | + |
| 92 | + //IRsend irsend(IrTx); //inverted = false |
| 93 | + //Serial.println(IrTx); |
| 94 | + IRsend irsend(IrTx,true); // Set the GPIO to be used to sending the message. |
| 95 | + //IRsend irsend(IrTx); //inverted = false |
| 96 | + irsend.begin(); |
| 97 | + cJSON *root = cJSON_Parse(cmd_str.c_str() + 6); |
| 98 | + if (root == NULL) { |
| 99 | + Serial.println("This is NOT json format"); |
| 100 | + return; |
| 101 | + } |
| 102 | + uint16_t bits = 32; // defaults to 32 bits |
| 103 | + const char *dataStr = ""; |
| 104 | + String protocolStr = "nec"; // defaults to NEC protocol |
| 105 | + |
| 106 | + cJSON * protocolItem = cJSON_GetObjectItem(root,"protocol"); |
| 107 | + cJSON * dataItem = cJSON_GetObjectItem(root, "data"); |
| 108 | + cJSON * bitsItem = cJSON_GetObjectItem(root,"bits"); |
| 109 | + |
| 110 | + if(protocolItem && cJSON_IsString(protocolItem)) protocolStr = protocolItem->valuestring; |
| 111 | + if(bitsItem && cJSON_IsNumber(bitsItem)) bits = bitsItem->valueint; |
| 112 | + if(dataItem && cJSON_IsString(dataItem)) { |
| 113 | + dataStr = dataItem->valuestring; |
| 114 | + } else { |
| 115 | + Serial.println("missing or invalid data to send"); |
| 116 | + return; |
| 117 | + } |
| 118 | + //String dataStr = cmd_str.substring(36, 36+8); |
| 119 | + uint64_t data = strtoul(dataStr, nullptr, 16); |
| 120 | + //Serial.println(dataStr); |
| 121 | + //SerialPrintHexString(data); |
| 122 | + //Serial.println(bits); |
| 123 | + //Serial.println(protocolItem->valuestring); |
| 124 | + |
| 125 | + cJSON_Delete(root); |
| 126 | + |
| 127 | + if(protocolStr == "nec"){ |
| 128 | + // sendNEC(uint64_t data, uint16_t nbits, uint16_t repeat) |
| 129 | + irsend.sendNEC(data, bits, 10); |
| 130 | + } |
| 131 | + // TODO: more protocols |
| 132 | + } |
| 133 | + |
| 134 | + // turn off the led |
| 135 | + digitalWrite(IrTx, LED_OFF); |
| 136 | + //backToMenu(); |
| 137 | + return; |
| 138 | + } // end of ir commands |
| 139 | + |
| 140 | + if(cmd_str.startsWith("rf") || cmd_str.startsWith("subghz" )) { |
| 141 | + if(RfTx==0) RfTx=GROVE_SDA; // quick fix |
| 142 | + pinMode(RfTx, OUTPUT); |
| 143 | + //Serial.println(RfTx); |
| 144 | + |
| 145 | + /* WIP: |
| 146 | + if(cmd_str.startsWith("subghz tx")) { |
| 147 | + // flipperzero-like cmd https://docs.flipper.net/development/cli/#wLVht |
| 148 | + // e.g. subghz tx 0000000000200001 868250000 403 10 // https://forum.flipper.net/t/friedland-libra-48249sl-wireless-doorbell-request/4528/20 |
| 149 | + // {hex_key} {frequency} {te} {count} |
| 150 | + }*/ |
| 151 | + if(cmd_str.startsWith("rfsend")) { |
| 152 | + // tasmota json command https://tasmota.github.io/docs/Tasmota-IR/#sending-ir-commands |
| 153 | + // e.g. RfSend {"Data":"0x447503","Bits":24,"Protocol":1,"Pulse":174,"Repeat":10} // on |
| 154 | + // e.g. RfSend {"Data":"0x44750C","Bits":24,"Protocol":1,"Pulse":174,"Repeat":10} // off |
| 155 | + |
| 156 | + cJSON *root = cJSON_Parse(cmd_str.c_str() + 6); |
| 157 | + if (root == NULL) { |
| 158 | + Serial.println("This is NOT json format"); |
| 159 | + return; |
| 160 | + } |
| 161 | + unsigned int bits = 32; // defaults to 32 bits |
| 162 | + const char *dataStr = ""; |
| 163 | + int protocol = 1; // defaults to 1 |
| 164 | + int pulse = 0; // 0 leave the library use the default value depending on protocol |
| 165 | + int repeat = 10; |
| 166 | + |
| 167 | + cJSON * protocolItem = cJSON_GetObjectItem(root,"protocol"); |
| 168 | + cJSON * dataItem = cJSON_GetObjectItem(root, "data"); |
| 169 | + cJSON * bitsItem = cJSON_GetObjectItem(root,"bits"); |
| 170 | + cJSON * pulseItem = cJSON_GetObjectItem(root,"pulse"); |
| 171 | + cJSON * repeatItem = cJSON_GetObjectItem(root,"repeat"); |
| 172 | + |
| 173 | + if(protocolItem && cJSON_IsNumber(protocolItem)) protocol = protocolItem->valueint; |
| 174 | + if(bitsItem && cJSON_IsNumber(bitsItem)) bits = bitsItem->valueint; |
| 175 | + if(pulseItem && cJSON_IsNumber(pulseItem)) pulse = pulseItem->valueint; |
| 176 | + if(repeatItem && cJSON_IsNumber(repeatItem)) repeat = repeatItem->valueint; |
| 177 | + if(dataItem && cJSON_IsString(dataItem)) { |
| 178 | + dataStr = dataItem->valuestring; |
| 179 | + } else { |
| 180 | + Serial.println("missing or invalid data to send"); |
| 181 | + cJSON_Delete(root); |
| 182 | + return; |
| 183 | + } |
| 184 | + //String dataStr = cmd_str.substring(36, 36+8); |
| 185 | + uint64_t data = strtoul(dataStr, nullptr, 16); |
| 186 | + //Serial.println(dataStr); |
| 187 | + //SerialPrintHexString(data); |
| 188 | + //Serial.println(bits); |
| 189 | + |
| 190 | + RCSwitch_send(data, bits, pulse, protocol, repeat); |
| 191 | + |
| 192 | + cJSON_Delete(root); |
| 193 | + return; |
| 194 | + } |
| 195 | + } // endof rf |
| 196 | + |
| 197 | + if(cmd_str.startsWith("music_player " ) || cmd_str.startsWith("ttf" ) ) { |
| 198 | + // TODO: move in audio.cpp module |
| 199 | + AudioOutputI2S *audioout = new AudioOutputI2S(); // https://github.com/earlephilhower/ESP8266Audio/blob/master/src/AudioOutputI2S.cpp#L32 |
| 200 | + #ifdef CARDPUTER |
| 201 | + audioout->SetPinout(41, 43, 42); |
| 202 | + // TODO: other pinouts |
| 203 | + #endif |
| 204 | + AudioGenerator* generator = NULL; |
| 205 | + AudioFileSource* source = NULL; |
| 206 | + |
| 207 | + if(cmd_str.startsWith("music_player " ) ) { // || cmd_str.startsWith("play " ) |
| 208 | + String song = cmd_str.substring(13, cmd_str.length()); |
| 209 | + if(song.indexOf(":") != -1) { |
| 210 | + // RTTTL player |
| 211 | + // 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 |
| 212 | + // derived from https://github.com/earlephilhower/ESP8266Audio/blob/master/examples/PlayRTTTLToI2SDAC/PlayRTTTLToI2SDAC.ino |
| 213 | + generator = new AudioGeneratorRTTTL(); |
| 214 | + source = new AudioFileSourcePROGMEM( song.c_str(), song.length() ); |
| 215 | + } else if(song.indexOf(".") != -1) { |
| 216 | + // try to open "song" as a file |
| 217 | + // e.g. music_player audio/Axel-F.txt |
| 218 | + if(!song.startsWith("/")) song = "/" + song; // add "/" if missing |
| 219 | + // try opening on SD |
| 220 | + //if(setupSdCard()) source = new AudioFileSourceFS(SD, song.c_str()); |
| 221 | + if(setupSdCard()) source = new AudioFileSourceSD(song.c_str()); |
| 222 | + // try opening on LittleFS |
| 223 | + //if(!source) source = new AudioFileSourceFS(LittleFS, song.c_str()); |
| 224 | + if(!source) source = new AudioFileSourceLittleFS(song.c_str()); |
| 225 | + if(!source) { |
| 226 | + Serial.print("audio file not found: "); |
| 227 | + Serial.println(song); |
| 228 | + return; |
| 229 | + } |
| 230 | + if(source){ |
| 231 | + // switch on extension |
| 232 | + song.toLowerCase(); // case-insensitive match |
| 233 | + if(song.endsWith(".txt") || song.endsWith(".rtttl")) generator = new AudioGeneratorRTTTL(); |
| 234 | + /* 2FIX: compilation issues |
| 235 | + if(song.endsWith(".mid")) { |
| 236 | + // need to load a soundfont |
| 237 | + AudioFileSource* sf2 = NULL; |
| 238 | + if(setupSdCard()) sf2 = new AudioFileSourceSD("audio/1mgm.sf2"); // TODO: make configurable |
| 239 | + if(!sf2) sf2 = new AudioFileSourceLittleFS("audio/1mgm.sf2"); // TODO: make configurable |
| 240 | + if(sf2) { |
| 241 | + // a soundfount was found |
| 242 | + AudioGeneratorMIDI* midi = new AudioGeneratorMIDI(); |
| 243 | + generator->SetSoundfont(sf2); |
| 244 | + generator = midi; |
| 245 | + } |
| 246 | + }*/ |
| 247 | + if(song.endsWith(".wav")) generator = new AudioGeneratorWAV(); |
| 248 | + if(song.endsWith(".mod")) generator = new AudioGeneratorMOD(); |
| 249 | + if(song.endsWith(".mp3")) { |
| 250 | + generator = new AudioGeneratorMP3(); |
| 251 | + source = new AudioFileSourceID3(source); |
| 252 | + } |
| 253 | + if(song.endsWith(".opus")) generator = new AudioGeneratorOpus(); |
| 254 | + // TODO: more formats |
| 255 | + } |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + //TODO: tone |
| 260 | + // https://github.com/earlephilhower/ESP8266Audio/issues/643 |
| 261 | + |
| 262 | + //TODO: webradio |
| 263 | + // https://github.com/earlephilhower/ESP8266Audio/tree/master/examples/WebRadio |
| 264 | + |
| 265 | + if(cmd_str.startsWith("tts " ) || cmd_str.startsWith("say " )) { |
| 266 | + // https://github.com/earlephilhower/ESP8266SAM/blob/master/examples/Speak/Speak.ino |
| 267 | + audioout->begin(); |
| 268 | + ESP8266SAM *sam = new ESP8266SAM; |
| 269 | + sam->Say(audioout, cmd_str.c_str() + strlen("tts ")); |
| 270 | + delete sam; |
| 271 | + return; |
| 272 | + } |
| 273 | + |
| 274 | + if(generator && source && audioout) { |
| 275 | + generator->begin(source, audioout); |
| 276 | + // TODO async play |
| 277 | + while (generator->isRunning()) { |
| 278 | + if (!generator->loop()) generator->stop(); |
| 279 | + } |
| 280 | + delete generator; delete source, delete audioout; |
| 281 | + return; |
| 282 | + } |
| 283 | + } // end of music_player |
| 284 | + |
| 285 | + // WIP: record | mic |
| 286 | + // https://github.com/earlephilhower/ESP8266Audio/issues/70 |
| 287 | + // https://github.com/earlephilhower/ESP8266Audio/pull/118 |
| 288 | + |
| 289 | +/* TODO: rewrite using the new screen dimmer feat. |
| 290 | + if(cmd_str.startsWith("lcd " ) || cmd_str.startsWith("tft" ) ) { |
| 291 | + String new_status = cmd_str.substring(strlen("lcd "), cmd_str.length()); |
| 292 | + if(new_status=="off") { |
| 293 | + analogWrite(BACKLIGHT, 0); |
| 294 | + esp_timer_stop(screensaver_timer); |
| 295 | + } else if(new_status=="on") { |
| 296 | + getBrightness(); // reinit brightness |
| 297 | + reset_screensaver_timer(); |
| 298 | + } |
| 299 | + return; |
| 300 | + } |
| 301 | +*/ |
| 302 | + |
| 303 | + if(cmd_str == "clock" ) { |
| 304 | + //esp_timer_stop(screensaver_timer); // disable screensaver while the clock is running |
| 305 | + runClockLoop(); |
| 306 | + return; |
| 307 | + } |
| 308 | + |
| 309 | + Serial.println("unsupported serial command: " + cmd_str); |
| 310 | + |
| 311 | + |
| 312 | +} |
| 313 | + |
0 commit comments