From 01f4a54ce9cc7a1045adc187cbc28c9042018882 Mon Sep 17 00:00:00 2001 From: eadmaster <925171+eadmaster@users.noreply.github.com> Date: Mon, 5 Aug 2024 02:12:59 +0200 Subject: [PATCH 1/2] added headless mode, numbered keyboard shortcuts, added boot sound (#107) (#113) --- platformio.ini | 56 ++ src/core/VectorDisplay.h | 1329 +++++++++++++++++++++++++++ src/core/display.cpp | 18 +- src/core/globals.cpp | 6 +- src/core/globals.h | 16 +- src/core/mykeyboard.cpp | 32 +- src/core/mykeyboard.h | 3 + src/core/sd_functions.cpp | 4 + src/core/serialcmds.cpp | 274 +++--- src/core/serialcmds.h | 8 +- src/core/settings.cpp | 12 +- src/main.cpp | 61 +- src/modules/ir/TV-B-Gone.cpp | 372 ++++---- src/modules/ir/TV-B-Gone.h | 1 + src/modules/others/audio.cpp | 114 +++ src/modules/others/audio.h | 10 + src/modules/others/bad_usb.cpp | 6 +- src/modules/others/bad_usb.h | 4 +- src/modules/others/qrcode_menu.cpp | 2 + src/modules/others/webInterface.cpp | 41 +- src/modules/others/webInterface.h | 86 +- src/modules/rf/rf.cpp | 32 +- src/modules/rf/rf.h | 3 +- 23 files changed, 2155 insertions(+), 335 deletions(-) create mode 100755 src/core/VectorDisplay.h create mode 100644 src/modules/others/audio.cpp create mode 100644 src/modules/others/audio.h diff --git a/platformio.ini b/platformio.ini index 4eae5068..9957bd1d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -70,6 +70,9 @@ build_flags = ;Have RTC Chip -DHAS_RTC=1 + + ;Have buzzer + -DBUZZ_PIN=2 ;Buttons configuration -DHAS_BTN=1 @@ -623,6 +626,59 @@ lib_deps = ${common.lib_deps} +[env:esp32-s3-devkitc-1] +platform = espressif32 +board = esp32-s3-devkitc-1 +framework = arduino +;board_build.partitions = custom_16Mb.csv +;board_upload.flash_size = 16MB +build_flags = + ${common.build_flags} + -DESP32S3DEVKITC1 + -DUSB_as_HID=1 + ; needed for serial + -DARDUINO_USB_CDC_ON_BOOT=1 + ; ir led pin + -DLED=40 + -DLED_ON=HIGH + -DLED_OFF=LOW + ; sd card pins + -DSDCARD_CS=-1 + -DSDCARD_SCK=-1 + -DSDCARD_MISO=-1 + -DSDCARD_MOSI=-1 + ; grove pins (SDA=default TX pin, SCL=default RX pin) + -DGROVE_SDA=35 + -DGROVE_SCL=36 + ; tft vars + -DROTATION=1 + -DBACKLIGHT=15 ; tft backlight pin + -DWIDTH=240 + -DHEIGHT=135 + -DMINBRIGHT=160 ; unused? + -DMAX_MENU_SIZE=5 + -DSMOOTH_FONT=1 + -DTFT_DISPON=0x29 + -DTFT_DISPOFF=0x28 + ; text sizes + -DFP=1 + -DFM=2 + -DFG=3 + ; ui control buttons + -DSEL_BTN=1 + -DUP_BTN=2 ; also work as ESC + -DDW_BTN=3 ; also work as NEXT + -DTOUCH_THRESHOLD=20 + -DBTN_ALIAS='"OK"' + ;Microphone + ;-DMIC_SPM1423=1 ; uncomment to enable Applicable for SPM1423 device + -DPIN_CLK=43 + -DI2S_SCLK_PIN=43 + -DI2S_DATA_PIN=46 + -DPIN_DATA=46 +lib_deps = + ${common.lib_deps} + #New device model [env:NewDeviceModel] platform = espressif32 diff --git a/src/core/VectorDisplay.h b/src/core/VectorDisplay.h new file mode 100755 index 00000000..fcd0d8b4 --- /dev/null +++ b/src/core/VectorDisplay.h @@ -0,0 +1,1329 @@ +#ifndef _VECTOR_DISPLAY_H +#define _VECTOR_DISPLAY_H + +#ifndef ARDUINO +#define NO_SERIAL +#include +#define pgm_read_byte_near(a) (*(uint8_t*)(a)) + +#include +#include + +typedef std::string String; + +uint32_t millis() { + struct timeb t; + ftime(&t); + return t.millitm + t.time * 1000; +} + +class Print { +public: + virtual size_t write(uint8_t c) = 0; + virtual size_t write(const uint8_t* s, size_t length) { + size_t wrote = 0; + while (length > 0) { + size_t b = write(*s++); + if (b <= 0) + break; + b++; + length--; + } + return wrote; + } + + virtual size_t write(const char* s) { + return write((uint8_t*)s, strlen(s)); + } +}; + +class Stream : public Print { +public: + virtual int read() = 0; + virtual int available() = 0; +}; + +#else +#include +#endif + +#ifdef ESP8266 +# include +#endif + +#define VECTOR_DISPLAY_MESSAGE_SIZE 8 +#define VECTOR_DISPLAY_MAX_STRING 256 + +#define VECTOR_DISPLAY_DEFAULT_WIDTH 240 +#define VECTOR_DISPLAY_DEFAULT_HEIGHT 320 + +#define ALIGN_LEFT 'l' +#define ALIGN_RIGHT 'r' +#define ALIGN_CENTER 'c' +#define ALIGN_TOP 't' +#define ALIGN_BOTTOM 'b' +#define ALIGN_BASELINE 'l' + +#ifndef VECTOR_DISPLAY_SEND_DELAY +#define VECTOR_DISPLAY_SEND_DELAY 0 +#endif + +#define TFT_BLACK 0x0000 /* 0, 0, 0 */ +#define TFT_NAVY 0x000F /* 0, 0, 128 */ +#define TFT_DARKGREEN 0x03E0 /* 0, 128, 0 */ +#define TFT_DARKCYAN 0x03EF /* 0, 128, 128 */ +#define TFT_MAROON 0x7800 /* 128, 0, 0 */ +#define TFT_PURPLE 0x780F /* 128, 0, 128 */ +#define TFT_OLIVE 0x7BE0 /* 128, 128, 0 */ +#define TFT_LIGHTGREY 0xC618 /* 192, 192, 192 */ +#define TFT_DARKGREY 0x7BEF /* 128, 128, 128 */ +#define TFT_BLUE 0x001F /* 0, 0, 255 */ +#define TFT_GREEN 0x07E0 /* 0, 255, 0 */ +#define TFT_CYAN 0x07FF /* 0, 255, 255 */ +#define TFT_RED 0xF800 /* 255, 0, 0 */ +#define TFT_MAGENTA 0xF81F /* 255, 0, 255 */ +#define TFT_YELLOW 0xFFE0 /* 255, 255, 0 */ +#define TFT_WHITE 0xFFFF /* 255, 255, 255 */ +#define TFT_ORANGE 0xFD20 /* 255, 165, 0 */ +#define TFT_GREENYELLOW 0xAFE5 /* 173, 255, 47 */ +#define TFT_PINK 0xF81F + +// Color definitions for backwards compatibility +#define ILI9341_BLACK 0x0000 /* 0, 0, 0 */ +#define ILI9341_NAVY 0x000F /* 0, 0, 128 */ +#define ILI9341_DARKGREEN 0x03E0 /* 0, 128, 0 */ +#define ILI9341_DARKCYAN 0x03EF /* 0, 128, 128 */ +#define ILI9341_MAROON 0x7800 /* 128, 0, 0 */ +#define ILI9341_PURPLE 0x780F /* 128, 0, 128 */ +#define ILI9341_OLIVE 0x7BE0 /* 128, 128, 0 */ +#define ILI9341_LIGHTGREY 0xC618 /* 192, 192, 192 */ +#define ILI9341_DARKGREY 0x7BEF /* 128, 128, 128 */ +#define ILI9341_BLUE 0x001F /* 0, 0, 255 */ +#define ILI9341_GREEN 0x07E0 /* 0, 255, 0 */ +#define ILI9341_CYAN 0x07FF /* 0, 255, 255 */ +#define ILI9341_RED 0xF800 /* 255, 0, 0 */ +#define ILI9341_MAGENTA 0xF81F /* 255, 0, 255 */ +#define ILI9341_YELLOW 0xFFE0 /* 255, 255, 0 */ +#define ILI9341_WHITE 0xFFFF /* 255, 255, 255 */ +#define ILI9341_ORANGE 0xFD20 /* 255, 165, 0 */ +#define ILI9341_GREENYELLOW 0xAFE5 /* 173, 255, 47 */ +#define ILI9341_PINK 0xF81F + +#define MESSAGE_DOWN 'D' +#define MESSAGE_UP 'U' +#define MESSAGE_MOVE 'M' +#define MESSAGE_BUTTON 'B' +#define MESSAGE_ACK 'A' + +typedef uint32_t FixedPoint32; +#define TO_FP32(f) ((uint32_t)((f)*65536. + 0.5)) + +struct VectorDisplayMessage { + char what; + char what2; + union { + uint8_t button; + struct { + int16_t x; + int16_t y; + } xy; + } data; +} __attribute__((packed)); + +class VectorDisplayClass : public Print { +private: + static const uint32_t MAX_BUFFER = (uint32_t)1024*256; + static const uint32_t MESSAGE_TIMEOUT = 3000; + static const uint8_t FLAG_LOW_ENDIAN_BITS = 1; + static const uint8_t FLAG_HAVE_MASK = 2; + static const uint8_t FLAG_PAD_BYTE = 4; + static const uint8_t FLAG_LOW_ENDIAN_BYTES = 8; + + bool waitForAck = true; + int gfxFontSize = 1; + int curx = 0; + int cury = 0; + int readPos = 0; + int32_t curForeColor565 = -1; + uint32_t lastMessageStart = 0; + int pointerX; + int pointerY; + int curWidth = VECTOR_DISPLAY_DEFAULT_WIDTH; + int curHeight = VECTOR_DISPLAY_DEFAULT_HEIGHT; + uint8_t curRotation = 0; + bool pointerDown = false; + bool wrap = 1; + bool fixCP437 = true; + uint16_t polyLineCount; + uint8_t polyLineSum; + uint32_t delayTime = 0; + + uint8_t readBuf[VECTOR_DISPLAY_MESSAGE_SIZE]; + union { + uint32_t color; + uint16_t twoByte[9]; + struct { + uint16_t x; + uint16_t y; + char text[VECTOR_DISPLAY_MAX_STRING+1]; + } __attribute__((packed)) xyText; + struct { + uint16_t endianness; + uint16_t width; + uint16_t height; + FixedPoint32 aspectRatio; + uint16_t reserved[3]; + } __attribute__((packed)) initialize; + struct { + uint8_t c; + char text[VECTOR_DISPLAY_MAX_STRING+1]; + } __attribute__((packed)) charText; + struct { + uint16_t width; + uint16_t height; + } __attribute__((packed)) coords; + struct { + char attr; + uint8_t value; + } __attribute__((packed)) attribute8; + struct { + char attr; + uint16_t value; + } __attribute__((packed)) attribute16; + struct { + char attr; + uint32_t value; + } __attribute__((packed)) attribute32; + struct { + uint32_t length; + uint8_t depth; + uint8_t flags; + uint16_t x; + uint16_t y; + uint16_t w; + uint16_t h; + uint32_t foreColor; // only if depth==1 + uint32_t backColor; // only if depth==1 + } __attribute__((packed)) bitmap; + struct { + uint16_t x1; + uint16_t y1; + uint16_t x2; + uint16_t y2; + uint16_t r; + uint8_t filled; + } __attribute__((packed)) roundedRectangle; + struct { + uint16_t x; + uint16_t y; + uint16_t r; + FixedPoint32 angle1; + FixedPoint32 sweep; + uint8_t filled; + } __attribute__((packed)) arc; + struct { + char attr; + uint16_t values[2]; + } __attribute__((packed)) attribute16x2; + uint8_t bytes[VECTOR_DISPLAY_MAX_STRING+1]; + char text[VECTOR_DISPLAY_MAX_STRING+1]; + } args; + uint32_t lastSend = 0; + +private: + inline void sendDelay() { + if (delayTime>0) { + while(millis()-lastSend < delayTime) ; + lastSend = millis(); + } + } + +public: + int textsize = 1; + uint32_t textcolor = TFT_WHITE; + uint32_t textbgcolor = TFT_BLACK; + + void setWaitForAck(bool wait) { + waitForAck = wait; + } + + void setDelay(uint32_t delayMillis) { + delayTime = delayMillis; + lastSend = millis(); + } + + virtual void remoteFlush() { + /*while(remoteAvailable()) + remoteRead(); + * */ + } + virtual int remoteRead() = 0; // must be non-blocking + virtual void remoteWrite(uint8_t c) = 0; + virtual void remoteWrite(const void* data, size_t n) = 0; + virtual size_t remoteAvailable() = 0; + + void attribute8(char a, uint8_t value) { + args.attribute8.attr = a; + args.attribute8.value = value; + sendCommand('Y', &args, 2); + } + + void attribute8(char a, bool value) { + args.attribute8.attr = a; + args.attribute8.value = value ? 1 : 0; + sendCommand('Y', &args, 2); + } + + void attribute16(char a, uint16_t value) { + args.attribute16.attr = a; + args.attribute16.value = value; + sendCommand('A', &args, 3); + } + + void attribute32(char a, uint32_t value) { + args.attribute32.attr = a; + args.attribute32.value = value; + sendCommand('B', &args, 5); + } + + void sendCommand(char c, const void* arguments, int argumentsLength) { + sendDelay(); + remoteWrite(c); + remoteWrite(c^0xFF); + if (argumentsLength > 0) + remoteWrite((uint8_t*)arguments, argumentsLength); + uint8_t sum = 0; + for (int i = 0; i 0) + s += *p++; + return s; + } + + void startPoly(char c, uint16_t n) { + polyLineCount = n; + remoteWrite(c); + remoteWrite(c^0xFF); + args.twoByte[0] = n; + remoteWrite((uint8_t*)&args, 2); + polyLineSum = args.bytes[0] + args.bytes[1]; + } + + void startFillPoly(uint16_t n) { + startPoly('N', n); + } + + void startPolyLine(uint16_t n) { + startPoly('O', n); + } + + void addPolyLine(int16_t x, int16_t y) { + if (polyLineCount>0) { + args.twoByte[0] = x; + args.twoByte[1] = y; + remoteWrite((uint8_t*)&args, 4); + polyLineSum += args.bytes[0] + args.bytes[1] + args.bytes[2] + args.bytes[3]; + polyLineCount--; + if (polyLineCount == 0) { + remoteWrite(0xFF^polyLineSum); + } + } + } + + void line(int x1, int y1, int x2, int y2) { + args.twoByte[0] = x1; + args.twoByte[1] = y1; + args.twoByte[2] = x2; + args.twoByte[3] = y2; + sendCommand('L', &args, 8); + } + + void fillRectangle(int x1, int y1, int x2, int y2) { + args.twoByte[0] = x1; + args.twoByte[1] = y1; + args.twoByte[2] = x2; + args.twoByte[3] = y2; + sendCommand('R', &args, 8); + } + + void rectangle(int x1, int y1, int x2, int y2, bool fill=false) { + if (fill) + fillRectangle(x1,y1,x2,y2); + else { + startPolyLine(4); + addPolyLine(x1,y1); + addPolyLine(x2,y1); + addPolyLine(x2,y2); + addPolyLine(x1,y2); + } + } + + void roundedRectangle(int x1, int y1, int x2, int y2, int r, bool fill) { + args.roundedRectangle.filled = fill ? 1 : 0; + args.roundedRectangle.x1 = x1; + args.roundedRectangle.x2 = x2; + args.roundedRectangle.y1 = y1; + args.roundedRectangle.y2 = y2; + args.roundedRectangle.r = r; + sendCommand('Q', &args, 11); + } + + void roundedRectangle(int x1, int y1, int x2, int y2, int r) { + roundedRectangle(x1,y1,x2,y2,r,false); + } + + void fillRoundedRectangle(int x1, int y1, int x2, int y2, int r) { + roundedRectangle(x1,y1,x2,y2,r,true); + } + + void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { + args.twoByte[0] = x1; + args.twoByte[1] = y1; + args.twoByte[2] = x2; + args.twoByte[3] = y2; + args.twoByte[4] = x3; + args.twoByte[5] = y3; + sendCommand('G', &args, 12); + } + +/* void initialize() { + args.twoByte[0] = 0x1234; // endianness detector + args.twoByte[1] = 0; + sendCommandWithAck('H', &args, 4); + } */ + + void initialize(int w=VECTOR_DISPLAY_DEFAULT_WIDTH, int h=VECTOR_DISPLAY_DEFAULT_HEIGHT) { + args.initialize.endianness = 0x1234; // endianness detector + args.initialize.width = w; + args.initialize.height = h; + args.initialize.aspectRatio = TO_FP32(1.); + args.initialize.reserved[0] = 0; + args.initialize.reserved[1] = 0; + args.initialize.reserved[2] = 0; + curWidth = w; + curHeight = h; + + sendCommandWithAck('Z', &args, 16); + } + + void fillCircle(int x, int y, int r) { + args.twoByte[0] = x; + args.twoByte[1] = y; + args.twoByte[2] = r; + sendCommand('J', &args, 6); + } + + void circle(int x, int y, int r) { + args.twoByte[0] = x; + args.twoByte[1] = y; + args.twoByte[2] = r; + sendCommand('I', &args, 6); + } + + void point(int x, int y) { + args.twoByte[0] = x; + args.twoByte[1] = y; + sendCommand('P', &args, 4); + } + + void arc(int x, int y, int r, FixedPoint32 angle1, FixedPoint32 sweep, bool fill=false) { + args.arc.x = x; + args.arc.y = y; + args.arc.r = r; + args.arc.angle1 = angle1; + args.arc.sweep = sweep; + args.arc.filled = fill ? 1 : 0; + sendCommand('S', &args, 15); + } + + void arc(int x, int y, int r, float angle1, float sweep, bool fill=false) { + arc(x,y,r,TO_FP32(angle1),TO_FP32(sweep),fill); + } + + // 32-bit fixed point + void textSize(FixedPoint32 s) { + args.attribute32.attr = 's'; + args.attribute32.value = s; + sendCommand('B', &args, 5); + } + + void text(int x, int y, const char* str, int n) { + args.xyText.x = x; + args.xyText.y = y; + if (n>VECTOR_DISPLAY_MAX_STRING) + n = VECTOR_DISPLAY_MAX_STRING; + strncpy(args.xyText.text, str, n); + + if (fixCP437) { + for (int i=0;i=176) + args.xyText.text[i]++; + } + } + args.xyText.text[n] = 0; + sendCommand('T', &args, 4+strlen(args.xyText.text)+1); + } + + void text(int x, int y, const char* str) { + text(x, y, str, strlen(str)); + } + + void text(int x, int y, String str) { + text(x,y,str.c_str(), str.length()); + } + + void deleteButton(uint8_t command) { + sendCommand('D', &command, 1); + } + + void addButton(uint8_t command, const char* str) { + args.charText.c = command; + strncpy(args.charText.text, str, VECTOR_DISPLAY_MAX_STRING); + args.charText.text[VECTOR_DISPLAY_MAX_STRING] = 0; + sendCommand('U', &args, 1+strlen(args.charText.text)+1); + } + + void addButton(uint8_t command, String str) { + addButton(command, str.c_str()); + } + + void toast(const char* str, unsigned n) { + if (VECTOR_DISPLAY_MAX_STRING < n) + n = VECTOR_DISPLAY_MAX_STRING; + strncpy(args.text, str, n); + args.text[n] = 0; + sendCommand('M', &args, n+1); + } + + void toast(const char* str) { + toast(str, strlen(str)); + } + + void toast(String text) { + toast(text.c_str(), text.length()); + } + + void foreColor(uint32_t color) { + args.attribute32.attr = 'f'; + args.attribute32.value = color; + sendCommand('B', &args, 5); + curForeColor565 = -1; + } + + void backColor(uint32_t color) { + args.attribute32.attr = 'b'; + args.attribute32.value = color; + sendCommand('B', &args, 5); + } + + void textBackColor(uint32_t color) { + args.attribute32.attr = 'k'; + args.attribute32.value = color; + sendCommand('B', &args, 5); + } + + void textForeColor(uint32_t color) { + args.attribute32.attr = 'F'; + args.attribute32.value = color; + sendCommand('B', &args, 5); + } + + void foreColor565(uint16_t color) { + args.attribute16.attr = 'f'; + args.attribute16.value = color; + sendCommand('A', &args, 3); + curForeColor565 = color; + } + + void backColor565(uint16_t color) { + args.attribute16.attr = 'b'; + args.attribute16.value = color; + sendCommand('A', &args, 3); + } + + void textBackColor565(uint16_t color) { + args.attribute16.attr = 'k'; + args.attribute16.value = color; + sendCommand('A', &args, 3); + } + + void textForeColor565(uint16_t color) { + args.attribute16.attr = 'F'; + args.attribute16.value = color; + sendCommand('A', &args, 3); + } + + void rounded(uint8_t value) { + args.attribute8.attr = 'n'; + args.attribute8.value = value ? 1 : 0; + sendCommand('Y', &args, 2); + } + + void thickness(FixedPoint32 t) { + args.attribute32.attr = 't'; + args.attribute32.value = t; + sendCommand('B', &args, 5); + } + + void pixelAspectRatio(FixedPoint32 a) { + args.attribute32.attr = 'a'; + args.attribute32.value = a; + sendCommand('B', &args, 5); + } + +#ifdef SUPPORT_FLOATING_POINT + inline void setThickness(double thickness) { + setThickness(TO_FP32(thickness)); + } + + inline void setPixelAspectRatio(double aspect) { + setThickness(TO_FP32(aspect)); + } +#endif + + void clear() { + sendCommand('C', NULL, 0); + } + + void update() { + sendCommand('F', NULL, 0); + } + +/* void reset() { + sendCommandWithAck('E', NULL, 0); + } */ + + void coordinates(int width, int height) { + args.attribute16x2.attr = 'c'; + curWidth = width; + curHeight = height; + args.attribute16x2.values[0] = width; + args.attribute16x2.values[1] = height; + sendCommandWithAck('B', &args, 5); + } + + void continuousUpdate(bool value) { + args.attribute8.attr = 'c'; + args.attribute8.value = value ? 1 : 0; + sendCommand('Y', &args, 2); + } + + void textHorizontalAlign(char hAlign) { + args.attribute8.attr = 'h'; + args.attribute8.value = hAlign; + sendCommand('Y', &args, 2); + } + + void textVerticalAlign(char hAlign) { + args.attribute8.attr = 'v'; + args.attribute8.value = hAlign; + sendCommand('Y', &args, 2); + } + + void textOpaqueBackground(bool opaque) { + args.attribute8.attr = 'o'; + args.attribute8.value = opaque ? 1 : 0; + sendCommand('Y', &args, 2); + } + + void textBold(bool bold) { + args.attribute8.attr = 'b'; + args.attribute8.value = bold ? 1 : 0; + sendCommand('Y', &args, 2); + } + + bool isTouchDown() { + return pointerDown; + } + + int getTouchX() { + return pointerX; + } + + int getTouchY() { + return pointerY; + } + + bool readMessage(VectorDisplayMessage* msg) { + while (remoteAvailable()) { + uint8_t c = remoteRead(); + + if (0 < readPos && millis()-lastMessageStart > MESSAGE_TIMEOUT) + readPos = 0; + + if (2 <= readPos) { + readBuf[readPos++] = c; + if (readPos >= VECTOR_DISPLAY_MESSAGE_SIZE) { + readPos = 0; + if (msg != NULL) + memcpy(msg, readBuf, sizeof(VectorDisplayMessage)); + else + msg = (VectorDisplayMessage*)readBuf; + + if (msg->what == MESSAGE_DOWN || msg->what == MESSAGE_UP || msg->what == MESSAGE_MOVE) { + pointerDown = msg->what != MESSAGE_UP; + pointerX = msg->data.xy.x; + pointerY = msg->data.xy.y; + } + return true; + } + continue; + } + + if (1 <= readPos) { + if ( (*readBuf == 'U' && c == 'P') || + (*readBuf == 'D' && c == 'N') || + (*readBuf == 'M' && c == 'V') || + (*readBuf == 'B' && c == 'T') || + (*readBuf == 'A' && c == 'c') + ) { + readBuf[readPos++] = c; + continue; + } + readPos = 0; + } + if (readPos == 0 && (c == 'U' || c == 'D' || c == 'M' || c == 'B' || c == 'A')) { + readBuf[readPos++] = c; + lastMessageStart = millis(); + } + } + return false; + } + + uint32_t color565To8888(uint16_t c) { + return 0xFF000000 | ((((c>>11) & 0x1F) * 255 / 0x1F) << 16) | ((((c>>5) & 0x3F) * 255 / 0x3F) << 8) | ((c & 0x1F) * 255 / 0x1F); + } + + uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + + uint32_t getBitmap1Size(int16_t w, int16_t h, uint8_t flags=0) { + return (flags & FLAG_PAD_BYTE) ? ((uint32_t)w+7)/8*h : ((uint32_t)w*h+7)/8; + } + + uint32_t getBitmapSize(int16_t w, int16_t h, uint8_t depth=1, uint8_t flags=0) { + if (depth==1) { + return getBitmap1Size(w,h,flags); + } + else { + return w*h*(depth/8); + } + } + + /*TODO: stubs*/ + void* createSprite(int16_t width, int16_t height, uint8_t frames = 1) { return NULL; } + void pushSprite(int32_t x, int32_t y) {} + void deleteSprite(void) {} + void fillSprite(uint32_t color) {} + + void bitmap_progmem(int16_t x, int16_t y, const uint8_t* bmp, + int16_t w, int16_t h, uint8_t depth=1, uint8_t flags=0, const uint8_t* mask=NULL, + uint32_t foreColor=0xFFFFFFFF, + uint32_t backColor=0x00FFFFFF) /* PROGMEM */ { + if (mask != NULL) + flags |= FLAG_HAVE_MASK; + uint32_t bitmapSize = getBitmapSize(w,h,depth,flags); + int headerSize = depth==1 ? 22 : 14; + uint32_t maskSize = mask == NULL ? 0 : getBitmap1Size(w,h,flags); + uint32_t fullSize = bitmapSize + headerSize + maskSize; + + if (fullSize + 1 > MAX_BUFFER) + return; + + sendDelay(); + remoteWrite('K'); + remoteWrite('K'^0xFF); + args.bitmap.length = fullSize; + args.bitmap.depth = 1; + args.bitmap.flags = flags; + args.bitmap.x = x; + args.bitmap.y = y; + args.bitmap.w = w; + args.bitmap.h = h; + if (depth == 1) { + args.bitmap.foreColor = foreColor; + args.bitmap.backColor = backColor; + } + + uint8_t sum = sumBytes(&args, headerSize); + remoteWrite(&args,headerSize); + for (uint32_t i=0; i MAX_BUFFER) + return; + + sendDelay(); + remoteWrite('K'); + remoteWrite('K'^0xFF); + args.bitmap.length = fullSize; + args.bitmap.depth = depth; + args.bitmap.flags = flags; + args.bitmap.x = x; + args.bitmap.y = y; + args.bitmap.w = w; + args.bitmap.h = h; + if (depth == 1) { + args.bitmap.foreColor = foreColor; + args.bitmap.backColor = backColor; + } + remoteWrite(&args,headerSize); + remoteWrite(bmp,bitmapSize); + uint8_t sum = sumBytes(&args, headerSize) + sumBytes((void*)bmp, bitmapSize); + if (maskSize > 0) { + remoteWrite(mask,maskSize); + sum += sumBytes((void*)mask, maskSize); + } + remoteWrite(sum^0xFF); + } + + void utf8() { + fixCP437 = false; + args.attribute8.attr = 'i'; + args.attribute8.value = 0; + sendCommand('Y', &args, 2); + } + + /* The following are meant to be compatible with Adafruit GFX */ + void cp437(bool s) { + // if true, activates real cp437 mode; if false, activates buggy Arduino compatible cp437 mode + fixCP437 = !s; + args.attribute8.attr = 'i'; + args.attribute8.value = 1; + sendCommand('Y', &args, 2); + } + + void setRotation(uint8_t r) { + args.attribute8.attr = 'r'; + args.attribute8.value = r; + curRotation = r & 3; + sendCommand('Y', &args, 2); + } + + void setTextSize(uint8_t size) { + gfxFontSize = size; + textsize = size; + textSize((FixedPoint32)size * 8 * 65536); + } + + void setTextColor(uint16_t f, uint16_t b) { + textBackColor565(b); + textForeColor565(f); + textcolor = f; + textbgcolor = b; + textOpaqueBackground(true); + } + + void setTextColor(uint16_t f) { + textForeColor565(f); + textcolor = f; + textOpaqueBackground(false); + } + + void setCursor(int16_t x, int16_t y) { + curx = x; + cury = y; + } + + + int16_t getCursorX(void) { + return curx; + } + + int16_t getCursorY(void) { + return cury; + } + + void setTextWrap(bool w) { + wrap = w; + } + + int16_t drawRightString(const char *string, int32_t x, int32_t y, uint8_t font) { + // TODO: add spaces + return drawString(string, x, y); + } + + int16_t drawRightString(const String& string, int32_t x, int32_t y, uint8_t font) { + return drawRightString(string.c_str(), x, y, font); + } + + int16_t drawCentreString(const char *string, int32_t x, int32_t y, uint8_t font) { + // TODO: add spaces + return drawString(string, x, y); + } + + + int16_t drawCentreString(const String& string, int32_t x, int32_t y, uint8_t font) { + return drawCentreString(string.c_str(), x, y, font); + } + + int16_t drawString(const String& string, int32_t x, int32_t y) { + return drawString(string.c_str(), x, y); + } + + int16_t drawString(const char *string, int32_t x, int32_t y) { + setCursor(x, y); + return write(string); + }; + + int16_t drawChar(uint16_t uniCode, int32_t x, int32_t y) { + setCursor(x, y); + return write(uniCode); + } + + // TODO: fix back color handling + size_t write(uint8_t c) override { + if (wrap && curx + 5*gfxFontSize>width()) { + curx = 0; + cury += 8*gfxFontSize; + } + text(curx, cury, (char*)&c, 1); + curx += 5*gfxFontSize; + return 0; + } + + // TODO: fix back color handling + size_t write(const char* s) /*override*/ { //ESP8266 core doesn't supply write(const char*) + int l = strlen(s); + int w = width(); + if (!wrap || curx + 5*gfxFontSize*l <= w) { + text(curx, cury, s); + curx += 5*gfxFontSize*l; + } + else { + while(l>0) { + int end = ((int)w-curx)/(5*gfxFontSize); + if (end <= 0) { + curx = 0; + cury += 8*gfxFontSize; + end = w/(5*gfxFontSize); + } + if (end > l) + end = l; + text(curx, cury, s, end); + l-=end; + s += end; + curx = 5*gfxFontSize*end; + } + } + return 0; + } + + void drawPixel(int16_t x, int16_t y, uint16_t color) { + if (color != curForeColor565) { + foreColor565(color); + } + point(x, y); + } + + void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + if (color != curForeColor565) { + foreColor565(color); + } + rectangle(x,y,x+w-1,y+h-1); + } + + void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + if (color != curForeColor565) { + foreColor565(color); + } + fillRectangle(x,y,x+w-1,y+h-1); + } + + void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { + if (color != curForeColor565) { + foreColor565(color); + } + line(x,y,x+w,y); + } + + void drawLine(int16_t x, int16_t y, int16_t x2, int16_t y2, uint16_t color) { + if (color != curForeColor565) { + foreColor565(color); + } + line(x,y,x2,y2); + } + + + // Draw an anti-aliased wide line from ax,ay to bx,by width wd with radiused ends (radius is wd/2) + // If bg_color is not included the background pixel colour will be read from TFT or sprite + void drawWideLine(float ax, float ay, float bx, float by, float wd, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF) { + drawRect(ax, ay, wd, abs(ay-by), fg_color); + } + + // As per "drawSmoothArc" except the ends of the arc are NOT anti-aliased, this facilitates dynamic arc length changes with + // arc segments and ensures clean segment joints. + // The sides of the arc are anti-aliased by default. If smoothArc is false sides will NOT be anti-aliased + void drawArc(int32_t x, int32_t y, int32_t r, int32_t ir, uint32_t startAngle, uint32_t endAngle, uint32_t fg_color, uint32_t bg_color, bool smoothArc = true){ + // TODO + drawRect(x, y, r*2, ir*2, fg_color); + } + + void drawSmoothArc(int32_t x, int32_t y, int32_t r, int32_t ir, uint32_t startAngle, uint32_t endAngle, uint32_t fg_color, uint32_t bg_color, bool roundEnds = false) { + drawArc(x, y, r, ir, startAngle, endAngle, fg_color, bg_color); + } + + // Draw an anti-aliased filled circle at x, y with radius r + // If bg_color is not included the background pixel colour will be read from TFT or sprite + void fillSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t color, uint32_t bg_color = 0x00FFFFFF) { + fillCircle(x, y, r, color) ; + } + + void drawSmoothRoundRect(int32_t x, int32_t y, int32_t r, int32_t ir, int32_t w, int32_t h, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF, uint8_t quadrants = 0xF) { + drawRoundRect(x, y, w, h, r, fg_color); + } + + // Draw a filled rounded rectangle , corner radius r and bounding box defined by x,y and w,h + void fillSmoothRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint32_t color, uint32_t bg_color = 0x00FFFFFF) { + fillRoundRect(x, y, w, h, radius, color); + } + + + void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { + if (color != curForeColor565) { + foreColor565(color); + } + line(x,y,x,y+h); + } + + void fillScreen(uint16_t color) { + backColor565(color); + clear(); + backColor(0xFF000000); + } + + void drawCircle(int16_t x, int16_t y, int16_t r, uint16_t color) { + if (color != curForeColor565) { + foreColor565(color); + } + circle(x,y,r); + } + + void fillCircle(int16_t x, int16_t y, int16_t r, uint16_t color) { + if (color != curForeColor565) { + foreColor565(color); + } + fillCircle(x,y,r); + } + + void drawEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color) { + } + void fillEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color) { + // TODO + if(rx0) { + rectangle(cx-r,cy,cx,cy+delta,false); + rectangle(cx-r,cy,cx,cy+delta,true); + } + } + if (corners & 1) { + arc(cx,cy,r,TO_FP32(270),TO_FP32(180),false); // drawing edges separately makes things fit better + arc(cx,cy,r,TO_FP32(270),TO_FP32(180),true); + arc(cx,cy+delta,r,TO_FP32(270),TO_FP32(180),false); + arc(cx,cy+delta,r,TO_FP32(270),TO_FP32(180),true); + if (delta>0) { + rectangle(cx,cy,cx+r,cy+delta,false); + rectangle(cx,cy,cx+r,cy+delta,true); + } + } + } + + void pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data) {}; + void pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data, uint16_t transparent) {}; + + + /* the following Adafruit GFX APIs are not implemented at present */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, + uint16_t bg, uint8_t size) {} + void setFont(const void /*GFXfont*/ *f = NULL) {} + void getTextBounds(const char *string, int16_t x, int16_t y, + int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h) {} + void getTextBounds(const void /*__FlashStringHelper*/ *s, int16_t x, int16_t y, + int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h) {} +#pragma GCC diagnostic pop +}; + +class SerialDisplayClass : public VectorDisplayClass { + private: + Stream& s; + const bool doSerialBegin; + + public: + virtual int remoteRead() override { + //return s.read(); + return('A'); + } + + virtual void remoteWrite(uint8_t c) override { + //s.write(c); + } + + virtual void remoteWrite(const void* data, size_t n) override { + //s.write((uint8_t*)data, n); + } + + /* only works with the Serial object; do not call externally without it */ + void begin(uint32_t speed, int width=VECTOR_DISPLAY_DEFAULT_WIDTH, int height=VECTOR_DISPLAY_DEFAULT_HEIGHT) { +#ifndef NO_SERIAL + if (doSerialBegin) { + Serial.begin(speed); + while(!Serial) ; + } +#endif + VectorDisplayClass::begin(width, height); + } + + virtual void begin(int width=VECTOR_DISPLAY_DEFAULT_WIDTH, int height=VECTOR_DISPLAY_DEFAULT_HEIGHT) override { + begin(115200, width, height); + } + + virtual size_t remoteAvailable() override { + return s.available(); + } + +#ifndef NO_SERIAL + SerialDisplayClass() : s(Serial), doSerialBegin(true) {} +#endif + + SerialDisplayClass(Stream& _s) : s(_s), doSerialBegin(false) {} +}; + +#ifdef ESP8266 +class WiFiDisplayClass : public SerialDisplayClass { + private: + WiFiClient client; + public: + bool begin(const char* host, int width=VECTOR_DISPLAY_DEFAULT_WIDTH, int height=VECTOR_DISPLAY_DEFAULT_HEIGHT) { + VectorDisplayClass::begin(width, height); + return client.connect(host, 7788); + } + + virtual void end() override { + VectorDisplayClass::end(); + client.stop(); + } + + WiFiDisplayClass() : SerialDisplayClass(client) { + } +}; +#endif + +#endif diff --git a/src/core/display.cpp b/src/core/display.cpp index 822b24ee..55808bcd 100644 --- a/src/core/display.cpp +++ b/src/core/display.cpp @@ -2,6 +2,7 @@ #include "mykeyboard.h" #include "wg.h" //for isConnectedWireguard to print wireguard lock #include "settings.h" //for timeStr +#include "modules/others/webInterface.h" // for server #if defined(CARDPUTER) || defined(STICK_C_PLUS2) //Battery Calculation #include @@ -59,6 +60,9 @@ void initDisplay(int i) { ** Description: Display Red Stripe with information ***************************************************************************************/ void displayRedStripe(String text, uint16_t fgcolor, uint16_t bgcolor) { + // detect if not running in interactive mode -> show nothing onscreen and return immediately + if(server || isSleeping || isScreenOff) return; // webui is running + int size; if(fgcolor==bgcolor && fgcolor==TFT_WHITE) fgcolor=TFT_BLACK; if(text.length()*LW*FM<(WIDTH-2*FM*LW)) size = FM; @@ -222,7 +226,19 @@ void loopOptions(const std::vector> } #ifdef CARDPUTER - if(checkEscPress()) break; + if(checkEscPress()) break; + int pressed_number = checkNumberShortcutPress(); + if(pressed_number>=0) { + if(index == pressed_number) { + // press 2 times the same number to confirm + options[index].second(); + break; + } + // else only highlight the option + index = pressed_number; + if((index+1)>options.size()) index = options.size() - 1; + redraw = true; + } #endif } delay(200); diff --git a/src/core/globals.cpp b/src/core/globals.cpp index 1acb7b7b..f649d943 100644 --- a/src/core/globals.cpp +++ b/src/core/globals.cpp @@ -1,7 +1,5 @@ #include "globals.h" -#define BUZZ_PIN 2 - /********************************************************************* ** Function: backToMenu ** sets the global var to be be used in the options second parameter @@ -74,5 +72,9 @@ void updateTimeStr(struct tm timeInfo) { } void _tone(unsigned int frequency, unsigned long duration = 0UL) { +#if defined(BUZZ_PIN) tone(BUZZ_PIN, frequency, duration); +//#elif defined(HAS_NS4168_SPKR) +//TODO: alt. implementation using the speaker +#endif } diff --git a/src/core/globals.h b/src/core/globals.h index 8850c371..aded9606 100644 --- a/src/core/globals.h +++ b/src/core/globals.h @@ -6,7 +6,6 @@ extern char16_t FGCOLOR; #define BGCOLOR TFT_BLACK #include -#include #include #include //#include @@ -35,9 +34,17 @@ extern char16_t FGCOLOR; #include #endif // Declaração dos objetos TFT -extern TFT_eSPI tft; -extern TFT_eSprite sprite; -extern TFT_eSprite draw; +#if defined(HAS_SCREEN) + #include + extern TFT_eSPI tft; + extern TFT_eSprite sprite; + extern TFT_eSprite draw; +#else + #include "VectorDisplay.h" + extern SerialDisplayClass tft; + extern SerialDisplayClass& sprite; + extern SerialDisplayClass& draw; +#endif extern char timeStr[10]; @@ -104,4 +111,5 @@ extern String wui_usr; extern String wui_pwd; extern int tmz; +void setup_gpio(); void _tone(unsigned int frequency, unsigned long duration); diff --git a/src/core/mykeyboard.cpp b/src/core/mykeyboard.cpp index cca39842..f5b1ffdf 100644 --- a/src/core/mykeyboard.cpp +++ b/src/core/mykeyboard.cpp @@ -54,6 +54,9 @@ bool checkNextPress(){ #elif defined(M5STACK) M5.update(); if(M5.BtnC.isHolding() || M5.BtnC.isPressed()) + #elif ! defined(HAS_SCREEN) + // always return false + if(false) #else if(digitalRead(DW_BTN)==LOW) #endif @@ -78,6 +81,9 @@ bool checkPrevPress() { #elif defined(M5STACK) M5.update(); if(M5.BtnA.isHolding() || M5.BtnA.isPressed()) + #elif ! defined(HAS_SCREEN) + // always return false + if(false) #else if(digitalRead(UP_BTN)==LOW) #endif @@ -98,7 +104,9 @@ bool checkSelPress(){ #if defined (CARDPUTER) Keyboard.update(); if(Keyboard.isKeyPressed(KEY_ENTER) || digitalRead(0)==LOW) - //#elif defined(NEW_DEVICE) + #elif ! defined(HAS_SCREEN) + // always return false + if(false) #elif defined(M5STACK) M5.update(); if(M5.BtnB.isHolding() || M5.BtnB.isPressed()) @@ -124,6 +132,9 @@ bool checkEscPress(){ #elif defined (CARDPUTER) Keyboard.update(); if(Keyboard.isKeyPressed('`')) + #elif ! defined(HAS_SCREEN) + // always return false + if(false) #elif defined(M5STACK) M5.update(); if(M5.BtnA.isHolding() || M5.BtnA.isPressed()) @@ -157,19 +168,30 @@ bool checkAnyKeyPress() { } +#ifdef CARDPUTER void checkShortcutPress(){ - // some shortctus to quickly starts apps -#if defined (CARDPUTER) + // shortctus to quickly starts apps Keyboard.update(); if(Keyboard.isKeyPressed('i')) otherIRcodes(); if(Keyboard.isKeyPressed('r') || Keyboard.isKeyPressed('s')) otherRFcodes(); if(Keyboard.isKeyPressed('b')) usb_setup(); // badusb if(Keyboard.isKeyPressed('w')) loopOptionsWebUi(); -// TODO: other boards +// TODO: other boards? // TODO: user-configurable -#endif } +int checkNumberShortcutPress() { + // shortctus to quickly select options + Keyboard.update(); + char c; + for (c = '1'; c <= '9'; c++) { + if(Keyboard.isKeyPressed(c)) return(c - '1'); + } + // else + return -1; +} +#endif + /* Starts keyboard to type data */ String keyboard(String mytext, int maxSize, String msg) { diff --git a/src/core/mykeyboard.h b/src/core/mykeyboard.h index d5fed3c8..dba18982 100644 --- a/src/core/mykeyboard.h +++ b/src/core/mykeyboard.h @@ -12,7 +12,10 @@ bool checkSelPress(); bool checkEscPress(); +#ifdef CARDPUTER void checkShortcutPress(); +int checkNumberShortcutPress(); +#endif bool checkAnyKeyPress(); diff --git a/src/core/sd_functions.cpp b/src/core/sd_functions.cpp index f4735565..5feb2483 100644 --- a/src/core/sd_functions.cpp +++ b/src/core/sd_functions.cpp @@ -22,6 +22,10 @@ FilePage filePages[100]; // Maximum of 100 pages ** Description: Start SD Card ***************************************************************************************/ bool setupSdCard() { + if(SDCARD_SCK==-1) { + sdcardMounted = false; + return false; + } #if TFT_MOSI == SDCARD_MOSI if (!SD.begin(SDCARD_CS)) #else diff --git a/src/core/serialcmds.cpp b/src/core/serialcmds.cpp index 3fc47da8..7e09386d 100644 --- a/src/core/serialcmds.cpp +++ b/src/core/serialcmds.cpp @@ -2,21 +2,49 @@ #include "serialcmds.h" #include "globals.h" #include -#include -#include "modules/ir/TV-B-Gone.h" +//#include #include "cJSON.h" #include // for PRIu64 -#ifndef STICK_C_PLUS - #include - #include -#endif #include "sd_functions.h" #include "settings.h" #include "display.h" #include "powerSave.h" #include "modules/rf/rf.h" +#include "modules/ir/TV-B-Gone.h" +#include "modules/others/bad_usb.h" + +#if defined(HAS_NS4168_SPKR) + #include "modules/others/audio.h" +#endif + +/* task to handle serial commands, currently used in headless mode only */ +#include +#include + +void serialcmds_loop(void* pvParameters) { + Serial.begin (115200); + while (1) { + handleSerialCommands(); + //delay (500); // wait for half a second + vTaskDelay(500); // sleep this task only + } +} + +void startSerialCommandsHandlerTask() { + TaskHandle_t serialcmdsTaskHandle; + + xTaskCreatePinnedToCore ( + serialcmds_loop, // Function to implement the task + "serialcmds", // Name of the task (any string) + 20000, // Stack size in bytes + NULL, // This is a pointer to the parameter that will be passed to the new task. We are not using it here and therefore it is set to NULL. + 0, // Priority of the task + &serialcmdsTaskHandle, // Task handle (optional, can be NULL). + 0 // Core where the task should run. By default, all your Arduino code runs on Core 1 and the Wi-Fi and RF functions (these are usually hidden from the Arduino environment) use the Core 0. + ); +} void SerialPrintHexString(uint64_t val) { @@ -27,7 +55,10 @@ void SerialPrintHexString(uint64_t val) { Serial.println(s); } + void handleSerialCommands() { + // read and process a single command + String cmd_str; /* @@ -54,15 +85,26 @@ void handleSerialCommands() { //log_d(cmd_str.c_str()); cmd_str.trim(); - cmd_str.toLowerCase(); // case-insensitive matching + // case-insensitive matching only without filename args -- TODO: better solution for this + if(cmd_str.indexOf("from_file ") == -1) + cmd_str.toLowerCase(); + + bool r = processSerialCommand(cmd_str); + if(r) setup_gpio(); // temp fix for menu inf. loop +} - // TODO: more commands https://docs.flipper.net/development/cli#0Z9fs + +bool processSerialCommand(String cmd_str) { + // return true on success, false on error if(cmd_str == "" ) { // empty - return; + return false; } if(cmd_str.startsWith("ir") ) { + + gsetIrTxPin(false); + //if(IrTx==0) IrTx = LED; // quickfix init issue? CARDPUTER is 44 // ir tx
// : NEC, NECext, NEC42, NEC42ext, Samsung32, RC6, RC5, RC5X, SIRC, SIRC15, SIRC20, Kaseikyo, RCA @@ -73,29 +115,41 @@ void handleSerialCommands() { 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; + return true; } 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; + return true; } 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; + return true; } - // TODO: more protocols: Samsung32, SIRC - //if(cmd_str.startsWith("ir tx raw")){ + //if(cmd_str.startsWith("ir tx sirc")){ + //if(cmd_str.startsWith("ir tx samsung")){ + + //if(cmd_str.startsWith("ir tx raw")){ + + if(cmd_str.startsWith("ir tx_from_file ")){ + String filepath = cmd_str.substring(strlen("ir tx_from_file "), cmd_str.length()); + filepath.trim(); + if(filepath.indexOf(".ir") == -1) return false; // invalid filename + if(!filepath.startsWith("/")) filepath = "/" + filepath; // add "/" if missing + if(SD.exists(filepath)) return txIrFile(&SD, filepath); + if(LittleFS.exists(filepath)) return txIrFile(&LittleFS, filepath); + // else file not found + return false; + } 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); @@ -105,7 +159,7 @@ void handleSerialCommands() { cJSON *root = cJSON_Parse(cmd_str.c_str() + 6); if (root == NULL) { Serial.println("This is NOT json format"); - return; + return false; } uint16_t bits = 32; // defaults to 32 bits const char *dataStr = ""; @@ -121,7 +175,7 @@ void handleSerialCommands() { dataStr = dataItem->valuestring; } else { Serial.println("missing or invalid data to send"); - return; + return false; } //String dataStr = cmd_str.substring(36, 36+8); uint64_t data = strtoul(dataStr, nullptr, 16); @@ -135,36 +189,53 @@ void handleSerialCommands() { if(protocolStr == "nec"){ // sendNEC(uint64_t data, uint16_t nbits, uint16_t repeat) irsend.sendNEC(data, bits, 10); + return true; } // TODO: more protocols + return false; } // turn off the led digitalWrite(IrTx, LED_OFF); //backToMenu(); - return; + return false; } // end of ir commands if(cmd_str.startsWith("rf") || cmd_str.startsWith("subghz" )) { - if(RfTx==0) RfTx=GROVE_SDA; // quick fix + + gsetRfTxPin(false); + //if(RfTx==0) RfTx=GROVE_SDA; // quick fix pinMode(RfTx, OUTPUT); //Serial.println(RfTx); - /* WIP: + if(cmd_str.startsWith("subghz tx_from_file")) { + String filepath = cmd_str.substring(strlen("subghz tx_from_file "), cmd_str.length()); + filepath.trim(); + if(filepath.indexOf(".sub") == -1) return false; // invalid filename + if(!filepath.startsWith("/")) filepath = "/" + filepath; // add "/" if missing + if(SD.exists(filepath)) return txSubFile(&SD, filepath); + if(LittleFS.exists(filepath)) return txSubFile(&LittleFS, filepath); + // else file not found + return false; + } + + /* TODO: 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} + * //RCSwitch_send( hexStringToDecimal(txt.c_str()) , bits, pulse, protocol, repeat); }*/ + if(cmd_str.startsWith("rfsend")) { - // tasmota json command https://tasmota.github.io/docs/Tasmota-IR/#sending-ir-commands + // tasmota json command https://tasmota.github.io/docs/RF-Protocol/ // 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; + return false; } unsigned int bits = 32; // defaults to 32 bits const char *dataStr = ""; @@ -187,7 +258,7 @@ void handleSerialCommands() { } else { Serial.println("missing or invalid data to send"); cJSON_Delete(root); - return; + return false; } //String dataStr = cmd_str.substring(36, 36+8); uint64_t data = strtoul(dataStr, nullptr, 16); @@ -198,101 +269,64 @@ void handleSerialCommands() { RCSwitch_send(data, bits, pulse, protocol, repeat); cJSON_Delete(root); - return; + return true; } } // endof rf + + #if defined(USB_as_HID) + // badusb available + if(cmd_str.startsWith("badusb tx_from_file ")) { + String filepath = cmd_str.substring(strlen("badusb tx_from_file "), cmd_str.length()); + filepath.trim(); + if(filepath.indexOf(".txt") == -1) return false; // invalid filename + if(!filepath.startsWith("/")) filepath = "/" + filepath; // add "/" if missing + FS* fs = NULL; + if(SD.exists(filepath)) fs = &SD; + if(LittleFS.exists(filepath)) fs = &LittleFS; + if(!fs) return false; // file not found + Kb.begin(); + USB.begin(); + key_input(*fs, filepath); + return true; + } + #endif #if defined(HAS_NS4168_SPKR) //M5StickCs doesn't have speakers.. they have buzzers on pin 02 that only beeps in different frequencies - if(cmd_str.startsWith("music_player " ) || cmd_str.startsWith("tts" ) || cmd_str.startsWith("say" ) ) { - // TODO: move in audio.cpp module - AudioOutputI2S *audioout = new AudioOutputI2S(); // https://github.com/earlephilhower/ESP8266Audio/blob/master/src/AudioOutputI2S.cpp#L32 - audioout->SetPinout(BCLK, WCLK, DOUT); // bclk, wclk, dout - 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("1mgm.sf2"); // TODO: make configurable - if(!sf2) sf2 = new AudioFileSourceLittleFS("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 - } - } + 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 + return playAudioRTTTLString(song); + + } else if(song.indexOf(".") != -1) { + // try to open "song" as a file + // e.g. music_player boot.wav + if(!song.startsWith("/")) song = "/" + song; // add "/" if missing + if(SD.exists(song)) return playAudioFile(&SD, song); + if(LittleFS.exists(song)) return playAudioFile(&LittleFS, song); + // else not found + return false; } + } - //TODO: tone - // https://github.com/earlephilhower/ESP8266Audio/issues/643 - - //TODO: webradio - // https://github.com/earlephilhower/ESP8266Audio/tree/master/examples/WebRadio + //TODO: tone + // https://github.com/earlephilhower/ESP8266Audio/issues/643 - 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; - } + //TODO: webradio + // https://github.com/earlephilhower/ESP8266Audio/tree/master/examples/WebRadio - 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 - #endif + if(cmd_str.startsWith("tts " ) || cmd_str.startsWith("say " )) { + String text = cmd_str.substring(4, cmd_str.length()); + return tts(text); + } + #endif // HAS_NS4168_SPKR // WIP: record | mic // https://github.com/earlephilhower/ESP8266Audio/issues/70 // https://github.com/earlephilhower/ESP8266Audio/pull/118 +#if defined(HAS_SCREEN) // backlight brightness adjust (range 0-255) https://docs.flipper.net/development/cli/#XQQAI // e.g. "led br 127" if(cmd_str.startsWith("led br ")) { @@ -303,7 +337,7 @@ void handleSerialCommands() { if(value<=0) value=1; if(value>100) value=100; setBrightness(value, false); // false -> do not save - return; + return true; } else if(cmd_str.startsWith("led ")) { // change UI color @@ -312,18 +346,24 @@ void handleSerialCommands() { int r, g, b; if (sscanf(rgbString, "%d %d %d", &r, &g, &b) != 3) { Serial.println("invalid color: " + String(rgbString)); - return; + return false; } if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) { Serial.println("invalid color: " + String(rgbString)); - return; + return false; } uint16_t hexColor = tft.color565(r, g, b); // Use the TFT_eSPI function to convert RGB to 16-bit color //Serial.print("converted color:"); //SerialPrintHexString(hexColor); FGCOLOR = hexColor; // change global var, dont save in settings - return; + return true; } + if(cmd_str == "clock" ) { + //esp_timer_stop(screensaver_timer); // disable screensaver while the clock is running + runClockLoop(); + return true; + } +#endif // HAS_SCREEN // power cmds: off, reboot, sleep if(cmd_str == "power off" ) { @@ -337,33 +377,27 @@ void handleSerialCommands() { //ESP.deepSleep(0); esp_deep_sleep_start(); // only wake up via hardware reset #endif - return; + return true; } if(cmd_str == "power reboot" ) { ESP.restart(); - return; + return true; } if(cmd_str == "power sleep" ) { // NOTE: cmd not supported on flipper0 setSleepMode(); //turnOffDisplay(); //esp_timer_stop(screensaver_timer); - return; - } - - if(cmd_str == "clock" ) { - //esp_timer_stop(screensaver_timer); // disable screensaver while the clock is running - runClockLoop(); - return; + return true; } // TODO: "storage" cmd to manage files https://docs.flipper.net/development/cli/#Xgais // TODO: "gpio" cmds https://docs.flipper.net/development/cli/#aqA4b + // TODO: more commands https://docs.flipper.net/development/cli#0Z9fs Serial.println("unsupported serial command: " + cmd_str); - - + return false; } diff --git a/src/core/serialcmds.h b/src/core/serialcmds.h index 8ffcac56..cb5a1481 100644 --- a/src/core/serialcmds.h +++ b/src/core/serialcmds.h @@ -1,3 +1,9 @@ +#include + +void handleSerialCommands(); + +bool processSerialCommand(String cmd_str); + +void startSerialCommandsHandlerTask(); -void handleSerialCommands(); \ No newline at end of file diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 5957f970..fd12e5a2 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -44,12 +44,16 @@ void setBrightness(int brightval, bool save) { if(bright>100) bright=100; #if defined(STICK_C_PLUS2) || defined(CARDPUTER) - int bl = MINBRIGHT + round(((255 - MINBRIGHT) * brightval/100 )); - analogWrite(BACKLIGHT, bl); + if(brightval == 0){ + analogWrite(BACKLIGHT, brightval); + } else { + int bl = MINBRIGHT + round(((255 - MINBRIGHT) * brightval/100 )); + analogWrite(BACKLIGHT, bl); + } #elif defined(STICK_C_PLUS) - axp192.ScreenBreath(brightval); + axp192.ScreenBreath(brightval); #elif defined(M5STACK) - M5.Display.setBrightness(brightval); + M5.Display.setBrightness(brightval); #endif if(save){ diff --git a/src/main.cpp b/src/main.cpp index fa676b4e..25364722 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,9 +46,15 @@ std::vector>> options; const int bufSize = 4096; uint8_t buff[4096] = {0}; // Protected global variables -TFT_eSPI tft = TFT_eSPI(); // Invoke custom library -TFT_eSprite sprite = TFT_eSprite(&tft); -TFT_eSprite draw = TFT_eSprite(&tft); +#if defined(HAS_SCREEN) + TFT_eSPI tft = TFT_eSPI(); // Invoke custom library + TFT_eSprite sprite = TFT_eSprite(&tft); + TFT_eSprite draw = TFT_eSprite(&tft); +#else + SerialDisplayClass tft; + SerialDisplayClass& sprite = tft; + SerialDisplayClass& draw = tft; +#endif #if defined(CARDPUTER) Keyboard_Class Keyboard = Keyboard_Class(); @@ -63,6 +69,7 @@ TFT_eSprite draw = TFT_eSprite(&tft); #include "core/settings.h" #include "core/main_menu.h" #include "core/serialcmds.h" +#include "modules/others/audio.h" /********************************************************************* @@ -84,8 +91,8 @@ void setup_gpio() { Keyboard.begin(); pinMode(0, INPUT); pinMode(10, INPUT); // Pin that reads the - #elif defined(M5STACK) // init must be done after tft, to make SDCard work - #elif defined(NEW_DEVICE) + #elif ! defined(HAS_SCREEN) + // do nothing #else pinMode(UP_BTN, INPUT); // Sets the power btn as an INPUT pinMode(SEL_BTN, INPUT); @@ -103,7 +110,12 @@ void setup_gpio() { ** Config tft *********************************************************************/ void begin_tft(){ +#if defined(HAS_SCREEN) tft.init(); +#else + tft.begin(); //115200, 240,320); + tft.clear(); +#endif rotation = gsetRotation(); tft.setRotation(rotation); resetTftDisplay(); @@ -139,10 +151,18 @@ void boot_screen() { } } +#if defined(BUZZ_PIN) // Bip M5 just because it can. Does not bip if splashscreen is bypassed _tone(5000, 50); delay(200); _tone(5000, 50); +/* 2fix: menu infinite loop */ +#elif defined(HAS_NS4168_SPKR) + // play a boot sound + if(SD.exists("/boot.wav")) playAudioFile(&SD, "/boot.wav"); + else if(LittleFS.exists("/boot.wav")) playAudioFile(&LittleFS, "/boot.wav"); + setup_gpio(); // temp fix for menu inf. loop +#endif } @@ -247,11 +267,17 @@ void setup() { M5.begin(); // Begin after TFT, for SDCard to work #endif load_eeprom(); - boot_screen(); init_clock(); - + if(!LittleFS.begin(true)) { LittleFS.format(), LittleFS.begin();} - + + boot_screen(); + + #if ! defined(HAS_SCREEN) + // start a task to handle serial commands while the webui is running + startSerialCommandsHandlerTask(); + #endif + delay(200); previousMillis = millis(); } @@ -260,6 +286,7 @@ void setup() { ** Function: loop ** Main loop **********************************************************************/ +#if defined(HAS_SCREEN) void loop() { #if defined(HAS_RTC) RTC_TimeTypeDef _time; @@ -326,3 +353,21 @@ void loop() { } } } +#else + +// alternative loop function for headless boards +#include "core/wifi_common.h" +#include "modules/others/webInterface.h" + +void loop() { + setupSdCard(); + getConfigs(); + + if(!wifiConnected) { + Serial.println("wifiConnect"); + wifiConnect("",0,true); // TODO: read mode from settings file + } + Serial.println("startWebUi"); + startWebUi(true); // MEMO: will quit when checkEscPress +} +#endif diff --git a/src/modules/ir/TV-B-Gone.cpp b/src/modules/ir/TV-B-Gone.cpp index c588c7cf..831ed523 100644 --- a/src/modules/ir/TV-B-Gone.cpp +++ b/src/modules/ir/TV-B-Gone.cpp @@ -251,7 +251,149 @@ struct Codes selectRecentIrMenu() { loopOptions(options); return(selected_code); } + + +bool txIrFile(FS *fs, String filepath) { + // SPAM all codes of the file + + int total_codes = 0; + String line; + + File databaseFile = fs->open(filepath, FILE_READ); + + pinMode(IrTx, OUTPUT); + //digitalWrite(IrTx, LED_ON); + + if (!databaseFile) { + Serial.println("Failed to open database file."); + displayError("Fail to open file"); + delay(2000); + return false; + } + Serial.println("Opened database file."); + + bool endingEarly; + int codes_sent=0; + int frequency = 0; + String rawData = ""; + String protocol = ""; + String address = ""; + String command = ""; + databaseFile.seek(0); // comes back to first position + + // count the number of codes to replay + while (databaseFile.available()) { + line = databaseFile.readStringUntil('\n'); + if(line.startsWith("type:")) total_codes++; + } + + Serial.printf("\nStarted SPAM all codes with: %d codes", total_codes); + // comes back to first position, beggining of the file + databaseFile.seek(0); + while (databaseFile.available()) { + progressHandler(codes_sent,total_codes); + line = databaseFile.readStringUntil('\n'); + if (line.endsWith("\r")) line.remove(line.length() - 1); + + if (line.startsWith("type:")) { + codes_sent++; + String type = line.substring(5); + type.trim(); + Serial.println("Type: "+type); + if (type == "raw") { + Serial.println("RAW code"); + while (databaseFile.available()) { + line = databaseFile.readStringUntil('\n'); + if (line.endsWith("\r")) line.remove(line.length() - 1); + + if (line.startsWith("frequency:")) { + line = line.substring(10); + line.trim(); + frequency = line.toInt(); + Serial.println("Frequency: " + String(frequency)); + } else if (line.startsWith("data:")) { + rawData = line.substring(5); + rawData.trim(); + Serial.println("RawData: "+rawData); + } else if ((frequency != 0 && rawData != "") || line.startsWith("#")) { + sendRawCommand(frequency, rawData); + rawData = ""; + frequency = 0; + type = ""; + line = ""; + break; + } + } + } else if (type == "parsed") { + Serial.println("PARSED"); + while (databaseFile.available()) { + line = databaseFile.readStringUntil('\n'); + if (line.endsWith("\r")) line.remove(line.length() - 1); + + if (line.startsWith("protocol:")) { + protocol = line.substring(9); + protocol.trim(); + Serial.println("Protocol: "+protocol); + } else if (line.startsWith("address:")) { + address = line.substring(8); + address.trim(); + Serial.println("Address: "+address); + } else if (line.startsWith("command:")) { + command = line.substring(8); + command.trim(); + Serial.println("Command: "+command); + } else if (line.indexOf("#") != -1) { // TODO: also detect EOF + if (protocol.startsWith("NEC")) { + sendNECCommand(address, command); + } else if (protocol.startsWith("RC5")) { + sendRC5Command(address, command); + } else if (protocol.startsWith("RC6")) { + sendRC6Command(address, command); + } else if (protocol.startsWith("Samsung")) { + sendSamsungCommand(address, command); + } else if (protocol.startsWith("SIRC")) { + sendSonyCommand(address, command); + } + protocol = ""; + address = ""; + command = ""; + type = ""; + line = ""; + break; + } + } + } + } + // if user is pushing (holding down) TRIGGER button, stop transmission early + if (checkSelPress()) // Pause TV-B-Gone + { + while (checkSelPress()) yield(); + displayRedStripe("Paused", TFT_WHITE, BGCOLOR); + + while (!checkSelPress()){ // If Presses Select again, continues + if(checkEscPress()) { + endingEarly= true; + break; + } + } + while (checkSelPress()){ + yield(); + } + if (endingEarly) break; // Cancels custom IR Spam + displayRedStripe("Running, Wait", TFT_WHITE, FGCOLOR); + } + } // end while file has lines to process + databaseFile.close(); + Serial.println("closed"); + Serial.println("EXTRA finished"); + + resetCodesArray(); + digitalWrite(IrTx, LED_OFF); + return true; +} + + void otherIRcodes() { resetCodesArray(); int total_codes = 0; @@ -259,6 +401,7 @@ void otherIRcodes() { File databaseFile; FS *fs = NULL; struct Codes selected_code; + options = { {"Recent", [&]() { selected_code = selectRecentIrMenu(); }}, {"LittleFS", [&]() { fs=&LittleFS; }}, @@ -282,11 +425,31 @@ void otherIRcodes() { // no need to proceed, go back } + // select a file to tx filepath = loopSD(*fs, true, "IR"); + if(filepath=="") return; // cancelled + // else + + // select mode + bool mode_cmd=true; + options = { + {"Choose cmd", [&]() { mode_cmd=true; }}, + {"Spam all", [&]() { mode_cmd=false; }}, + }; + delay(200); + loopOptions(options); + delay(200); + + if(mode_cmd == false) { + // Spam all selected + txIrFile(fs, filepath); + return; + } + + // else continue and try to parse the file + databaseFile = fs->open(filepath, FILE_READ); drawMainBorder(); - pinMode(IrTx, OUTPUT); - //digitalWrite(IrTx, LED_ON); if (!databaseFile) { Serial.println("Failed to open database file."); @@ -295,176 +458,51 @@ void otherIRcodes() { return; } Serial.println("Opened database file."); - bool mode_cmd=true; - options = { - {"Choose cmd", [&]() { mode_cmd=true; }}, - {"Spam all", [&]() { mode_cmd=false; }}, - }; - delay(200); - loopOptions(options); - delay(200); - + + pinMode(IrTx, OUTPUT); + //digitalWrite(IrTx, LED_ON); + // Mode to choose and send command by command limitted to 50 commands String line; - - // Mode to choose and send command by command limitted to 50 commands - if(mode_cmd) { - String txt; - while (databaseFile.available() && total_codes<50) { - line = databaseFile.readStringUntil('\n'); - txt=line.substring(line.indexOf(":") + 1); - txt.trim(); - if(line.startsWith("name:")) { codes[total_codes].name = txt; codes[total_codes].filepath = txt + " " + filepath.substring( 1 + filepath.lastIndexOf("/") ) ;} - if(line.startsWith("type:")) codes[total_codes].type = txt; - if(line.startsWith("protocol:")) codes[total_codes].protocol = txt; - if(line.startsWith("address:")) codes[total_codes].address = txt; - if(line.startsWith("frequency:")) codes[total_codes].frequency = txt.toInt(); - //if(line.startsWith("duty_cycle:")) codes[total_codes].duty_cycle = txt.toFloat(); - if(line.startsWith("command:")) { codes[total_codes].command = txt; total_codes++; } - if(line.startsWith("data:")) { codes[total_codes].data = txt; total_codes++; } - } - options = { }; - bool exit = false; - for(int i=0; i<=total_codes; i++) { - if(codes[i].type=="raw") options.push_back({ codes[i].name.c_str(), [=](){ sendRawCommand(codes[i].frequency, codes[i].data); addToRecentCodes(codes[i]); }}); - if(codes[i].protocol.startsWith("NEC")) options.push_back({ codes[i].name.c_str(), [=](){ sendNECCommand(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); - if(codes[i].protocol.startsWith("RC5")) options.push_back({ codes[i].name.c_str(), [=](){ sendRC5Command(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); - if(codes[i].protocol.startsWith("RC6")) options.push_back({ codes[i].name.c_str(), [=](){ sendRC6Command(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); - if(codes[i].protocol.startsWith("Samsung")) options.push_back({ codes[i].name.c_str(), [=](){ sendSamsungCommand(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); - if(codes[i].protocol=="SIRC") options.push_back({ codes[i].name.c_str(), [=](){ sendSonyCommand(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); - } - options.push_back({ "Main Menu" , [&](){ exit=true; }}); - databaseFile.close(); - - digitalWrite(IrTx, LED_OFF); - while (1) { - delay(200); - loopOptions(options); - if(checkEscPress() || exit) break; - delay(200); - } + String txt; + while (databaseFile.available() && total_codes<50) { + line = databaseFile.readStringUntil('\n'); + txt=line.substring(line.indexOf(":") + 1); + txt.trim(); + if(line.startsWith("name:")) { codes[total_codes].name = txt; codes[total_codes].filepath = txt + " " + filepath.substring( 1 + filepath.lastIndexOf("/") ) ;} + if(line.startsWith("type:")) codes[total_codes].type = txt; + if(line.startsWith("protocol:")) codes[total_codes].protocol = txt; + if(line.startsWith("address:")) codes[total_codes].address = txt; + if(line.startsWith("frequency:")) codes[total_codes].frequency = txt.toInt(); + //if(line.startsWith("duty_cycle:")) codes[total_codes].duty_cycle = txt.toFloat(); + if(line.startsWith("command:")) { codes[total_codes].command = txt; total_codes++; } + if(line.startsWith("data:")) { codes[total_codes].data = txt; total_codes++; } } - - - else { // SPAM all codes of the file - bool endingEarly; - int codes_sent=0; - int frequency = 0; - String rawData = ""; - String protocol = ""; - String address = ""; - String command = ""; - databaseFile.seek(0); // comes back to first position - // count the number of codes to replay - while (databaseFile.available()) { - line = databaseFile.readStringUntil('\n'); - if(line.startsWith("type:")) total_codes++; - } - - Serial.printf("\nStarted SPAM all codes with: %d codes", total_codes); - // comes back to first position, beggining of the file - databaseFile.seek(0); - while (databaseFile.available()) { - progressHandler(codes_sent,total_codes); - line = databaseFile.readStringUntil('\n'); - if (line.endsWith("\r")) line.remove(line.length() - 1); - - if (line.startsWith("type:")) { - codes_sent++; - String type = line.substring(5); - type.trim(); - Serial.println("Type: "+type); - if (type == "raw") { - Serial.println("RAW code"); - while (databaseFile.available()) { - line = databaseFile.readStringUntil('\n'); - if (line.endsWith("\r")) line.remove(line.length() - 1); - - if (line.startsWith("frequency:")) { - line = line.substring(10); - line.trim(); - frequency = line.toInt(); - Serial.println("Frequency: " + String(frequency)); - } else if (line.startsWith("data:")) { - rawData = line.substring(5); - rawData.trim(); - Serial.println("RawData: "+rawData); - } else if ((frequency != 0 && rawData != "") || line.startsWith("#")) { - sendRawCommand(frequency, rawData); - rawData = ""; - frequency = 0; - type = ""; - line = ""; - break; - } - } - } else if (type == "parsed") { - Serial.println("PARSED"); - while (databaseFile.available()) { - line = databaseFile.readStringUntil('\n'); - if (line.endsWith("\r")) line.remove(line.length() - 1); - - if (line.startsWith("protocol:")) { - protocol = line.substring(9); - protocol.trim(); - Serial.println("Protocol: "+protocol); - } else if (line.startsWith("address:")) { - address = line.substring(8); - address.trim(); - Serial.println("Address: "+address); - } else if (line.startsWith("command:")) { - command = line.substring(8); - command.trim(); - Serial.println("Command: "+command); - } else if (line.indexOf("#") != -1) { - if (protocol.startsWith("NEC")) { - sendNECCommand(address, command); - } else if (protocol.startsWith("RC5")) { - sendRC5Command(address, command); - } else if (protocol.startsWith("RC6")) { - sendRC6Command(address, command); - } else if (protocol.startsWith("Samsung")) { - sendSamsungCommand(address, command); - } else if (protocol.startsWith("SIRC")) { - sendSonyCommand(address, command); - } - protocol = ""; - address = ""; - command = ""; - type = ""; - line = ""; - break; - } - } - } - } - // if user is pushing (holding down) TRIGGER button, stop transmission early - if (checkSelPress()) // Pause TV-B-Gone - { - while (checkSelPress()) yield(); - displayRedStripe("Paused", TFT_WHITE, BGCOLOR); - - while (!checkSelPress()){ // If Presses Select again, continues - if(checkEscPress()) { - endingEarly= true; - break; - } - } - while (checkSelPress()){ - yield(); - } - if (endingEarly) break; // Cancels custom IR Spam - displayRedStripe("Running, Wait", TFT_WHITE, FGCOLOR); - } - - } - databaseFile.close(); - Serial.println("closed"); - Serial.println("EXTRA finished"); + options = { }; + bool exit = false; + for(int i=0; i<=total_codes; i++) { + if(codes[i].type=="raw") options.push_back({ codes[i].name.c_str(), [=](){ sendRawCommand(codes[i].frequency, codes[i].data); addToRecentCodes(codes[i]); }}); + if(codes[i].protocol.startsWith("NEC")) options.push_back({ codes[i].name.c_str(), [=](){ sendNECCommand(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); + if(codes[i].protocol.startsWith("RC5")) options.push_back({ codes[i].name.c_str(), [=](){ sendRC5Command(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); + if(codes[i].protocol.startsWith("RC6")) options.push_back({ codes[i].name.c_str(), [=](){ sendRC6Command(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); + if(codes[i].protocol.startsWith("Samsung")) options.push_back({ codes[i].name.c_str(), [=](){ sendSamsungCommand(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); + if(codes[i].protocol=="SIRC") options.push_back({ codes[i].name.c_str(), [=](){ sendSonyCommand(codes[i].address, codes[i].command); addToRecentCodes(codes[i]); }}); } - resetCodesArray(); + options.push_back({ "Main Menu" , [&](){ exit=true; }}); + databaseFile.close(); + digitalWrite(IrTx, LED_OFF); -} + + while (1) { + delay(200); + loopOptions(options); + if(checkEscPress() || exit) break; + delay(200); + } + + returnToMenu=true; +} // end of otherIRcodes + //IR commands void sendNECCommand(String address, String command) { diff --git a/src/modules/ir/TV-B-Gone.h b/src/modules/ir/TV-B-Gone.h index 4c0c37be..591a2932 100644 --- a/src/modules/ir/TV-B-Gone.h +++ b/src/modules/ir/TV-B-Gone.h @@ -72,3 +72,4 @@ void sendRC6Command(String address, String command); void sendSamsungCommand(String address, String command); void sendSonyCommand(String address, String command); void otherIRcodes(); +bool txIrFile(FS *fs, String filepath); diff --git a/src/modules/others/audio.cpp b/src/modules/others/audio.cpp new file mode 100644 index 00000000..842485d9 --- /dev/null +++ b/src/modules/others/audio.cpp @@ -0,0 +1,114 @@ +#include "audio.h" +#include +#include +#include "core/mykeyboard.h" + + +#if defined(HAS_NS4168_SPKR) + +bool playAudioFile(FS* fs, String filepath) { + + AudioOutputI2S* audioout = new AudioOutputI2S(); // https://github.com/earlephilhower/ESP8266Audio/blob/master/src/AudioOutputI2S.cpp#L32 + audioout->SetPinout(BCLK, WCLK, DOUT); + + AudioFileSource* source = new AudioFileSourceFS(*fs, filepath.c_str()); + if(!source) return false; + + AudioGenerator* generator = NULL; + + // switch on extension + filepath.toLowerCase(); // case-insensitive match + if (filepath.endsWith(".txt") || filepath.endsWith(".rtttl")) + generator = new AudioGeneratorRTTTL(); + if (filepath.endsWith(".wav")) + generator = new AudioGeneratorWAV(); + if (filepath.endsWith(".mod")) + generator = new AudioGeneratorMOD(); + if (filepath.endsWith(".mp3")) { + generator = new AudioGeneratorMP3(); + source = new AudioFileSourceID3(source); + } + if (filepath.endsWith(".opus")) generator = new AudioGeneratorOpus(); + /* 2FIX: compilation issues + if(song.endsWith(".mid")) { + // need to load a soundfont + AudioFileSource* sf2 = NULL; + if(setupSdCard()) sf2 = new AudioFileSourceSD("1mgm.sf2"); // TODO: make configurable + if(!sf2) sf2 = new AudioFileSourceLittleFS("1mgm.sf2"); // TODO: make configurable + if(sf2) { + // a soundfount was found + AudioGeneratorMIDI* midi = new AudioGeneratorMIDI(); + generator->SetSoundfont(sf2); + generator = midi; + } + }*/ + + if (generator && source && audioout) { + Serial.println("Start audio"); + generator->begin(source, audioout); + while (generator->isRunning()) { + if (!generator->loop()) generator->stop(); // || checkEscPress() + + } + audioout->stop(); + source->close(); + Serial.println("Stop audio"); + + delete generator; + delete source; + delete audioout; + + return true; + } + + return false; // init error +} + +bool playAudioRTTTLString(String song) { + // derived from https://github.com/earlephilhower/ESP8266Audio/blob/master/examples/PlayRTTTLToI2SDAC/PlayRTTTLToI2SDAC.ino + + song.trim(); + if(song=="") return false; + + AudioOutputI2S* audioout = new AudioOutputI2S(); + audioout->SetPinout(BCLK, WCLK, DOUT); + + AudioGenerator* generator = new AudioGeneratorRTTTL(); + + AudioFileSource* source = new AudioFileSourcePROGMEM( song.c_str(), song.length() ); + + if (generator && source && audioout) { + Serial.println("Start audio"); + generator->begin(source, audioout); + while (generator->isRunning()) { + if (!generator->loop()) generator->stop(); + } + audioout->stop(); + source->close(); + Serial.println("Stop audio"); + + delete generator; + delete source; + delete audioout; + + return true; + } + return false; // init error +} + +bool tts(String text){ + text.trim(); + if(text=="") return false; + + AudioOutputI2S* audioout = new AudioOutputI2S(); + audioout->SetPinout(BCLK, WCLK, DOUT); + + // https://github.com/earlephilhower/ESP8266SAM/blob/master/examples/Speak/Speak.ino + audioout->begin(); + ESP8266SAM *sam = new ESP8266SAM; + sam->Say(audioout, text.c_str()); + delete sam; + return true; +} + +#endif diff --git a/src/modules/others/audio.h b/src/modules/others/audio.h new file mode 100644 index 00000000..08256ec3 --- /dev/null +++ b/src/modules/others/audio.h @@ -0,0 +1,10 @@ + +#include +#include +#include + +bool playAudioFile(FS* fs, String filepath); // TODO: bool async arg -> play in a task? + +bool playAudioRTTTLString(String song); + +bool tts(String text); \ No newline at end of file diff --git a/src/modules/others/bad_usb.cpp b/src/modules/others/bad_usb.cpp index a57e95df..477b485f 100644 --- a/src/modules/others/bad_usb.cpp +++ b/src/modules/others/bad_usb.cpp @@ -272,7 +272,7 @@ void usb_setup() { - +#if defined(CARDPUTER) //Now cardputer works as a USB Keyboard! //Keyboard functions @@ -347,6 +347,6 @@ void usb_keyboard() { } } } +#endif - -#endif \ No newline at end of file +#endif diff --git a/src/modules/others/bad_usb.h b/src/modules/others/bad_usb.h index 296a51f9..038e4a71 100644 --- a/src/modules/others/bad_usb.h +++ b/src/modules/others/bad_usb.h @@ -8,6 +8,8 @@ // For some reason, StickC and P and P2 dont recognize the following library... may be it need to use EspTinyUSB lib.... need studies #include +extern USBHIDKeyboard Kb; + void key_input(FS fs, String bad_script = "/badpayload.txt"); void usb_setup(); @@ -15,4 +17,4 @@ void usb_setup(); void usb_keyboard(); -#endif \ No newline at end of file +#endif diff --git a/src/modules/others/qrcode_menu.cpp b/src/modules/others/qrcode_menu.cpp index 556bfa08..b6f33c3f 100644 --- a/src/modules/others/qrcode_menu.cpp +++ b/src/modules/others/qrcode_menu.cpp @@ -32,12 +32,14 @@ String calculate_crc(String input) { void qrcode_display(String qrcodeUrl) { +#ifdef DHAS_SCREEN QRcode qrcode(&tft); qrcode.init(); qrcode.create(qrcodeUrl); while(!checkEscPress() && !checkSelPress()) delay(100); tft.fillScreen(BGCOLOR); +#endif } void custom_qrcode() { diff --git a/src/modules/others/webInterface.cpp b/src/modules/others/webInterface.cpp index a91762bf..0b35b539 100644 --- a/src/modules/others/webInterface.cpp +++ b/src/modules/others/webInterface.cpp @@ -3,6 +3,7 @@ #include "core/wifi_common.h" // using common wifisetup #include "core/mykeyboard.h" // using keyboard when calling rename #include "core/display.h" // using displayRedStripe as error msg +#include "core/serialcmds.h" #include "webInterface.h" @@ -126,10 +127,15 @@ String listFiles(FS fs, bool ishtml, String folder, bool isLittleFS) { if(!(foundfile.isDirectory())) { if (ishtml) { returnText += "" + String(foundfile.name()); - if (String(foundfile.name()).substring(String(foundfile.name()).lastIndexOf('.') + 1).equalsIgnoreCase("bin")) returnText+= " "; returnText += "\n"; returnText += "" + humanReadableSize(foundfile.size()) + "\n"; returnText += "  \n"; + //if (String(foundfile.name()).substring(String(foundfile.name()).lastIndexOf('.') + 1).equalsIgnoreCase("bin")) returnText+= "  \n"; + if (String(foundfile.name()).substring(String(foundfile.name()).lastIndexOf('.') + 1).equalsIgnoreCase("sub")) returnText+= "  \n"; + if (String(foundfile.name()).substring(String(foundfile.name()).lastIndexOf('.') + 1).equalsIgnoreCase("ir")) returnText+= "  \n"; + #if defined(USB_as_HID) + if (String(foundfile.name()).substring(String(foundfile.name()).lastIndexOf('.') + 1).equalsIgnoreCase("txt")) returnText+= "  \n"; + #endif returnText += "  \n"; returnText += "\n\n"; } else { @@ -250,6 +256,22 @@ void configureWebServer() { // Index page server->on("/", HTTP_GET, []() { if (checkUserWebAuth()) { + // WIP: custom webui page serving + /* + FS* fs = NULL; + File custom_index_html_file = NONE; + if(SD.exists("/webui.html")) fs = &SD; + if(LittleFS.exists("/webui.html")) fs = &LittleFS; + if(fs) { + // try to read the custom page and serve that + File custom_index_html_file = fs->open("/webui.html", FILE_READ); + if(custom_index_html_file) { + // read the whole file + //server->send(200, "text/html", processor(custom_index_html)); + } + } + */ + // just serve the hardcoded page server->send(200, "text/html", processor(index_html)); } else { server->requestAuthentication(); @@ -279,7 +301,20 @@ void configureWebServer() { } }); - + + // Route to send an generic command (Tasmota compatible API) https://tasmota.github.io/docs/Commands/#with-web-requests + server->on("/cm", HTTP_POST, []() { + if (server->hasArg("cmnd")) { + String cmnd = server->arg("cmnd"); + if( processSerialCommand( cmnd ) ) { + server->send(200, "text/plain", "OK"); + } else { + server->send(400, "text/plain", "ERROR, check the serial log on the device for details"); + } + } + server->send(400, "text/plain", "http request missing required arg: cmnd"); + }); + // Reinicia o ESP server->on("/reboot", HTTP_GET, []() { if (checkUserWebAuth()) { @@ -480,4 +515,4 @@ void startWebUi(bool mode_ap) { delay(100); wifiDisconnect(); -} \ No newline at end of file +} diff --git a/src/modules/others/webInterface.h b/src/modules/others/webInterface.h index 481b111d..be662ca6 100644 --- a/src/modules/others/webInterface.h +++ b/src/modules/others/webInterface.h @@ -6,6 +6,8 @@ #include #include +extern WebServer* server; // used to check if the webserver is running + // function defaults String humanReadableSize(uint64_t bytes); String listFiles(FS fs, bool ishtml, String folder, bool isLittleFS); @@ -25,6 +27,7 @@ const char index_html[] PROGMEM = R"rawliteral( +