Skip to content

Commit 8acf3da

Browse files
authored
Merge pull request #84 from eadmaster/serialcmds
added serialcmds module (#64)
2 parents b84f61f + 4744c31 commit 8acf3da

File tree

4 files changed

+324
-1
lines changed

4 files changed

+324
-1
lines changed

platformio.ini

+6-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ build_flags =
2222
-DLH=8
2323
-DLW=6
2424
-DCONFIG_FILE='"/config.conf"'
25+
2526
lib_deps =
2627
WireGuard-ESP32
2728
IRremoteESP8266
@@ -35,7 +36,9 @@ lib_deps =
3536
bblanchon/ArduinoJson
3637
rc-switch
3738
;https://github.com/Martin-Laclaustra/rc-switch.git#transmittimingsarray
38-
39+
ESP8266Audio
40+
FFat
41+
ESP8266SAM
3942

4043
[env:m5stack-cplus2]
4144
platform = espressif32
@@ -226,6 +229,8 @@ build_flags =
226229

227230
-DGROVE_SDA=2
228231
-DGROVE_SCL=1
232+
233+
-DARDUINO_USB_CDC_ON_BOOT=1
229234
lib_deps =
230235
${common.lib_deps}
231236
xylopyrographer/LiteLED@^1.2.0

src/core/serialcmds.cpp

+313
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
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+

src/core/serialcmds.h

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
3+
void handleSerialCommands();

src/main.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ TFT_eSprite draw = TFT_eSprite(&tft);
5656
#include "core/sd_functions.h"
5757
#include "core/settings.h"
5858
#include "core/main_menu.h"
59+
#include "core/serialcmds.h"
5960

6061

6162
/*********************************************************************
@@ -253,6 +254,7 @@ void loop() {
253254
delay(200);
254255
}
255256

257+
handleSerialCommands();
256258
checkShortcutPress(); // shortctus to quickly start apps without navigating the menus
257259

258260
if (checkPrevPress()) {

0 commit comments

Comments
 (0)