├── Pictures ├── Dev_board.jpg ├── meter_during.jpg ├── Charge_detect.jpg ├── SparkBox_final.jpg ├── SparkBox_Battery.png ├── web-file-manager.png ├── SparkBox_Heltec_Exp.png ├── thumbnail_IMG_7475.jpg └── SparkBox_Heltec_Exp_2.png ├── Banks.cpp ├── HTTP_Method.h ├── Banks.h ├── mimetable.h ├── RingBuffer.h ├── ESPxWebFlMgrWpF.h ├── RequestHandler.h ├── mimetable.cpp ├── CRC32.cpp ├── Issue_list.txt ├── SparkStructures.h ├── Spark.h ├── SimplePortal.h ├── CRC32.h ├── config.h ├── SparkComms.h ├── RingBuffer.ino ├── SimplePortal.cpp ├── ESPxWebFlMgr.h ├── RequestHandlersImpl.h ├── SparkIO.h ├── WebServer.h ├── UI.h ├── README.md ├── SparkBox.ino ├── ESPxWebFlMgrWp.h ├── SparkComms.ino ├── SparkPresets.h ├── Spark.ino ├── bitmaps.h ├── Parsing.cpp └── WebServer.cpp /Pictures/Dev_board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/Dev_board.jpg -------------------------------------------------------------------------------- /Pictures/meter_during.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/meter_during.jpg -------------------------------------------------------------------------------- /Pictures/Charge_detect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/Charge_detect.jpg -------------------------------------------------------------------------------- /Pictures/SparkBox_final.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/SparkBox_final.jpg -------------------------------------------------------------------------------- /Pictures/SparkBox_Battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/SparkBox_Battery.png -------------------------------------------------------------------------------- /Pictures/web-file-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/web-file-manager.png -------------------------------------------------------------------------------- /Pictures/SparkBox_Heltec_Exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/SparkBox_Heltec_Exp.png -------------------------------------------------------------------------------- /Pictures/thumbnail_IMG_7475.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/thumbnail_IMG_7475.jpg -------------------------------------------------------------------------------- /Pictures/SparkBox_Heltec_Exp_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/happyhappysundays/SparkBox/HEAD/Pictures/SparkBox_Heltec_Exp_2.png -------------------------------------------------------------------------------- /Banks.cpp: -------------------------------------------------------------------------------- 1 | #include "Banks.h" 2 | 3 | tBankConfig bankConfig[NUM_BANKS+1]; 4 | tPedalCfg pedalCfg; 5 | String bankConfigFile = "/.bank_config.json"; 6 | 7 | 8 | eMode_t curMode = MODE_PRESETS; 9 | eMode_t oldMode = MODE_PRESETS; 10 | eMode_t returnMode = MODE_PRESETS; 11 | eMode_t mainMode = MODE_PRESETS; 12 | -------------------------------------------------------------------------------- /HTTP_Method.h: -------------------------------------------------------------------------------- 1 | #ifndef _HTTP_Method_H_ 2 | #define _HTTP_Method_H_ 3 | 4 | typedef enum { 5 | HTTP_GET = 0b00000001, 6 | HTTP_POST = 0b00000010, 7 | HTTP_DELETE = 0b00000100, 8 | HTTP_PUT = 0b00001000, 9 | HTTP_PATCH = 0b00010000, 10 | HTTP_HEAD = 0b00100000, 11 | HTTP_OPTIONS = 0b01000000, 12 | HTTP_ANY = 0b01111111, 13 | } HTTPMethod; 14 | 15 | #endif /* _HTTP_Method_H_ */ 16 | -------------------------------------------------------------------------------- /Banks.h: -------------------------------------------------------------------------------- 1 | #ifndef _BANKS_H 2 | #define _BANKS_H 3 | 4 | #include 5 | #include "config.h" 6 | #include "SparkStructures.h" 7 | 8 | enum eMode_t {MODE_PRESETS, MODE_EFFECTS, MODE_BANKS, MODE_CONFIG, MODE_TUNER, MODE_BYPASS, MODE_MESSAGE, MODE_LEVEL}; 9 | 10 | #define JSON_SIZE 96 * (NUM_BANKS+1) 11 | 12 | typedef struct { 13 | char bank_name[STR_LEN+1] = ""; 14 | uint8_t start_chan = 255; 15 | } tBankConfig; 16 | 17 | typedef struct { 18 | uint8_t active_bank = 0; 19 | uint8_t active_chan = 0; 20 | eMode_t active_mode = MODE_PRESETS; 21 | } tPedalCfg; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /mimetable.h: -------------------------------------------------------------------------------- 1 | #ifndef __MIMETABLE_H__ 2 | #define __MIMETABLE_H__ 3 | 4 | 5 | namespace mime 6 | { 7 | 8 | enum type 9 | { 10 | html, 11 | htm, 12 | css, 13 | txt, 14 | js, 15 | json, 16 | png, 17 | gif, 18 | jpg, 19 | ico, 20 | svg, 21 | ttf, 22 | otf, 23 | woff, 24 | woff2, 25 | eot, 26 | sfnt, 27 | xml, 28 | pdf, 29 | zip, 30 | gz, 31 | appcache, 32 | none, 33 | maxType 34 | }; 35 | 36 | struct Entry 37 | { 38 | const char endsWith[16]; 39 | const char mimeType[32]; 40 | }; 41 | 42 | 43 | extern const Entry mimeTable[maxType]; 44 | } 45 | 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /RingBuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef RingBuffer_h 2 | #define RingBuffer_h 3 | 4 | class RingBuffer 5 | { 6 | public: 7 | RingBuffer(); 8 | bool add(uint8_t b); 9 | bool get(uint8_t *b); 10 | bool set_at_index(int ind, uint8_t b); 11 | bool get_at_index(int ind, uint8_t *b); 12 | bool set_bit_at_index(int ind, uint8_t b); 13 | int get_len(); 14 | int get_pos(); 15 | bool is_empty(); 16 | void commit(); 17 | void drop(); 18 | void clear(); 19 | void dump(); 20 | void dump2(); 21 | void dump3(); 22 | private: 23 | static const int RB_BUFF_MAX = 2048; 24 | uint8_t rb[RB_BUFF_MAX]; 25 | int st, en, len, t_len; 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /ESPxWebFlMgrWpF.h: -------------------------------------------------------------------------------- 1 | // inline guard. Still broken by design? 2 | #ifndef ESPxWebFlMgrWpF_h 3 | #define ESPxWebFlMgrWpF_h 4 | 5 | static const char ESPxWebFlMgrWpFormIntro[] PROGMEM = 6 | R"==x==(
  15 | 16 | )==x=="; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /RequestHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef REQUESTHANDLER_H 2 | #define REQUESTHANDLER_H 3 | 4 | #include 5 | #include 6 | #include "mimetable.h" 7 | 8 | class RequestHandler { 9 | public: 10 | virtual ~RequestHandler() { } 11 | virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; } 12 | virtual bool canUpload(String uri) { (void) uri; return false; } 13 | virtual bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; } 14 | virtual void upload(WebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; } 15 | 16 | RequestHandler* next() { return _next; } 17 | void next(RequestHandler* r) { _next = r; } 18 | 19 | private: 20 | RequestHandler* _next = nullptr; 21 | 22 | protected: 23 | std::vector pathArgs; 24 | 25 | public: 26 | const String& pathArg(unsigned int i) { 27 | assert(i < pathArgs.size()); 28 | return pathArgs[i]; 29 | } 30 | }; 31 | 32 | #endif //REQUESTHANDLER_H 33 | -------------------------------------------------------------------------------- /mimetable.cpp: -------------------------------------------------------------------------------- 1 | #include "mimetable.h" 2 | #include "pgmspace.h" 3 | 4 | namespace mime 5 | { 6 | 7 | // Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules 8 | const Entry mimeTable[maxType] = 9 | { 10 | { ".html", "text/html" }, 11 | { ".htm", "text/html" }, 12 | { ".css", "text/css" }, 13 | { ".txt", "text/plain" }, 14 | { ".js", "application/javascript" }, 15 | { ".json", "application/json" }, 16 | { ".png", "image/png" }, 17 | { ".gif", "image/gif" }, 18 | { ".jpg", "image/jpeg" }, 19 | { ".ico", "image/x-icon" }, 20 | { ".svg", "image/svg+xml" }, 21 | { ".ttf", "application/x-font-ttf" }, 22 | { ".otf", "application/x-font-opentype" }, 23 | { ".woff", "application/font-woff" }, 24 | { ".woff2", "application/font-woff2" }, 25 | { ".eot", "application/vnd.ms-fontobject" }, 26 | { ".sfnt", "application/font-sfnt" }, 27 | { ".xml", "text/xml" }, 28 | { ".pdf", "application/pdf" }, 29 | { ".zip", "application/zip" }, 30 | { ".gz", "application/x-gzip" }, 31 | { ".appcache", "text/cache-manifest" }, 32 | { "", "application/octet-stream" } 33 | }; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /CRC32.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2013 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #include "CRC32.h" 9 | 10 | // Conditionally use pgm memory if it is available. 11 | 12 | #if defined(PROGMEM) 13 | #define FLASH_PROGMEM PROGMEM 14 | #define FLASH_READ_DWORD(x) (pgm_read_dword_near(x)) 15 | #else 16 | #define FLASH_PROGMEM 17 | #define FLASH_READ_DWORD(x) (*(uint32_t*)(x)) 18 | #endif 19 | 20 | 21 | static const uint32_t crc32_table[] FLASH_PROGMEM = { 22 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 23 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 24 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 25 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c 26 | }; 27 | 28 | 29 | CRC32::CRC32() 30 | { 31 | reset(); 32 | } 33 | 34 | 35 | void CRC32::reset() 36 | { 37 | _state = ~0L; 38 | } 39 | 40 | 41 | void CRC32::update(const uint8_t& data) 42 | { 43 | // via http://forum.arduino.cc/index.php?topic=91179.0 44 | uint8_t tbl_idx = 0; 45 | 46 | tbl_idx = _state ^ (data >> (0 * 4)); 47 | _state = FLASH_READ_DWORD(crc32_table + (tbl_idx & 0x0f)) ^ (_state >> 4); 48 | tbl_idx = _state ^ (data >> (1 * 4)); 49 | _state = FLASH_READ_DWORD(crc32_table + (tbl_idx & 0x0f)) ^ (_state >> 4); 50 | } 51 | 52 | 53 | uint32_t CRC32::finalize() const 54 | { 55 | return ~_state; 56 | } 57 | -------------------------------------------------------------------------------- /Issue_list.txt: -------------------------------------------------------------------------------- 1 | Based on Paul's HeltecToSpark9 and SparkBox 2 | -------------------------------------------------------- 3 | 4 | v0.42 5 | - 100% Heltec WIFI kit 32version 6 | - Battery scale initial calibration done 7 | - Optional true charging indicator 8 | 9 | v0.43 10 | - Battery display logic tweaked to handle all 3 build options 11 | 12 | v0.44 13 | - Tidy code. Prepare for BLE update 14 | 15 | v0.45 16 | - Initial BLE rough code 17 | - Throws errors on failed connects 18 | 19 | v0.46 20 | - General BLE release 21 | - Corrected crashes on failed connects 22 | - BT RSSI display working 23 | - Possible occasional unsync on reconnect 24 | 25 | v0.48 26 | - Merged SparkBox mods 27 | - Updated battery display 28 | 29 | v0.49 30 | - Merged SparkIO2 31 | - Works with app now 32 | - No disconnect ability 33 | 34 | v0.50 35 | - Merged SparkIO3 36 | - Works with app now 37 | - No disconnect ability 38 | 39 | v0.53 40 | - Fixed bug in SparkIO3 41 | - Merged with SparkTracker3 42 | - Smoothed out expression pedal noise nicely 43 | 44 | v0.55 45 | - Now only sends messages to connected devices 46 | - Sometimes will do a kind of double-disconnect when the app is removed. When this happens the app can not reconnectwithout a reboot. 47 | - Now shows a reconnect message when Spark is lost 48 | - No more bad blocks 49 | 50 | ToDo: 51 | - Find out why we get bad block messages sometimes -------------------------------------------------------------------------------- /SparkStructures.h: -------------------------------------------------------------------------------- 1 | #ifndef SparkStructures_h 2 | #define SparkStructures_h 3 | 4 | #define STR_LEN 40 5 | 6 | typedef struct { 7 | uint8_t curr_preset; 8 | uint8_t preset_num; 9 | char UUID[STR_LEN]; 10 | char Name[STR_LEN]; 11 | char Version[STR_LEN]; 12 | char Description[STR_LEN]; 13 | char Icon[STR_LEN]; 14 | float BPM; 15 | struct SparkEffects { 16 | char EffectName[STR_LEN]; 17 | bool OnOff; 18 | uint8_t NumParameters; 19 | float Parameters[10]; 20 | } effects[7]; 21 | uint8_t chksum; 22 | } SparkPreset; 23 | 24 | typedef struct { 25 | uint8_t param1; 26 | uint8_t param2; 27 | uint8_t param3; 28 | uint8_t param4; 29 | uint32_t param5; 30 | float val; 31 | char str1[STR_LEN]; 32 | char str2[STR_LEN]; 33 | bool onoff; 34 | } SparkMessage; 35 | 36 | 37 | /* 38 | SparkPreset my_preset{0x0,0x7f,"F00DF00D-FEED-0123-4567-987654321004","Paul Preset Test","0.7","Nothing Here","icon.png",120.000000,{ 39 | {"bias.noisegate", true, 2, {0.316873, 0.304245}}, 40 | {"Compressor", false, 2, {0.341085, 0.665754}}, 41 | {"Booster", true, 1, {0.661412}}, 42 | {"Bassman", true, 5, {0.768152, 0.491509, 0.476547, 0.284314, 0.389779}}, 43 | {"UniVibe", false, 3, {0.500000, 1.000000, 0.700000}}, 44 | {"VintageDelay", true, 4, {0.152219, 0.663314, 0.144982, 1.000000}}, 45 | {"bias.reverb", true, 7, {0.120109, 0.150000, 0.500000, 0.406755, 0.299253, 0.768478, 0.100000}} },0x00 }; 46 | */ 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /Spark.h: -------------------------------------------------------------------------------- 1 | #ifndef Spark_h 2 | #define Spark_h 3 | 4 | #include "SparkIO.h" 5 | 6 | // variables required to track spark state and also for communications generally 7 | unsigned int cmdsub; 8 | SparkMessage msg; 9 | SparkPreset preset; 10 | SparkPreset presets[6]; 11 | 12 | enum spark_status_values {SPARK_DISCONNECTED, SPARK_CONNECTED, SPARK_COMMUNICATING, SPARK_CHECKSUM, SPARK_SYNCING, SPARK_SYNCED}; 13 | enum spark_ui_updates {UI_NONE, UI_CUSTOM, UI_HARDWARE}; 14 | 15 | spark_status_values spark_state; 16 | spark_ui_updates ui_update_in_progress; 17 | unsigned long spark_ping_timer; 18 | 19 | bool spark_state_tracker_start(); 20 | bool update_spark_state(); 21 | void update_ui(); 22 | 23 | void change_comp_model(char *new_eff); 24 | void change_drive_model(char *new_eff); 25 | void change_amp_model(char *new_eff); 26 | void change_mod_model(char *new_eff); 27 | void change_delay_model(char *new_eff); 28 | 29 | void change_noisegate_onoff(bool onoff); 30 | void change_comp_onoff(bool onoff); 31 | void change_drive_onoff(bool onoff); 32 | void change_amp_onoff(bool onoff); 33 | void change_mod_onoff(bool onoff); 34 | void change_delay_onoff(bool onoff); 35 | void change_reverb_onoff(bool onoff); 36 | 37 | void change_noisegate_toggle(); 38 | void change_comp_toggle(); 39 | void change_drive_toggle(); 40 | void change_amp_toggle(); 41 | void change_mod_toggle(); 42 | void change_delay_toggle(); 43 | void change_reverb_toggle(); 44 | 45 | void change_noisegate_param(int param, float val); 46 | void change_comp_param(int param, float val); 47 | void change_drive_param(int param, float val); 48 | void change_amp_param(int param, float val); 49 | void change_mod_param(int param, float val); 50 | void change_delay_param(int param, float val); 51 | void change_reverb_param(int param, float val); 52 | 53 | void change_hardware_preset(int pres_num); 54 | void change_custom_preset(SparkPreset *preset, int pres_num); 55 | 56 | void tuner_on_off(bool on_off); 57 | 58 | #define AMP_GAIN 0 59 | #define AMP_TREBLE 1 60 | #define AMP_MID 2 61 | #define AMP_BASS 3 62 | #define AMP_MASTER 4 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /SimplePortal.h: -------------------------------------------------------------------------------- 1 | /* 2 | Простой менеджер WiFi для esp8266 для задания логина-пароля WiFi и режима работы 3 | GitHub: https://github.com/GyverLibs/SimplePortal 4 | 5 | AlexGyver, alex@alexgyver.ru 6 | https://alexgyver.ru/ 7 | MIT License 8 | 9 | Версии: 10 | v1.0 11 | v1.1 - совместимость с ESP32 12 | */ 13 | 14 | #ifndef _SimplePortal_h 15 | #define _SimplePortal_h 16 | 17 | #include 18 | #include "config.h" 19 | #include "ESPxWebFlMgr.h" 20 | #include 21 | 22 | #ifdef ESP8266 23 | #include 24 | #include 25 | #else 26 | #include 27 | #include "WebServer.h" 28 | #endif 29 | 30 | #ifdef SSD1306 31 | #include "SSD1306Wire.h" // https://github.com/ThingPulse/esp8266-oled-ssd1306 32 | #endif 33 | #ifdef SH1106 34 | #include "SH1106Wire.h" 35 | #endif 36 | #include "OLEDDisplayUi.h" 37 | #include "ESPxWebFlMgr.h" 38 | 39 | #ifdef SSD1306 40 | extern SSD1306Wire oled; 41 | #endif 42 | #ifdef SH1106 43 | extern SH1106Wire oled; 44 | #endif 45 | extern OLEDDisplayUi ui; 46 | 47 | extern ESPxWebFlMgr filemgr; 48 | 49 | extern bool wifi_connected; 50 | 51 | extern void refreshUI(); 52 | extern void doPushButtons(); 53 | extern void showMessage(const String &capText, const String &text1, const String &text2, const ulong msTimeout) ; 54 | 55 | #define SP_ERROR 0 56 | #define SP_SUBMIT 1 57 | #define SP_SWITCH_AP 2 58 | #define SP_SWITCH_LOCAL 3 59 | #define SP_EXIT 4 60 | #define SP_TIMEOUT 5 61 | 62 | struct PortalCfg { 63 | char SSID[32] = ""; 64 | char pass[32] = ""; 65 | uint8_t mode = WIFI_AP; // (1 WIFI_STA, 2 WIFI_AP) 66 | bool succeed = false; 67 | bool tried = false; 68 | }; 69 | 70 | extern PortalCfg portalCfg; 71 | 72 | void portalStart(); // startup the portal 73 | void portalStop(); // stop the portal 74 | bool portalTick(); // to call in a loop 75 | void portalRun(uint32_t prd = 60000); // blocking call 76 | byte portalStatus(); // status: 1 connect, 2 ap, 3 local, 4 exit, 5 timeout 77 | void listNetworks(); 78 | void SP_handleConnect(); 79 | void SP_handleAP(); 80 | void SP_handleLocal(); 81 | void SP_handleExit(); 82 | #endif 83 | -------------------------------------------------------------------------------- /CRC32.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2013 Christopher Baker 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | 8 | #pragma once 9 | 10 | 11 | #include "Arduino.h" 12 | 13 | 14 | /// \brief A class for calculating the CRC32 checksum from arbitrary data. 15 | /// \sa http://forum.arduino.cc/index.php?topic=91179.0 16 | class CRC32 17 | { 18 | public: 19 | /// \brief Initialize an empty CRC32 checksum. 20 | CRC32(); 21 | 22 | /// \brief Reset the checksum claculation. 23 | void reset(); 24 | 25 | /// \brief Update the current checksum caclulation with the given data. 26 | /// \param data The data to add to the checksum. 27 | void update(const uint8_t& data); 28 | 29 | /// \brief Update the current checksum caclulation with the given data. 30 | /// \tparam Type The data type to read. 31 | /// \param data The data to add to the checksum. 32 | template 33 | void update(const Type& data) 34 | { 35 | update(&data, 1); 36 | } 37 | 38 | /// \brief Update the current checksum caclulation with the given data. 39 | /// \tparam Type The data type to read. 40 | /// \param data The array to add to the checksum. 41 | /// \param size Size of the array to add. 42 | template 43 | void update(const Type* data, size_t size) 44 | { 45 | size_t nBytes = size * sizeof(Type); 46 | const uint8_t* pData = (const uint8_t*)data; 47 | 48 | for (size_t i = 0; i < nBytes; i++) 49 | { 50 | update(pData[i]); 51 | } 52 | } 53 | 54 | /// \returns the caclulated checksum. 55 | uint32_t finalize() const; 56 | 57 | /// \brief Calculate the checksum of an arbitrary data array. 58 | /// \tparam Type The data type to read. 59 | /// \param data A pointer to the data to add to the checksum. 60 | /// \param size The size of the data to add to the checksum. 61 | /// \returns the calculated checksum. 62 | template 63 | static uint32_t calculate(const Type* data, size_t size) 64 | { 65 | CRC32 crc; 66 | crc.update(data, size); 67 | return crc.finalize(); 68 | } 69 | 70 | private: 71 | /// \brief The internal checksum state. 72 | uint32_t _state = ~0L; 73 | 74 | }; 75 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H 2 | #define _CONFIG_H 3 | // 4 | // 5 | // 6 | // Comment this line out for a production build 7 | #define DEBUG_ON 8 | // 9 | // Battery charge function defines. Please uncomment just one. 10 | // You have no mods to monitor the battery, so it will show empty 11 | //#define BATT_CHECK_0 12 | // 13 | // You are monitoring the battery via a 2:1 10k/10k resistive divider to GPIO23 14 | // You can see an accurate representation of the remaining battery charge and a kinda-sorta 15 | // indicator of when the battery is charging. Maybe. 16 | //#define BATT_CHECK_1 17 | // 18 | // You have the battery monitor mod described above AND you have a connection between the 19 | // CHRG pin of the charger chip and GPIO 33. Go you! Now you have a guaranteed charge indicator too. 20 | #define BATT_CHECK_2 21 | // 22 | // Expression pedal define. Comment this out if you DO NOT have the expression pedal mod 23 | #define EXPRESSION_PEDAL 24 | // 25 | // Dump preset define. Comment out if you'd prefer to not see so much text output 26 | //#define DUMP_ON 27 | // 28 | // Uncomment for better Bluetooth compatibility with Android devices 29 | //#define CLASSIC 30 | // 31 | // Uncomment when using a Heltec module as their implementation doesn't support setMTU() 32 | #define HELTEC_WIFI 33 | // 34 | // Choose and uncomment the type of OLED display used: 0.96" SSD1306 or 1.3" SH1106 35 | #define SSD1306 36 | //#define SH1106 37 | // 38 | // Uncomment if two-colour OLED screens are used. Offsets some text and alternate tuner 39 | //#define TWOCOLOUR 40 | // 41 | // Uncomment if you don't want the pedal to sleep to save power, this also prevents low-battery sleep 42 | #define NOSLEEP 43 | // 44 | // When adjusting the level of effects, always start with Master level settings. Comment this line out if you like it to remember your last choice 45 | #define RETURN_TO_MASTER 46 | // 47 | // Logical level of a button being pressed. If your buttons connect to GND, then comment this setting out. 48 | // This setting also affects Pull-up/down, and waking source settings. 49 | #define ACTIVE_HIGH 50 | // 51 | // How many pieces do you wish? 52 | #define NUM_BANKS 12 53 | // 54 | // How many switches do we have 55 | #define NUM_SWITCHES 4 56 | // 57 | // Uncomment if you have leds indicating selected channel 58 | //#define LEDS_USED 59 | // 60 | // GPIOs of connected leds 61 | #ifdef LEDS_USED 62 | const uint8_t ledPins[]{14,27,26,25}; 63 | #endif 64 | // 65 | // Are we using the normal or alternate IO for measuring the charging state? 66 | //#define ALTERNATE_CHARGE_AIN 67 | 68 | // GPIOs of the buttons in your setup in the form of switchPins[]{GPIO_for_button1, GPIO_for_button2, GPIO_for_button3, GPIO_for_button4, ... } 69 | const uint8_t switchPins[]{17,5,18,23}; // Switch gpio numbers (for those who already has built a pedal with these pins) 70 | //const uint8_t switchPins[]{33,14,27,26}; // PH EDIT 71 | //const uint8_t switchPins[]{25,26,27,14}; // Switch gpio numbers (recommended for those who is building a pedal, these pins allow deep sleep) 72 | // 73 | // Startup splash animation 74 | #define ANIMATION_1 75 | //#define ANIMATION_2 76 | // 77 | // 78 | #define SP_AP_NAME "SparkBox" // WiFi Access Point (AP) name 79 | #define SP_AP_IP 192,168,4,1 // IP Address of the web page for setting up WiFi credentials 80 | // 81 | #endif 82 | -------------------------------------------------------------------------------- /SparkComms.h: -------------------------------------------------------------------------------- 1 | #ifndef SparkComms_h 2 | #define SparkComms_h 3 | 4 | #ifdef CLASSIC 5 | #include "BluetoothSerial.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #else 11 | #include "NimBLEDevice.h" 12 | #endif 13 | 14 | #include "RingBuffer.h" 15 | 16 | 17 | // Functions for logging changes for any UI updates 18 | // Spark amp 19 | #define SPK 0 20 | // Spark app 21 | #define APP 1 22 | // Buetooth MIDI controller 23 | #define BLE_MIDI 2 24 | // USB Host MIDI 25 | #define USB_MIDI 3 26 | // Serial / DIN MIDI 27 | #define SER_MIDI 4 28 | // Bluetooth app MIDI 29 | //#define NOTHING 30 | 31 | 32 | #define NUM_CONNS 5 33 | 34 | #define TO 0 35 | #define FROM 1 36 | #define STATUS 2 37 | 38 | bool conn_status[NUM_CONNS]; 39 | unsigned long conn_last_changed[3][NUM_CONNS]; 40 | 41 | void set_conn_status_connected(int connection); 42 | void set_conn_status_disconnected(int connection); 43 | void set_conn_received(int connection); 44 | void set_conn_sent(int connection); 45 | 46 | 47 | #define BLE_BUFSIZE 5000 48 | 49 | #define C_SERVICE "ffc0" 50 | #define C_CHAR1 "ffc1" 51 | #define C_CHAR2 "ffc2" 52 | 53 | #define S_SERVICE "ffc0" 54 | #define S_CHAR1 "ffc1" 55 | #define S_CHAR2 "ffc2" 56 | 57 | #define MIDI_SERVICE "03b80e5a-ede8-4b33-a751-6ce34ec4c700" 58 | #define MIDI_CHAR "7772e5db-3868-4112-a1a9-f2669d106bf3" 59 | 60 | #define PEDAL_SERVICE "03b80e5a-ede8-4b33-a751-6ce34ec4c700" 61 | #define PEDAL_CHAR "7772e5db-3868-4112-a1a9-f2669d106bf3" 62 | 63 | //#define SPARK_BT_NAME "Spark MINI BLE" 64 | #define SPARK_BT_NAME "Spark 40 Audio" 65 | 66 | #define MAX_SCAN_COUNT 2 67 | 68 | bool connect_to_all(); 69 | void connect_spark(); 70 | #ifdef BLE_CONTROLLER 71 | void connect_pedal(); 72 | #endif 73 | 74 | bool sp_available(); 75 | bool app_available(); 76 | uint8_t sp_read(); 77 | uint8_t app_read(); 78 | void sp_write(byte *buf, int len); 79 | void app_write(byte *buf, int len); 80 | int ble_getRSSI(); 81 | 82 | #ifdef CLASSIC 83 | BluetoothSerial *bt; 84 | #endif 85 | 86 | bool is_ble; 87 | 88 | bool ble_app_connected; 89 | bool classic_app_connected; 90 | 91 | #ifdef BLE_CONTROLLER 92 | bool connected_pedal; 93 | bool found_pedal; 94 | #endif 95 | 96 | bool connected_sp; 97 | bool found_sp; 98 | bool got_param_callback; 99 | 100 | BLEServer *pServer; 101 | BLEService *pService; 102 | BLECharacteristic *pCharacteristic_receive; 103 | BLECharacteristic *pCharacteristic_send; 104 | 105 | #ifdef BLE_APP_MIDI 106 | BLEService *pServiceMIDI; 107 | BLECharacteristic *pCharacteristicMIDI; 108 | #endif 109 | 110 | BLEAdvertising *pAdvertising; 111 | 112 | BLEScan *pScan; 113 | BLEScanResults pResults; 114 | BLEAdvertisedDevice device; 115 | 116 | BLEClient *pClient_sp; 117 | BLERemoteService *pService_sp; 118 | BLERemoteCharacteristic *pReceiver_sp; 119 | BLERemoteCharacteristic *pSender_sp; 120 | BLERemoteDescriptor* p2902_sp; 121 | BLEAdvertisedDevice *sp_device; 122 | 123 | #ifdef BLE_CONTROLLER 124 | BLEClient *pClient_pedal; 125 | BLERemoteService *pService_pedal; 126 | BLERemoteCharacteristic *pReceiver_pedal; 127 | BLERemoteCharacteristic *pSender_pedal; 128 | BLERemoteDescriptor* p2902_pedal; 129 | BLEAdvertisedDevice *pedal_device; 130 | #endif 131 | 132 | RingBuffer ble_in; 133 | RingBuffer ble_app_in; 134 | 135 | #ifdef BLE_CONTROLLER 136 | RingBuffer midi_in; 137 | #endif 138 | 139 | #ifdef BLE_APP_MIDI 140 | RingBuffer ble_midi_in; 141 | #endif 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /RingBuffer.ino: -------------------------------------------------------------------------------- 1 | #include "RingBuffer.h" 2 | 3 | // 4 | // RingBuffer class 5 | // 6 | 7 | /* Implementation of a ring buffer - with a difference 8 | * New data is written to a temp area with add_to_buffer() and committed into the buffer once done - commit() 9 | * Commited data is read from the head of the buffer with get_from_buffer() 10 | * Data in this area can be read and updated by index - get_at_index() and set_at_index() 11 | * 12 | * If the temp data is not required it can be ignored using drop() rather than commit() 13 | * 14 | * +----------------------------------++----------------------------------------------------------------------+ 15 | * | 0 | 1 | 2 | 3 | 4 | 5 | 6 || 7 | 8 | 9 | 10 | 11 | 12 | 13 || 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 16 | * +----------------------------------++----------------------------------------------------------------------+ 17 | * ^ ^ ^ ^ 18 | * st ---------- len ------------+ +----------- t_len --------- en 19 | * 20 | * committed data temporary data empty 21 | */ 22 | 23 | RingBuffer::RingBuffer() { 24 | st = 0; 25 | en = 0; 26 | len = 0; 27 | t_len = 0; 28 | } 29 | 30 | bool RingBuffer::add(uint8_t b) { 31 | if (len + t_len < RB_BUFF_MAX) { 32 | rb[en] = b; 33 | t_len++; 34 | en++; 35 | if (en >= RB_BUFF_MAX) en = 0; 36 | return true; 37 | } 38 | else 39 | return false; 40 | } 41 | 42 | bool RingBuffer::get(uint8_t *b) { 43 | if (len > 0) { 44 | *b = rb[st]; 45 | len--; 46 | st++; 47 | if (st >= RB_BUFF_MAX) st = 0; 48 | return true; 49 | } 50 | else 51 | return false; 52 | } 53 | 54 | // set a value at a location in the temp area 55 | bool RingBuffer::set_at_index(int ind, uint8_t b) { 56 | if (ind >= 0 && ind < t_len) { 57 | rb[(st+len+ind) % RB_BUFF_MAX] = b; 58 | return true; 59 | } 60 | else 61 | return false; 62 | } 63 | 64 | // get a value from a location in the temp area 65 | bool RingBuffer::get_at_index(int ind, uint8_t *b) { 66 | if (ind >= 0 && ind < t_len) { 67 | *b = rb[(st+len+ind) % RB_BUFF_MAX]; 68 | return true; 69 | } 70 | else 71 | return false; 72 | } 73 | 74 | bool RingBuffer::set_bit_at_index(int ind, uint8_t b) { 75 | if (ind >= 0 && ind < t_len) { 76 | rb[(st+len+ind) % RB_BUFF_MAX] |= b; 77 | return true; 78 | } 79 | else 80 | return false; 81 | } 82 | 83 | int RingBuffer::get_len() { // total temp len 84 | return t_len; 85 | } 86 | 87 | int RingBuffer::get_pos() { // current position 88 | return t_len; 89 | } 90 | 91 | void RingBuffer::commit() { 92 | len += t_len; 93 | t_len = 0; 94 | } 95 | 96 | void RingBuffer::drop() { 97 | en = st + len; 98 | t_len = 0; 99 | } 100 | 101 | void RingBuffer::clear() { 102 | en = st; 103 | len = 0; 104 | } 105 | 106 | bool RingBuffer::is_empty() { 107 | return (len == 0); 108 | } 109 | 110 | void RingBuffer::dump() { 111 | int i; 112 | 113 | for (i=0; i 11 | 12 | 13 | 17 |
18 |

WiFi settings

19 |
20 | 21 | 22 | 23 |
24 |
Scanning networks...
25 |

Switch WiFi mode

26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 | 52 | )rawliteral"; 53 | 54 | static bool _SP_started = false; 55 | static byte _SP_status = 0; 56 | PortalCfg portalCfg; 57 | String networks = ""; 58 | 59 | void SP_handleConnect() { 60 | strcpy(portalCfg.SSID, _SP_server.arg("ssid").c_str()); 61 | strcpy(portalCfg.pass, _SP_server.arg("pass").c_str()); 62 | portalCfg.mode = WIFI_STA; 63 | _SP_status = 1; 64 | } 65 | 66 | void SP_handleList() { 67 | listNetworks(); 68 | _SP_server.send(200, "text/html", networks ); 69 | showMessage("WiFi Setup", "Submit", "Credentials", 0); 70 | } 71 | 72 | void SP_handleAP() { 73 | portalCfg.mode = WIFI_AP; 74 | _SP_status = 2; 75 | } 76 | 77 | void SP_handleLocal() { 78 | portalCfg.mode = WIFI_STA; 79 | _SP_status = 3; 80 | } 81 | 82 | void SP_handleExit() { 83 | _SP_status = 4; 84 | } 85 | 86 | void portalStart() { 87 | WiFi.softAPdisconnect(); 88 | delay(1); 89 | WiFi.disconnect(); 90 | delay(1); 91 | IPAddress apIP(SP_AP_IP); 92 | IPAddress subnet(255, 255, 255, 0); 93 | WiFi.mode(WIFI_AP); 94 | delay(1); 95 | WiFi.softAPConfig(apIP, apIP, subnet); 96 | delay(1); 97 | WiFi.softAP(SP_AP_NAME); 98 | delay(1); 99 | _SP_dnsServer.start(53, "*", apIP); 100 | _SP_server.onNotFound([]() { 101 | listNetworks(); 102 | _SP_server.send(200, "text/html", SP_connect_page); 103 | }); 104 | _SP_server.on("/connect", HTTP_POST, SP_handleConnect); 105 | _SP_server.on("/ap", HTTP_POST, SP_handleAP); 106 | _SP_server.on("/local", HTTP_POST, SP_handleLocal); 107 | _SP_server.on("/exit", HTTP_POST, SP_handleExit); 108 | _SP_server.on("/list", HTTP_POST, SP_handleList); 109 | _SP_server.begin(); 110 | _SP_started = true; 111 | _SP_status = 0; 112 | } 113 | 114 | void portalStop() { 115 | WiFi.softAPdisconnect(); 116 | _SP_server.stop(); 117 | _SP_dnsServer.stop(); 118 | _SP_started = false; 119 | } 120 | 121 | bool portalTick() { 122 | int remainingTimeBudget = ui.update(); 123 | static bool wifi_conn_old = false; 124 | //filemgr.handleClient(); 125 | // Process user input 126 | // doPushButtons(); 127 | wifi_connected = ( WiFi.softAPgetStationNum() > 0 ); 128 | if (wifi_connected != wifi_conn_old){ 129 | if (wifi_connected) { 130 | IPAddress apIP(SP_AP_IP); 131 | showMessage("Connected AP", "Open Site:", "http://" + apIP.toString(), 0); 132 | } else { 133 | showMessage("AP Mode", "Connect to AP", "\"" + (String)SP_AP_NAME + "\"", 0); 134 | } 135 | wifi_conn_old = wifi_connected; 136 | } 137 | refreshUI(); 138 | if (_SP_started) { 139 | _SP_dnsServer.processNextRequest(); 140 | _SP_server.handleClient(); 141 | 142 | if (_SP_status) { 143 | portalStop(); 144 | return 1; 145 | } 146 | } 147 | return 0; 148 | } 149 | 150 | void portalRun(uint32_t prd) { 151 | uint32_t tmr = millis(); 152 | portalStart(); 153 | while (!portalTick()) { 154 | if (millis() - tmr > prd) { 155 | _SP_status = 5; 156 | portalStop(); 157 | break; 158 | } 159 | } 160 | } 161 | 162 | byte portalStatus() { 163 | return _SP_status; 164 | } 165 | 166 | void listNetworks() { 167 | // scan for nearby networks: 168 | networks = ""; 169 | DEBUG("**WiFi** Scan Networks"); 170 | int numSsid = WiFi.scanNetworks(); 171 | if (numSsid == -1) { 172 | DEBUG("**WiFi** Couldn't get a wifi connection"); 173 | while (true); 174 | } 175 | 176 | // print the list of networks seen: 177 | DEB("**WiFi** number of available networks:"); 178 | DEBUG(numSsid); 179 | 180 | // print the network number and name for each network found: 181 | for (int thisNet = 0; thisNet < numSsid; thisNet++) { 182 | DEB(thisNet); 183 | DEB(") "); 184 | DEBUG(WiFi.SSID(thisNet)); 185 | networks += "" + (String)(WiFi.SSID(thisNet)) + "
"; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /ESPxWebFlMgr.h: -------------------------------------------------------------------------------- 1 | // inline guard. Did I mention that c/c++ is broken by design? 2 | #ifndef ESPxWebFlMgr_h 3 | #define ESPxWebFlMgr_h 4 | 5 | #define DEBUG_ON 6 | 7 | #ifndef DEB 8 | #ifdef DEBUG_ON 9 | // found this hint with __VA_ARGS__ on the web, it accepts different sets of arguments /Copych 10 | #define DEB(...) Serial.print(__VA_ARGS__) 11 | #define DEBUG(...) Serial.println(__VA_ARGS__) 12 | #else 13 | #define DEB(...) 14 | #define DEBUG(...) 15 | #endif 16 | #endif 17 | /* 18 | Changes 19 | V1.03 20 | x removed all SPIFFS from ESP32 version, switched fully to LittleFS 21 | x fixed rename+delete for ESP32+LittleFS (added /") 22 | 23 | V1.02 24 | x fixed the way to select the file system by conditional defines 25 | 26 | V1.01 27 | + added file name progress while uploading 28 | x fixed error in ZIP file structure (zip.bitflags needs a flag) 29 | 30 | V1.00 31 | + out of V0.9998... 32 | + ESP8266: LittleFS is default 33 | + javascript: added "msgline();" 34 | + javascript: added "Loading..." as a not-working-hint to show that Javascript is disabled 35 | + cleaning up the "/"-stuff (from SPIFF with leading "/" to LittleFS without...) 36 | + Warning: esp8266 2.7.4 has an error in mime::getContentType(path) for .TXT. Fix line 65 is { kTxtSuffix, kTxt }, 37 | + review of "edit file", moved some stuff to ESPxWebFlMgrWpF.h 38 | */ 39 | 40 | #include 41 | #include 42 | 43 | // file system default for esp8266 is LittleFS, for ESP32 it is SPIFFS (no time to check...) 44 | 45 | #ifdef ESP8266 46 | #include 47 | #include 48 | #include 49 | // 50 | #include "LittleFS.h" 51 | #define ESPxWebFlMgr_FileSystem LittleFS 52 | 53 | #endif 54 | 55 | #ifdef ESP32 56 | #include 57 | #include "WebServer.h" 58 | #include "RequestHandlersImpl.h" 59 | #include "mimetable.h" 60 | #include 61 | #include 62 | #define ESPxWebFlMgr_FileSystem LittleFS 63 | #endif 64 | 65 | 66 | #ifndef ESPxWebFlMgr_FileSystem 67 | #pragma message ("ESPxWebFlMgr_FileSystem not defined.") 68 | #endif 69 | 70 | /* undefine this to save about 10k code space. 71 | it requires to put the files from "/filemanager" into the FS. No free lunch. 72 | */ 73 | #define fileManagerServerStaticsInternally 74 | 75 | // will show the Edit-Button for every file type, even binary and such. 76 | //#define fileManagerEditEverything 77 | 78 | class ESPxWebFlMgr { 79 | private: 80 | word _Port ; 81 | String _curdir = "/"; 82 | #ifdef ESP8266 83 | ESP8266WebServer * fileManager = NULL; 84 | #endif 85 | #ifdef ESP32 86 | WebServer * fileManager = NULL; 87 | #endif 88 | bool _ViewSysFiles = false; 89 | String _SysFileStartPattern = "/.u"; 90 | File fsUploadFile; 91 | String _backgroundColor = "dimgray"; 92 | 93 | void fileManagerNotFound(void); 94 | String dispIntDotted(size_t i); 95 | String dispFileString(size_t fs); 96 | String CheckFileNameLengthLimit(String fn); 97 | 98 | // the webpage 99 | void fileManagerIndexpage(void); 100 | void fileManagerJS(void); 101 | void fileManagerCSS(void); 102 | void fileManagerGetBackGround(void); 103 | String extractAttr(const String &attr, const String &fName); 104 | 105 | // javascript xmlhttp includes 106 | String colorline(int i); 107 | String dircolorline(int i); 108 | String escapeHTMLcontent(String html); 109 | void fileManagerFileListInsert(void); 110 | void fileManagerFileEditorInsert(void); 111 | boolean allowAccessToThisFile(const String filename); 112 | void fileManagerCommandExecutor(void); 113 | void fileManagerReceiverOK(void); 114 | void fileManagerReceiver(void); 115 | 116 | // Zip-File uncompressed/stored 117 | void getAllFilesInOneZIP(void); 118 | int WriteChunk(const char* b, size_t l); 119 | 120 | // helper: fs.h from esp32 and esp8266 don't have a compatible solution 121 | // for getting a file list from a directory 122 | #ifdef ESP32 123 | #define Dir File 124 | #endif 125 | File nextFile(Dir &dir); 126 | File firstFile(Dir &dir); 127 | // and not to get this data about usage... 128 | size_t totalBytes(void); 129 | size_t usedBytes(void); 130 | 131 | public: 132 | ESPxWebFlMgr(word port); 133 | virtual ~ESPxWebFlMgr(); 134 | String getcurdir() { return _curdir; } 135 | void setcurdir(String curdirname) { _curdir = curdirname; } 136 | void begin(); 137 | void end(); 138 | virtual void handleClient(); 139 | 140 | // This must be called before the webpage is loaded in the browser... 141 | // must be a valid css color name, see https://en.wikipedia.org/wiki/Web_colors 142 | void setBackGroundColor(const String backgroundColor); 143 | 144 | void setViewSysFiles(bool vsf); 145 | bool getViewSysFiles(void); 146 | 147 | void setSysFileStartPattern(String sfsp); 148 | String getSysFileStartPattern(void); 149 | }; 150 | 151 | #endif 152 | 153 | /* 154 | History 155 | 156 | -- 2019-07-07 157 | + Renamed to ESPxWebFlMgr and made it work with esp32 and esp8266 158 | + separated file manager web page, "build script" to generate it 159 | 160 | -- 2019-07-06 161 | + "Download all files" creates a zip file from all files and downloads it 162 | + option to set background color 163 | - html5 fixes 164 | 165 | -- 2019-07-03 166 | + Public Release on https://github.com/holgerlembke/ESP8266WebFlMgr 167 | 168 | 169 | Things to do 170 | 171 | ?? unify file system access for SPIFFS, LittleFS and SDFS 172 | 173 | */ 174 | -------------------------------------------------------------------------------- /RequestHandlersImpl.h: -------------------------------------------------------------------------------- 1 | #ifndef REQUESTHANDLERSIMPL_H 2 | #define REQUESTHANDLERSIMPL_H 3 | 4 | #include "RequestHandler.h" 5 | #include "mimetable.h" 6 | #include "WString.h" 7 | #include "FS.h" 8 | 9 | using namespace mime; 10 | 11 | class FunctionRequestHandler : public RequestHandler { 12 | public: 13 | FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method) 14 | : _fn(fn) 15 | , _ufn(ufn) 16 | , _uri(uri) 17 | , _method(method) 18 | { 19 | int numParams = 0, start = 0; 20 | do { 21 | start = _uri.indexOf("{}", start); 22 | if (start > 0) { 23 | numParams++; 24 | start += 2; 25 | } 26 | } while (start > 0); 27 | pathArgs.resize(numParams); 28 | } 29 | 30 | bool canHandle(HTTPMethod requestMethod, String requestUri) override { 31 | if (_method != HTTP_ANY && _method != requestMethod) 32 | return false; 33 | 34 | if (_uri == requestUri) 35 | return true; 36 | 37 | size_t uriLength = _uri.length(); 38 | unsigned int pathArgIndex = 0; 39 | unsigned int requestUriIndex = 0; 40 | for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) { 41 | char uriChar = _uri[i]; 42 | char requestUriChar = requestUri[requestUriIndex]; 43 | 44 | if (uriChar == requestUriChar) 45 | continue; 46 | if (uriChar != '{') 47 | return false; 48 | 49 | i += 2; // index of char after '}' 50 | if (i >= uriLength) { 51 | // there is no char after '}' 52 | pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex); 53 | return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/' 54 | } 55 | else 56 | { 57 | char charEnd = _uri[i]; 58 | int uriIndex = requestUri.indexOf(charEnd, requestUriIndex); 59 | if (uriIndex < 0) 60 | return false; 61 | pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex); 62 | requestUriIndex = (unsigned int) uriIndex; 63 | } 64 | pathArgIndex++; 65 | } 66 | 67 | return requestUriIndex >= requestUri.length(); 68 | } 69 | 70 | bool canUpload(String requestUri) override { 71 | if (!_ufn || !canHandle(HTTP_POST, requestUri)) 72 | return false; 73 | 74 | return true; 75 | } 76 | 77 | bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override { 78 | (void) server; 79 | if (!canHandle(requestMethod, requestUri)) 80 | return false; 81 | 82 | _fn(); 83 | return true; 84 | } 85 | 86 | void upload(WebServer& server, String requestUri, HTTPUpload& upload) override { 87 | (void) server; 88 | (void) upload; 89 | if (canUpload(requestUri)) 90 | _ufn(); 91 | } 92 | 93 | protected: 94 | WebServer::THandlerFunction _fn; 95 | WebServer::THandlerFunction _ufn; 96 | String _uri; 97 | HTTPMethod _method; 98 | }; 99 | 100 | class StaticRequestHandler : public RequestHandler { 101 | public: 102 | StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header) 103 | : _fs(fs) 104 | , _uri(uri) 105 | , _path(path) 106 | , _cache_header(cache_header) 107 | { 108 | _isFile = fs.exists(path); 109 | log_v("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header); 110 | _baseUriLength = _uri.length(); 111 | } 112 | 113 | bool canHandle(HTTPMethod requestMethod, String requestUri) override { 114 | if (requestMethod != HTTP_GET) 115 | return false; 116 | 117 | if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) 118 | return false; 119 | 120 | return true; 121 | } 122 | 123 | bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override { 124 | if (!canHandle(requestMethod, requestUri)) 125 | return false; 126 | 127 | log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str()); 128 | 129 | String path(_path); 130 | 131 | if (!_isFile) { 132 | // Base URI doesn't point to a file. 133 | // If a directory is requested, look for index file. 134 | if (requestUri.endsWith("/")) 135 | requestUri += "index.htm"; 136 | 137 | // Append whatever follows this URI in request to get the file path. 138 | path += requestUri.substring(_baseUriLength); 139 | } 140 | log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile); 141 | 142 | String contentType = getContentType(path); 143 | 144 | // look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for 145 | // if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc... 146 | if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) { 147 | String pathWithGz = path + FPSTR(mimeTable[gz].endsWith); 148 | if(_fs.exists(pathWithGz)) 149 | path += FPSTR(mimeTable[gz].endsWith); 150 | } 151 | 152 | File f = _fs.open(path, "r"); 153 | if (!f) 154 | return false; 155 | 156 | if (_cache_header.length() != 0) 157 | server.sendHeader("Cache-Control", _cache_header); 158 | 159 | server.streamFile(f, contentType); 160 | return true; 161 | } 162 | 163 | static String getContentType(const String& path) { 164 | char buff[sizeof(mimeTable[0].mimeType)]; 165 | // Check all entries but last one for match, return if found 166 | for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) { 167 | strcpy_P(buff, mimeTable[i].endsWith); 168 | if (path.endsWith(buff)) { 169 | strcpy_P(buff, mimeTable[i].mimeType); 170 | return String(buff); 171 | } 172 | } 173 | // Fall-through and just return default type 174 | strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType); 175 | return String(buff); 176 | } 177 | 178 | protected: 179 | FS _fs; 180 | String _uri; 181 | String _path; 182 | String _cache_header; 183 | bool _isFile; 184 | size_t _baseUriLength; 185 | }; 186 | 187 | 188 | #endif //REQUESTHANDLERSIMPL_H 189 | -------------------------------------------------------------------------------- /SparkIO.h: -------------------------------------------------------------------------------- 1 | #ifndef SparkIO_h 2 | #define SparkIO_h 3 | 4 | #include "RingBuffer.h" 5 | #include "SparkStructures.h" 6 | #include "SparkComms.h" 7 | 8 | #define MAX_IO_BUFFER 2048 9 | 10 | uint8_t license_key[64]; 11 | bool is_spark_mini = false; 12 | 13 | // BLOCK INPUT CLASS 14 | class BlockIn 15 | { 16 | public: 17 | BlockIn() {}; 18 | void process(); 19 | virtual bool data_available(); 20 | virtual uint8_t data_read(); 21 | virtual void data_write(uint8_t *buf, int len); 22 | 23 | // processing received block 24 | uint8_t *blk_hdr; 25 | RingBuffer *rb; 26 | int rb_state = 0; 27 | int rb_len = 0; 28 | // passthru 29 | uint8_t io_buf[MAX_IO_BUFFER]; 30 | int io_pos = 0; 31 | int io_len = -1; 32 | int io_state = 0; 33 | bool pass_through = true; 34 | }; 35 | 36 | class SparkBlockIn: public BlockIn 37 | { 38 | public: 39 | SparkBlockIn() {}; 40 | bool data_available(); 41 | uint8_t data_read(); 42 | void data_write(uint8_t *buf, int len); 43 | void set(bool pass, RingBuffer *ring_buffer, uint8_t *hdr); 44 | }; 45 | 46 | class AppBlockIn: public BlockIn 47 | { 48 | public: 49 | AppBlockIn() {}; 50 | bool data_available(); 51 | uint8_t data_read(); 52 | void data_write(uint8_t *buf, int len); 53 | void set(bool pass, RingBuffer *ring_buffer, uint8_t *hdr); 54 | }; 55 | 56 | // CHUNK INPUT CLASS 57 | class ChunkIn 58 | { 59 | public: 60 | ChunkIn() {}; 61 | void process(); 62 | // processing received block 63 | RingBuffer *in_chunk; 64 | RingBuffer *in_message; 65 | bool *ok_to_send; 66 | 67 | int rc_state; 68 | bool in_message_bad; 69 | 70 | int rc_seq; 71 | int rc_cmd; 72 | int rc_sub; 73 | int rc_checksum; 74 | 75 | int rc_calc_checksum; 76 | 77 | bool rc_multi_chunk; 78 | int rc_data_pos; 79 | uint8_t rc_bitmask; 80 | int rc_bits; 81 | 82 | int rc_total_chunks; 83 | int rc_this_chunk; 84 | int rc_chunk_len; 85 | int rc_last_chunk; 86 | 87 | uint8_t *rec_seq; // last received sequence from app or amp 88 | }; 89 | 90 | class SparkChunkIn: public ChunkIn 91 | { 92 | public: 93 | SparkChunkIn() {}; 94 | void set(RingBuffer *chunks, RingBuffer *messages, bool *ok, uint8_t *seq); 95 | }; 96 | 97 | class AppChunkIn: public ChunkIn 98 | { 99 | public: 100 | AppChunkIn() {}; 101 | void set(RingBuffer *chunks, RingBuffer *messages, bool *ok, uint8_t *seq); 102 | }; 103 | 104 | // MESSAGE INPUT CLASS 105 | class MessageIn 106 | { 107 | public: 108 | MessageIn() {}; 109 | bool get_message(unsigned int *cmdsub, SparkMessage *msg, SparkPreset *preset); 110 | RingBuffer *in_message; 111 | 112 | void read_string(char *str); 113 | void read_prefixed_string(char *str); 114 | void read_onoff(bool *b); 115 | void read_float(float *f); 116 | void read_uint(uint8_t *b); 117 | void read_general_uint(uint32_t *b); 118 | void read_byte(uint8_t *b); 119 | }; 120 | 121 | class SparkMessageIn: public MessageIn 122 | { 123 | public: 124 | SparkMessageIn() {}; 125 | void set(RingBuffer *messages); 126 | }; 127 | 128 | class AppMessageIn: public MessageIn 129 | { 130 | public: 131 | AppMessageIn() {}; 132 | void set(RingBuffer *messages); 133 | }; 134 | 135 | // MESSAGE OUTPUT CLASS 136 | class MessageOut 137 | { 138 | public: 139 | MessageOut() {}; 140 | 141 | // creating messages to send 142 | void start_message(int cmdsub); 143 | void end_message(); 144 | void write_byte(byte b); 145 | void write_byte_no_chksum(byte b); 146 | 147 | void write_uint(byte b); 148 | void write_prefixed_string(const char *str); 149 | void write_long_string(const char *str); 150 | void write_string(const char *str); 151 | void write_float(float flt); 152 | void write_onoff(bool onoff); 153 | void write_uint32(uint32_t w); 154 | 155 | void create_preset(SparkPreset *preset); 156 | void turn_effect_onoff(char *pedal, bool onoff); 157 | void change_hardware_preset(uint8_t curr_preset, uint8_t preset_num); 158 | void change_effect(char *pedal1, char *pedal2); 159 | void change_effect_parameter(char *pedal, int param, float val); 160 | void get_serial(); 161 | void get_name(); 162 | void get_hardware_preset_number(); 163 | void get_preset_details(unsigned int preset); 164 | void get_checksum_info(); 165 | void get_firmware(); 166 | void save_hardware_preset(uint8_t curr_preset, uint8_t preset_num); 167 | void send_firmware_version(uint32_t firmware); 168 | void send_0x022a_info(byte v1, byte v2, byte v3, byte v4); 169 | void send_preset_number(uint8_t preset_h, uint8_t preset_l); 170 | void send_key_ack(); 171 | void send_serial_number(char *serial); 172 | void send_ack(unsigned int cmdsub); 173 | // trial message 174 | void tuner_on_off(bool onoff); 175 | 176 | RingBuffer *out_message; 177 | int cmd_base; 178 | int out_msg_chksum; 179 | }; 180 | 181 | class SparkMessageOut: public MessageOut 182 | { 183 | public: 184 | SparkMessageOut() {}; 185 | void set(RingBuffer *messages); 186 | }; 187 | 188 | class AppMessageOut: public MessageOut 189 | { 190 | public: 191 | AppMessageOut() {}; 192 | void set(RingBuffer *messages); 193 | }; 194 | 195 | // CHUNK INPUT CLASS 196 | class ChunkOut 197 | { 198 | public: 199 | ChunkOut() {}; 200 | void process(); 201 | 202 | void out_store(uint8_t b); 203 | // processing received block 204 | RingBuffer *out_chunk; 205 | RingBuffer *out_message; 206 | int chunk_size; 207 | 208 | uint8_t *rec_seq; 209 | 210 | uint8_t oc_cmd; 211 | uint8_t oc_sub; 212 | unsigned int oc_len; 213 | uint8_t oc_seq; 214 | 215 | uint8_t oc_bit_mask; 216 | int oc_bit_pos; 217 | uint8_t oc_checksum; 218 | }; 219 | 220 | class SparkChunkOut: public ChunkOut 221 | { 222 | public: 223 | SparkChunkOut() {}; 224 | void set(RingBuffer *chunks, RingBuffer *messages, uint8_t *seq); 225 | }; 226 | 227 | class AppChunkOut: public ChunkOut 228 | { 229 | public: 230 | AppChunkOut() {}; 231 | void set(RingBuffer *chunks, RingBuffer *messages, uint8_t *seq); 232 | }; 233 | 234 | // CHUNK INPUT CLASS 235 | class BlockOut 236 | { 237 | public: 238 | BlockOut() {}; 239 | void process(); 240 | virtual void data_write(uint8_t *buf, int len); 241 | 242 | RingBuffer *out_chunk; 243 | bool *ok_to_send; 244 | unsigned int last_sent_time; 245 | bool to_spark; 246 | 247 | int block_size; 248 | uint8_t *blk_hdr; 249 | 250 | uint8_t out_block[0xad]; 251 | int ob_pos; 252 | }; 253 | 254 | class SparkBlockOut: public BlockOut 255 | { 256 | public: 257 | SparkBlockOut() {}; 258 | void set(RingBuffer *chunks, uint8_t *hdr, bool *ok); 259 | void data_write(uint8_t *buf, int len); 260 | }; 261 | 262 | class AppBlockOut: public BlockOut 263 | { 264 | public: 265 | AppBlockOut() {}; 266 | void set(RingBuffer *chunks, uint8_t *hdr, bool *ok); 267 | void data_write(uint8_t *buf, int len); 268 | }; 269 | 270 | ///////////// 271 | 272 | 273 | void spark_start(bool passthru); 274 | 275 | void spark_process(); 276 | 277 | // processing received messages 278 | // bool spark_get_message(unsigned int *cmdsub, SparkMessage *msg, SparkPreset *preset); 279 | 280 | // sending data 281 | 282 | SparkBlockIn sp_bin; 283 | RingBuffer sp_in_chunk; 284 | SparkChunkIn sp_cin; 285 | RingBuffer sp_in_message; 286 | SparkMessageIn spark_msg_in; 287 | 288 | SparkMessageOut spark_msg_out; 289 | RingBuffer sp_out_message; 290 | SparkChunkOut sp_cout; 291 | RingBuffer sp_out_chunk; 292 | SparkBlockOut sp_bout; 293 | 294 | bool sp_ok_to_send; 295 | bool app_ok_to_send; 296 | 297 | uint8_t sp_rec_seq; 298 | uint8_t app_rec_seq; 299 | 300 | void app_process(); 301 | 302 | AppBlockIn app_bin; 303 | RingBuffer app_in_chunk; 304 | AppChunkIn app_cin; 305 | RingBuffer app_in_message; 306 | AppMessageIn app_msg_in; 307 | 308 | AppMessageOut app_msg_out; 309 | RingBuffer app_out_message; 310 | AppChunkOut app_cout; 311 | RingBuffer app_out_chunk; 312 | AppBlockOut app_bout; 313 | 314 | 315 | #endif 316 | 317 | -------------------------------------------------------------------------------- /WebServer.h: -------------------------------------------------------------------------------- 1 | /* 2 | WebServer.h - Dead simple web-server. 3 | Supports only one simultaneous client, knows how to handle GET and POST. 4 | 5 | Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) 21 | */ 22 | 23 | #define DEBUG_ON 24 | 25 | #ifndef DEB 26 | #ifdef DEBUG_ON 27 | // found this hint with __VA_ARGS__ on the web, it accepts different sets of arguments /Copych 28 | #define DEB(...) Serial.print(__VA_ARGS__) 29 | #define DEBUG(...) Serial.println(__VA_ARGS__) 30 | #else 31 | #define DEB(...) 32 | #define DEBUG(...) 33 | #endif 34 | #endif 35 | 36 | #ifndef WEBSERVER_H 37 | #define WEBSERVER_H 38 | 39 | #include 40 | #include 41 | #include 42 | #include "HTTP_Method.h" 43 | 44 | enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, 45 | UPLOAD_FILE_ABORTED }; 46 | enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE }; 47 | enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; 48 | 49 | #define HTTP_DOWNLOAD_UNIT_SIZE 1436 50 | 51 | #ifndef HTTP_UPLOAD_BUFLEN 52 | #define HTTP_UPLOAD_BUFLEN 1436 53 | #endif 54 | 55 | #define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request 56 | #define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive 57 | #define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed 58 | #define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection 59 | 60 | #define CONTENT_LENGTH_UNKNOWN ((size_t) -1) 61 | #define CONTENT_LENGTH_NOT_SET ((size_t) -2) 62 | 63 | class WebServer; 64 | 65 | typedef struct { 66 | HTTPUploadStatus status; 67 | String filename; 68 | String name; 69 | String type; 70 | size_t totalSize; // file size 71 | size_t currentSize; // size of data currently in buf 72 | uint8_t buf[HTTP_UPLOAD_BUFLEN]; 73 | } HTTPUpload; 74 | 75 | #include "RequestHandler.h" 76 | 77 | namespace fs { 78 | class FS; 79 | } 80 | 81 | class WebServer 82 | { 83 | public: 84 | WebServer(IPAddress addr, int port = 80); 85 | WebServer(int port = 80); 86 | virtual ~WebServer(); 87 | 88 | virtual void begin(); 89 | virtual void begin(uint16_t port); 90 | virtual void handleClient(); 91 | 92 | virtual void close(); 93 | void stop(); 94 | 95 | bool authenticate(const char * username, const char * password); 96 | void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") ); 97 | 98 | typedef std::function THandlerFunction; 99 | void on(const String &uri, THandlerFunction handler); 100 | void on(const String &uri, HTTPMethod method, THandlerFunction fn); 101 | void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); 102 | void addHandler(RequestHandler* handler); 103 | void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL ); 104 | void onNotFound(THandlerFunction fn); //called when handler is not assigned 105 | void onFileUpload(THandlerFunction fn); //handle file uploads 106 | 107 | String uri() { return _currentUri; } 108 | HTTPMethod method() { return _currentMethod; } 109 | virtual WiFiClient client() { return _currentClient; } 110 | HTTPUpload& upload() { return *_currentUpload; } 111 | 112 | String pathArg(unsigned int i); // get request path argument by number 113 | String arg(String name); // get request argument value by name 114 | String arg(int i); // get request argument value by number 115 | String argName(int i); // get request argument name by number 116 | int args(); // get arguments count 117 | bool hasArg(String name); // check if argument exists 118 | void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect 119 | String header(String name); // get request header value by name 120 | String header(int i); // get request header value by number 121 | String headerName(int i); // get request header name by number 122 | int headers(); // get header count 123 | bool hasHeader(String name); // check if header exists 124 | 125 | String hostHeader(); // get request host header if available or empty String if not 126 | 127 | // send response to the client 128 | // code - HTTP response code, can be 200 or 404 129 | // content_type - HTTP content type, like "text/plain" or "image/png" 130 | // content - actual content body 131 | void send(int code, const char* content_type = NULL, const String& content = String("")); 132 | void send(int code, char* content_type, const String& content); 133 | void send(int code, const String& content_type, const String& content); 134 | void send_P(int code, PGM_P content_type, PGM_P content); 135 | void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength); 136 | 137 | void enableCORS(boolean value = true); 138 | void enableCrossOrigin(boolean value = true); 139 | 140 | void setContentLength(const size_t contentLength); 141 | void sendHeader(const String& name, const String& value, bool first = false); 142 | void sendContent(const String& content); 143 | void sendContent_P(PGM_P content); 144 | void sendContent_P(PGM_P content, size_t size); 145 | 146 | static String urlDecode(const String& text); 147 | 148 | template 149 | size_t streamFile(T &file, const String& contentType) { 150 | _streamFileCore(file.size(), file.name(), contentType); 151 | return _currentClient.write(file); 152 | } 153 | 154 | protected: 155 | virtual size_t _currentClientWrite(const char* b, size_t l) { return _currentClient.write( b, l ); } 156 | virtual size_t _currentClientWrite_P(PGM_P b, size_t l) { return _currentClient.write_P( b, l ); } 157 | void _addRequestHandler(RequestHandler* handler); 158 | void _handleRequest(); 159 | void _finalizeResponse(); 160 | bool _parseRequest(WiFiClient& client); 161 | void _parseArguments(String data); 162 | static String _responseCodeToString(int code); 163 | bool _parseForm(WiFiClient& client, String boundary, uint32_t len); 164 | bool _parseFormUploadAborted(); 165 | void _uploadWriteByte(uint8_t b); 166 | int _uploadReadByte(WiFiClient& client); 167 | void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); 168 | bool _collectHeader(const char* headerName, const char* headerValue); 169 | 170 | void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType); 171 | 172 | String _getRandomHexString(); 173 | // for extracting Auth parameters 174 | String _extractParam(String& authReq,const String& param,const char delimit = '"'); 175 | 176 | struct RequestArgument { 177 | String key=""; 178 | String value=""; 179 | }; 180 | 181 | boolean _corsEnabled; 182 | WiFiServer _server; 183 | 184 | WiFiClient _currentClient; 185 | HTTPMethod _currentMethod; 186 | String _currentUri=""; 187 | uint8_t _currentVersion; 188 | HTTPClientStatus _currentStatus; 189 | unsigned long _statusChange; 190 | 191 | RequestHandler* _currentHandler; 192 | RequestHandler* _firstHandler; 193 | RequestHandler* _lastHandler; 194 | THandlerFunction _notFoundHandler; 195 | THandlerFunction _fileUploadHandler; 196 | 197 | int _currentArgCount; 198 | RequestArgument* _currentArgs; 199 | int _postArgsLen; 200 | RequestArgument* _postArgs; 201 | 202 | std::unique_ptr _currentUpload; 203 | 204 | int _headerKeysCount; 205 | RequestArgument* _currentHeaders; 206 | size_t _contentLength; 207 | String _responseHeaders=""; 208 | 209 | String _hostHeader=""; 210 | bool _chunked; 211 | 212 | String _snonce=""; // Store noance and opaque for future comparison 213 | String _sopaque=""; 214 | String _srealm=""; // Store the Auth realm between Calls 215 | 216 | }; 217 | 218 | 219 | #endif //ESP8266WEBSERVER_H 220 | -------------------------------------------------------------------------------- /UI.h: -------------------------------------------------------------------------------- 1 | // Defines 2 | #ifndef _UI_h 3 | #define _UI_h 4 | 5 | #include "config.h" 6 | #include "Banks.h" 7 | #include "SparkStructures.h" 8 | 9 | #ifdef SSD1306 10 | #include "SSD1306Wire.h" // https://github.com/ThingPulse/esp8266-oled-ssd1306 11 | #endif 12 | #ifdef SH1106 13 | #include "SH1106Wire.h" 14 | #endif 15 | #include "OLEDDisplayUi.h" // Include the UI lib 16 | 17 | #ifdef SSD1306 18 | SSD1306Wire oled(0x3c, 4, 15); // Default OLED Screen Definitions - ADDRESS, SDA, SCL 19 | #endif 20 | #ifdef SH1106 21 | SH1106Wire oled(0x3c, 4, 15); // or this line if you are using SSH1106 22 | #endif 23 | OLEDDisplayUi ui(&oled); // Create UI instance for the display (slightly advanced frame based GUI) 24 | 25 | #include "ESPxWebFlMgr.h" 26 | 27 | #define NUM_FRAMES 8 // How many UI frames we have 28 | #define CYCLE_MODES 2 // How many pedal modes (first N frames) switch in cycle when button 1 is long-pressed 29 | #define VBAT_AIN 32 // Vbat sense GPIO (2:1 divider) 30 | 31 | #ifdef ALTERNATE_CHARGE_AIN 32 | #define CHRG_AIN 35 // Charge pin sense (10k pull-up) // PH EDIT 33 | #else 34 | #define CHRG_AIN 33 // Charge pin sense GPIO (10k pull-up) 35 | #endif 36 | 37 | #define EXP_AIN 34 // Expression pedal input GPIO (a pot (usually 10-50kOhm) connected via an additional 1kOhm resistor to 3V3) 38 | #define BT_MAX_ATTEMPTS 5 // Bluetooth (re-)connection attempts before going to sleep 39 | #define MILLIS_PER_ATTEMPT 6000 // milliseconds per connection attempts, this is used when reconnecting, not quite as expected though 40 | #define WIFI_MAX_ATTEMPTS 10 //WiFi connection attempts before giving up 41 | 42 | #define HARD_PRESETS 24 // number of hard-coded presets in SparkPresets.h 43 | #define HW_PRESETS 5 // 4 hardware presets + 1 temporary in amp presets 44 | // 45 | #define ADC_COEFF 573 // Multiplier to get ADC value out of voltage 46 | 47 | #define BATTERY_0 3.69 48 | #define BATTERY_20 3.73 49 | #define BATTERY_40 3.8 50 | #define BATTERY_87 4.1 51 | #define BATTERY_100 4.17 // Theoretical max LiPo voltage is 4.20 but under under the smallest load I see 4.17 52 | 53 | #define BATTERY_OFF BATTERY_0 // Voltage when we decide to force the pedal to switch to stand-by mode 54 | #define BATTERY_FUDGE 3.70 // Fudge factor to speed up initial averaging calcs 55 | 56 | #define GAUGE_0 5 // Battery % when the meter is showing 0% 57 | #define GAUGE_100 95 // Battery % when the meter is showing 100% 58 | #define CHRG_LOW 3.47 // Charging voltage detection 59 | #define VBAT_NUM 10 // Number of vbat readings to average 60 | 61 | //GUI settings 62 | #define TRANSITION_TIME 350 // Frame transition time, ms 63 | #define BATT_WIDTH 26 // Battery icon width 64 | #define CONN_ICON_WIDTH 11 // Connection status icons width 65 | #define FX_ICON_WIDTH 18 // Exxects icons width 66 | #define STATUS_HEIGHT 16 // Status line height 67 | #define FRAME_TIMEOUT 3000 // (ms) to return to main UI from temporary UI frame 68 | 69 | #ifdef TWOCOLOUR 70 | // Two-colour displays 71 | #define X1 64 // Please wait, Version, Connecting, SparkBox, Reconnecting 72 | #define Y1 22 73 | #define Y2 47 74 | #define Y3 16 75 | #define Y4 41 76 | #define Y5 16 77 | #define tuner_share 5 78 | #define note_y 0 79 | #else 80 | 81 | // Single colour displays 82 | #define X1 64 // Please wait, Version, Connecting, SparkBox, Reconnecting 83 | #define Y1 16 84 | #define Y2 42 85 | #define Y3 16 // was 13 86 | #define Y4 41 // was 36 87 | #define Y5 0 88 | #define tuner_share 4 89 | #define note_y 16 90 | #endif 91 | 92 | // font aliases for quick modding 93 | //#define SMALL_FONT Lato_Hairline_11 94 | #define SMALL_FONT ArialMod_Plain_10 // It has some mods, making it look a bit better 95 | //#define SMALL_FONT ArialMT_Plain_10 96 | #define MEDIUM_FONT ArialMT_Plain_16 97 | #define BIG_FONT ArialMT_Plain_24 98 | //#define HUGE_FONT Roboto_Mono_Bold_52 99 | #define HUGE_FONT Roboto_Mono_Medium_52 100 | 101 | // Globals 102 | int vbat_result = 0; // For battery monitoring 103 | int express_ring_count = 0; 104 | int express_ring_sum = 0; 105 | int express_result = 0; // For expression pedal monitoring 106 | int old_exp_result = 0; 107 | float effect_volume = 0.0; 108 | int chrg_result = 0; // For charge state monitoring 109 | int attempt_count = 0; // Connection attempts counter 110 | int pendingBankNum = -1; 111 | int localBankNum = 0; 112 | String bankName = ""; // Name of the 4-presets bank to display in the BANKS mode 113 | uint8_t localPresetNum; // actual slot number on the pedal 114 | uint8_t remotePresetNum; // preset slot number on the amp 115 | int8_t pendingPresetNum = -1; 116 | unsigned long idleBankCounter = 0; 117 | const uint8_t RTC_pins[]{0,2,4,12,13,14,15,25,26,27,32,33,34,35,36,37,38,39}; // These are RTC enabled GPIOs of ESP32, this is hardware, and if you choose to connect buttons to at least one of this list, deep sleep will be enabled 118 | bool sw_RTC[NUM_SWITCHES]; 119 | int RTC_present = 0; // Number of RTC pins present in the config 120 | int RTC_1st = -1; 121 | // BUTTONS SECTION ==================================================================== 122 | const unsigned long longPressThreshold = 800; // the threshold (in milliseconds) before a long press is detected 123 | const unsigned long autoFireDelay = 100; // the threshold (in milliseconds) between clicks if autofire is enabled 124 | bool autoFireEnabled = false; // should the buttons generate continious clicks when pressed longer than a longPressThreshold 125 | const unsigned long debounceThreshold = 20; // the threshold (in milliseconds) for a button press to be confirmed (i.e. not "noise") 126 | uint8_t ActiveFlags = 0; // Write buttons states to one binary mask; it's global so that we can check it anywhere 127 | 128 | // UI SECTION ========================================================================= 129 | int scroller = 0; // Variable to keep scrolling offset 130 | String msgCaption = "", msgText = "", msgText1 = ""; 131 | bool tempUI = false; // If we are in the temporary frame which returns after a given timeout 132 | boolean isOLEDUpdate; // Flag OLED needs refresh 133 | unsigned long actual_timeout = FRAME_TIMEOUT; 134 | uint64_t timeToGoBack = 0, time_to_sleep = 0; 135 | 136 | // Presets section 137 | bool fxState[] = {false,false,false,false,false,false,false}; // Array to store FX's on/off state before total bypass is ON 138 | 139 | // Type for Coordinates of Fx Params 140 | typedef struct { 141 | int fxSlot; 142 | int fxNumber; 143 | } s_fx_coords; 144 | 145 | 146 | char str[STR_LEN]; // Used for processing Spark commands from amp 147 | char param_str[50]; // 148 | int param = -1; 149 | float val = 0.0; 150 | bool expression_target = false; // False = parameter change, true = effect on/off 151 | bool effectstate = false; // Current state of the effect controller by the expression pedal 152 | bool setting_modified = false; // Flag that user has modifed a setting 153 | bool inWifi = false; 154 | bool wifi_connected = false; 155 | String bankPresetFiles[4]; 156 | ulong loopTime; // millis per loop (performance measure) 157 | 158 | 159 | // Yeah, we need to maintain this list ((( 160 | 161 | const char spark_noisegates[][STR_LEN+1]{"bias.noisegate"}; 162 | const char spark_compressors[][STR_LEN+1]{"BassComp","BBEOpticalComp","BlueComp","Compressor","JH.Vox846","LA2AComp"}; 163 | const char spark_drives[][STR_LEN+1]{"BassBigMuff","Booster","DistortionTS9","Fuzz","GuitarMuff","KlonCentaurSilver","MaestroBassmaster", 164 | "Overdrive","ProCoRat","SABdriver","TrebleBooster"}; 165 | const char spark_amps[][STR_LEN+1]{"6505Plus","94MatchDCV2","AC Boost","Acoustic","AcousticAmpV2","ADClean","AmericanHighGain","Bassman", 166 | "BE101","BluesJrTweed","Bogner","Checkmate","Deluxe65","EVH","FatAcousticV2","FlatAcoustic","GK800","Hammer500","Invader","JH.JTM45", 167 | "JH.SuperLead100","ODS50CN","OrangeAD30","OverDrivenJM45","OverDrivenLuxVerb","Plexi","Rectifier","RolandJC120","SLO100","Sunny3000", 168 | "SwitchAxeLead","Twin","TwoStoneSP50","W600","YJM100"}; 169 | const char spark_modulations[][STR_LEN+1]{"ChorusAnalog","Cloner","Flanger","GuitarEQ6","JH.VoodooVibeJr","MiniVibe","Phaser", 170 | "Tremolator","Tremolo","TremoloSquare","UniVibe","Vibrato01"}; 171 | const char spark_delays[][STR_LEN+1]{"DelayEchoFilt","DelayMono","DelayMultiHead","DelayRe201","DelayReverse","VintageDelay"}; 172 | const char spark_reverbs[][STR_LEN+1]{"bias.reverb"}; 173 | 174 | // knob, fx, param 175 | const char spark_knobs[7][5][12] { 176 | {"THRESHOLD","DECAY","N.GT-2","N.GT-3","N.GT-4"}, //noise gate 177 | {"COMP-0","COMP-1","COMP-2","COMP-3","COMP-4"}, //compressor 178 | {"DRIVE","DRV-1","DRV-2","DRV-3","DRV-4"}, //drive 179 | {"GAIN","TREBLE","MID","BASS","MASTER"}, //amp 180 | {"MODULATION","MOD-INTENS.","MOD-2","MOD-3","MOD-4"}, //modulation 181 | {"DELAY","DLY-1","DLY-2","DLY-3","BPM"}, //delay 182 | {"REVERB","REV-1","REV-2","REV-3","REV-4"} //reverb 183 | }; 184 | 185 | const char hints[NUM_FRAMES][NUM_SWITCHES][10]{ // [MODE],[ICON],[char_len] 186 | {"", "", "", ""}, // MODE_PRESETS 187 | {"", "", "", ""}, // MODE_EFFECTS 188 | {"rcv", "-", "send", "+"}, // MODE_BANKS 189 | {"rcv", "-", "send", "+"}, // MODE_CONFIG 190 | {"", "", "", ""}, // MODE_TUNER 191 | {"", "", "", ""}, // MODE_BYPASS 192 | {"yes", "no", "", ""}, // MODE_MESSAGE 193 | {"sel.", "-", "save", "+"}, // MODE_LEVEL 194 | }; 195 | 196 | const s_fx_coords knobs_order[] = { 197 | {3,0}, //gain 198 | {3,3}, //bass 199 | {3,2}, //mid 200 | {3,1}, //treble 201 | {3,4}, //master 202 | {4,0}, //modulation 203 | {5,0}, //delay 204 | {6,0}, //reverb 205 | {2,0} //drive 206 | }; 207 | 208 | const uint8_t knobs_number = 9; 209 | 210 | const uint8_t MAX_LEVEL = 100; // maximum level of effect, actual value in UI is level divided by 100 211 | int curKnob=4, curFx=3, curParam=4; 212 | int level = 0; 213 | String fxCaption=spark_knobs[curFx][curParam]; // Effect caption for displaying at the top of the screen when adjusting 214 | 215 | // forward declarations 216 | void doPushButtons(); 217 | void refreshUI(); 218 | void showMessage(const String &capText, const String &text1, const String &text2, const ulong msTimeout) ; 219 | void loadConfiguration(const String filename, tBankConfig (&conf)[NUM_BANKS+1]); 220 | #endif 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SparkBox V1.00 2 | SparkBox is a BT pedal for the Positive Grid Spark 40. Now with expression pedal input and a web interface for adding up to 12 banks for four presets. 3 | 4 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/SparkBox_final.jpg?raw=true) 5 | 6 | # Functions 7 | - Expression pedal input on GPIO34 for altering the current parameter or on/off switch 8 | - Select 12 banks for four presets from the pedal. 9 | - Uses BLE so that it can be used with music function of the Spark app 10 | - Allows connection of the app for full simultaneous control 11 | - Supports the most common DIY display types: SSD1306 and SH1106 12 | - Switch presets either on footswitch, app or Spark to update display 13 | - Switch on and off all four major effects dynamically 14 | - Graphically display the effect state on the display 15 | - Supports 4-button pedals 16 | - Battery level indicator on UI 17 | - Inter-operable with both conventional and Heltec ESP32 modules. Also tested on WEMOS LOLIN32 Lite with battery support. 18 | - Stand-by mode added to reduce power when disconnected - light or deep ESP32 sleep mode will be configured automatically depending on GPIOs of the buttons and logical levels choosen in your build. 19 | 20 | # Pedal functions 21 | - Long press (more than 1s) BUTTON 1 to switch between Effect mode and Preset mode 22 | - Long press BUTTON 3 to adjust current effect parameters: use BUTTONS 2 and 4 to decrement/increment, BUTTON 1 to cycle thru parameters and long press BUTTON 3 to save your edits back to the amp. 23 | - Now with remote guitar TUNER display! Long press BUTTONS 1 and 2 simultaneously, or turn it ON from the app or on the amp. 24 | - Bypass mode, invoked by long pressing BUTTONS 3 and 4 simultaneously, allows adjusting effect levels to match your raw pickup output. 25 | - Holding BUTTON 1 during boot will switch the pedal to WiFi mode. 26 | 27 | # Preset bank functions 28 | - Long pressing BUTTON 2 or BUTTON 4 invokes Bank Select menu, continue using BUTTONS 2 and 4 to decrease/increase bank number. If you stop scrolling for a few seconds, the selected bank will be uploaded to the amp. 29 | - On the first run your current set of hardware presets will be saved to the bank 000. 30 | - Added WiFi support w/file manager. Holding BUTTON 1 during boot will switch the pedal to WiFi mode. 31 | - Initially the pedal will launch a WiFi Access Point (AP), SSID is "SparkBox" by default. 32 | - Connect to this WiFi, using your mobile or PC or whatever. 33 | - Direct your browser to http://192.168.4.1 34 | - Submit your local WiFi network credentials (SSID and password), so the pedal could connect to your home WiFi. 35 | - If everything is done correctly, holding BUTTON 1 on boot again will connect your pedal to your home WiFi network, and the OLED display on the pedal will show the address of the filemanager site, so you can access the pedal from any device connected to your local wireless network. 36 | - Use the filemanager to upload your tone presets to the banks folders, 4 presets per folder. 37 | - Format of *.json preset files is the same as backed up files, used by the Spark app (usually you can find them zipped in your Dropbox). 38 | 39 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/web-file-manager.png?raw=true) 40 | 41 | # Arduino libraries and board versions 42 | 43 | Note: Currently there may be a problem with LittleFS compiling on the Arduino IDE 2.x. No issues on 1.x. 44 | 45 | Under Files->Preferences->Additional Boards Manager URLs, enter the following: 46 | - https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 47 | - https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.5/package_heltec_esp32_index.json 48 | 49 | Under Tools->Board->Board manager ensure that you have the following version: 50 | - Heltec ESP32 Dev-Boards 0.0.5 (Heltec - WiFi Kit 32) **OR** 51 | - ESP32 by Espressif 2.0.2 (ESP32) 52 | 53 | Under Tools->Manage Libraries ensure that you have the following libraries and versions: 54 | - ThingPulse SSD1306 driver 4.3.0 (ESP32) 55 | - NimBLE-Arduino 1.4.1 56 | - ArduinoJson 6.19.4 57 | - Heltec ESP 1.1.1 (only for Heltec boards) 58 | 59 | The program won't fit into a standard APP partition. The cure is easy though: 60 | - in Arduino IDE choose Tools->Partition Scheme->No-OTA(Large APP), or something that gives you around 2MB APP partition along with enough (also 2MB) of SPIFFS space, cause presets are stored there. 61 | - Note, that some boards in Arduino IDE don't have Partition Scheme settings, in this case it's recommended to choose some other ESP32 board (ESP Dev Module, Heltec WiFi Kit 32, WEMOS LOLIN32, etc.) which has this menu. 62 | 63 | # Compile options (in config.h) 64 | 65 | - **define CLASSIC** 66 | 67 | Uncomment this to use with Android devices that are happier with classic BT code. 68 | 69 | - **define BATT_CHECK_0** 70 | 71 | You have no mods to monitor the battery, so it will show empty (default). 72 | 73 | - **define BATT_CHECK_1** 74 | 75 | You are monitoring the battery via a 2:1 10k/10k resistive divider to GPIO23. 76 | You can see an accurate representation of the remaining battery charge and a kinda-sorta 77 | indicator of when the battery is charging. Maybe. 78 | 79 | - **define BATT_CHECK_2** 80 | 81 | You have the battery monitor mod described above AND you have a connection between the 82 | CHRG pin of the charger chip and GPIO 33. Go you! Now you have a guaranteed charge indicator too. 83 | 84 | - **define EXPRESSION_PEDAL** 85 | 86 | Expression pedal define. Comment this out if you DO NOT have the expression pedal mod. 87 | 88 | - **define DUMP_ON** 89 | 90 | Dump preset define. Comment out if you'd prefer to not see so much text output 91 | 92 | - **define CLASSIC** 93 | 94 | Uncomment for better Bluetooth compatibility with Android devices 95 | 96 | - **define HELTEC_WIFI** 97 | 98 | Uncomment when using a Heltec module as their implementation doesn't support setMTU() 99 | 100 | - **define SSD1306** OR **define SH1106** 101 | 102 | Choose and uncomment the type of OLED display that you use: 0.96" SSD1306 or 1.3" SH1106 103 | 104 | - **define TWOCOLOUR** 105 | 106 | Uncomment if two-colour OLED screens are used. Offsets some text and shows an alternate tuner 107 | 108 | - **#define STALE_NUMBER** 109 | 110 | Uncomment if you want preset number to scroll together with the name, otherwise it'll be locked in place 111 | 112 | - **define NOSLEEP** 113 | 114 | Uncomment if you'd prefer not to use the power-saving sleep modes 115 | 116 | - **define RETURN_TO_MASTER** 117 | 118 | When adjusting the level of effects, always start with Master level settings. Comment this line out if you like it to remember your last choice 119 | 120 | - **define ACTIVE_HIGH** 121 | 122 | Comment out if your buttons connect to the GND rather than to VCC, this will engage internal pullup and sleep routines also. If you make a decision on the build right now, it's recommended to connect buttons to VCC and to use ACTIVE_HIGH directive. 123 | 124 | - **define NUM_SWITCHES 4** 125 | 126 | How many switches do we have 127 | 128 | - **ALTERNATE_CHARGE_AIN** 129 | 130 | Use IO35 instead of IO33. Just for Paul. 131 | 132 | - **uint8_t switchPins[]{25,26,27,14};** 133 | 134 | GPIOs of the buttons in your setup in the form of switchPins[]{GPIO_for_button1, GPIO_for_button2, GPIO_for_button3, GPIO_for_button4, ... }. Note that GPIOs 25,26,27 and 14 are recommended ones if you want to get the least battery drain in the stand-by mode. 135 | 136 | - **define ANIMATION_1 or ANIMATION_2** 137 | 138 | Until one or the other is voted as a winner you can choose between two animations at startup. 139 | 140 | - **define LEDS_USED** 141 | 142 | Option visual aid to see which tone has been selected. Lights one of four LEDs on pins defined by ledPins[]. 143 | 144 | # Heltec module version 145 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/Dev_board.jpg?raw=true) 146 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/Charge_detect.jpg?raw=true) 147 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/SparkBox_Heltec_Exp_2.png?raw=true) 148 | 149 | # ESP32 version 150 | ![alt text](https://github.com/happyhappysundays/SparkBox_old/blob/main/Pictures/thumbnail_IMG_6791.jpg?raw=true) 151 | ![alt text](https://github.com/happyhappysundays/SparkBox_old/blob/main/Pictures/thumbnail_IMG_6785.jpg?raw=true) 152 | ![alt text](https://github.com/happyhappysundays/SparkBox_old/blob/main/Pictures/thumbnail_IMG_6786.jpg?raw=true) 153 | ![alt text](https://github.com/happyhappysundays/SparkBox_old/blob/main/Pictures/thumbnail_IMG_6994.jpg?raw=true) 154 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/meter_during.jpg?raw=true) 155 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/thumbnail_IMG_7475.jpg?raw=true) 156 | ![alt text](https://github.com/happyhappysundays/SparkBox_old/blob/main/Pictures/V0_4.jpg?raw=true) 157 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/SparkBox_final.jpg?raw=true) 158 | ![alt text](https://github.com/happyhappysundays/SparkBox_old/blob/main/Pictures/SparkBox_Heltec_Exp.png?raw=true) 159 | ![alt text](https://github.com/happyhappysundays/SparkBox/blob/main/Pictures/SparkBox_Battery.png?raw=true) 160 | 161 | # ESP32 pedal parts list 162 | 163 | | Item | Description | Link | 164 | | -----| ----------------------|--------------------| 165 | | 1 | Box |https://www.aliexpress.com/item/32693268669.html?spm=a2g0s.9042311.0.0.27424c4dlzGiUH 166 | | 2 | Stomp switch |https://www.aliexpress.com/item/32918205335.html?spm=a2g0s.9042311.0.0.27424c4dszp4Ie 167 | | 3 | ESP-WROOM-32U module |https://www.aliexpress.com/item/32864722159.html?spm=a2g0s.9042311.0.0.27424c4dlzGiUH 168 | | 4 | LCD screen |https://www.ebay.com.au/itm/333085424031 (small) or https://www.aliexpress.com/item/32950307344.html (large). Be sure to get the i2c version, or you can convert an SPI via this video https://www.youtube.com/watch?v=uPWzL_MZ4q4&list=LL&index=4&t=590s&ab_channel=Defpom%27sElectronicsRepair 169 | | 5 | BT antenna |https://www.aliexpress.com/item/4001054693109.html?spm=a2g0s.9042311.0.0.27424c4dlzGiUH and https://www.ebay.com.au/itm/233962468558 170 | | 6 | USB extension |https://www.aliexpress.com/item/32808991941.html?spm=a2g0s.9042311.0.0.27424c4dlzGiUH 171 | | 7 | Power switch |https://www.jaycar.com.au/dpdt-miniature-toggle-switch-solder-tag/p/ST0355 172 | | 8 | DC input jack |https://www.jaycar.com.au/2-5mm-bulkhead-male-dc-power-connector/p/PS0524 173 | | 9 | Pedal jack |https://www.jaycar.com.au/6-5mm-stereo-enclosed-insulated-switched-socket/p/PS0184 174 | | 10 | LiPo battery |https://www.ebay.com.au/itm/133708965813 175 | | 11 | LiPo charger |https://www.ebay.com.au/itm/161821599467 176 | | 12 | LiPo booster |https://www.jaycar.com.au/arduino-compatible-5v-dc-to-dc-converter-module/p/XC4512 177 | | 13 | 9V to 5V converter |https://www.ebay.com.au/itm/303839459634 178 | | 14 | Glass window (opt) |https://www.aliexpress.com/item/4000377316108.html?spm=a2g0s.12269583.0.0.1a1e62440DlgU2 179 | -------------------------------------------------------------------------------- /SparkBox.ino: -------------------------------------------------------------------------------- 1 | //****************************************************************************************** 2 | // SparkBox - BLE pedal board for the Spark 40 amp - David Thompson December 2022 3 | // Supports four-switch pedals. Added an expression pedal to modify the current selected effect or toggle an effect. 4 | // Long press (more than 1s) BUTTON 1 to switch between Effect mode and Preset mode 5 | // Long press BUTTON 3 to adjust current effect parameters: use BUTTONS 2 and 4 to decrement/increment, 6 | // BUTTON 1 to cycle thru parameters and long press BUTTON 3 to save your edits back to the amp. 7 | // Guitar Tuner: Long press BUTTONS 1 and 2 simultaneously, or turn it ON from the app or on the amp. 8 | // Bypass mode, invoked by long pressing BUTTONS 3 and 4 simultaneously, allows adjusting effect levels to match your raw pickup output. 9 | // Holding BUTTON 1 during boot will switch the pedal to WiFi mode. 10 | // 11 | //****************************************************************************************** 12 | // Extra enchancements by copych 2022: 13 | // *OLED display library changed to ThingPulse's, supporting most common DIY display types; 14 | // *minor button routines improvements 15 | // *minor interface improvements; 16 | // *ESP32 deep/light sleep ability added; 17 | // *LittleFS support added; 18 | // *Preset banks functionality added; 19 | // *WiFi support added with AP/WLAN config, stored in EEPROM (hold BUTTON1 while booting); 20 | // *Web-based preset file manager added. 21 | // 22 | // Updates to V1.0 23 | // Aleksei Golikov: Add HW LEDs to indicate which preset is selected. 24 | // copych: Relative and absolute paths rename fixing 25 | // 26 | //****************************************************************************************** 27 | // 28 | // #define CONFIG_LITTLEFS_SPIFFS_COMPAT 1 29 | #define CONFIG_LITTLEFS_HUMAN_READABLE 1 30 | // #define CONFIG_LITTLEFS_FOR_IDF_3_2 1 31 | #define FORMAT_LITTLEFS_IF_FAILED true 32 | #define CONFIG_LITTLEFS_FLUSH_FILE_EVERY_WRITE 1 33 | #include "config.h" 34 | #include "Banks.h" 35 | extern tBankConfig bankConfig[NUM_BANKS+1]; 36 | extern String bankConfigFile; 37 | // 38 | #include "Spark.h" // Paul Hamshere's SparkIO library https://github.com/paulhamsh/Spark/tree/main/Spark 39 | #include "SparkIO.h" // " 40 | #include "SparkComms.h" // " 41 | #include "font.h" // Custom fonts 42 | #include "bitmaps.h" // Custom bitmaps (icons) 43 | #include "SparkPresets.h" // Some hard-coded presets 44 | #include "UI.h" // Any UI-related defines 45 | // 46 | #include "FS.h" 47 | #include "LittleFS.h" 48 | #include "BluetoothSerial.h" 49 | #define ARDUINOJSON_USE_DOUBLE 1 50 | #include "ArduinoJson.h" // Should be installed already https://github.com/bblanchon/ArduinoJson 51 | #include "driver/rtc_io.h" 52 | // 53 | #include 54 | #include 55 | #include "WebServer.h" 56 | #include "SimplePortal.h" 57 | #include "RequestHandlersImpl.h" 58 | 59 | //****************************************************************************************** 60 | #define PGM_NAME "SparkBox" 61 | #define VERSION "V1.00" 62 | 63 | extern eMode_t curMode; 64 | extern eMode_t oldMode; 65 | extern eMode_t returnMode; 66 | extern eMode_t mainMode; 67 | extern tPedalCfg pedalCfg; 68 | 69 | // Variables required to track spark state and also for communications generally 70 | bool got_presets = false; 71 | uint8_t display_preset_num; // Referenced preset number on Spark 72 | int8_t active_led_num = -1; 73 | int i, j, p; 74 | int count; // " 75 | bool flash_GUI; // Flash GUI items if true 76 | bool isTunerMode; // Tuner mode flag 77 | bool scan_result = false; // Connection attempt result 78 | enum ePresets_t {HW_PRESET_0, HW_PRESET_1, HW_PRESET_2, HW_PRESET_3, TMP_PRESET, CUR_EDITING, TMP_PRESET_ADDR=0x007f}; 79 | enum eEffects_t {FX_GATE, FX_COMP, FX_DRIVE, FX_AMP, FX_MOD, FX_DELAY, FX_REVERB}; 80 | #ifdef ACTIVE_HIGH 81 | uint8_t logicON = HIGH; 82 | uint8_t logicOFF = LOW; 83 | #else 84 | uint8_t logicON = LOW; 85 | uint8_t logicOFF = HIGH; 86 | #endif 87 | 88 | // interrupts 89 | hw_timer_t * timer = NULL; // Timer variables 90 | portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; 91 | volatile bool isTimeout = false; // Update battery icon flag 92 | 93 | // SWITCHES Init =========================================================================== 94 | typedef struct { 95 | const uint8_t pin; 96 | const String fxLabel; 97 | uint8_t fxSlotNumber; // [0-6] number in fx chain 98 | bool fxOnOff; // Effect onOff 99 | } s_switches ; 100 | 101 | s_switches SWITCHES[NUM_SWITCHES] = { 102 | {switchPins[0], "DRIVE", FX_DRIVE, false}, 103 | {switchPins[1], "MOD", FX_MOD, false}, 104 | {switchPins[2], "DELAY", FX_DELAY, false}, 105 | {switchPins[3], "REVERB", FX_REVERB, false}, 106 | }; 107 | 108 | //****************************************************************************************** 109 | 110 | // Timer interrupt handler 111 | void IRAM_ATTR onTime() { 112 | portENTER_CRITICAL_ISR(&timerMux); 113 | isTimeout = true; 114 | // isRSSIupdate = true; 115 | portEXIT_CRITICAL_ISR(&timerMux); 116 | } 117 | // array of frame drawing functions 118 | FrameCallback frames[] = { frPresets, frEffects, frBanks, frConfig, frTuner, frBypass, frMessage, frLevel }; 119 | // number of frames in UI 120 | const uint8_t frameCount = NUM_FRAMES; 121 | 122 | // Overlays are statically drawn on top of a frame eg. a clock 123 | OverlayCallback overlays[] = { screenOverlay }; 124 | const uint8_t overlaysCount = 1; 125 | 126 | //****************************************************************************************** 127 | 128 | void setup() { 129 | 130 | time_to_sleep = millis() + (1000*60); // Preset timeout 131 | setCpuFrequencyMhz(180); // Hopefully this will let the battery last a bit longer 132 | #ifdef DEBUG_ON 133 | Serial.begin(115200); // Start serial debug console monitoring via ESP32 134 | while (!Serial); 135 | #endif 136 | display_preset_num = 0; 137 | 138 | // Check FS 139 | if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)){ 140 | DEBUG("LittleFS Mount Failed"); 141 | return; 142 | } else { 143 | DEBUG("LittleFS Mount Completed"); 144 | } 145 | 146 | // This will create bank folders if needed 147 | createFolders(); 148 | 149 | // First launch init for bankConfig 150 | if (bankConfig[1].start_chan == 255) { // It is 255 (-1) by default and 0/1 after initializing 151 | for (int i = 0; i <= NUM_BANKS; i++) { 152 | bankConfig[i].start_chan = 0; 153 | strlcpy(bankConfig[i].bank_name , ("Bank " + lz(i, 3) ).c_str(), sizeof(bankConfig[i].bank_name)); 154 | } 155 | } 156 | 157 | loadConfiguration(bankConfigFile, bankConfig); // Load, or init w/default values 158 | strlcpy(bankConfig[0].bank_name , "SPARK", sizeof("SPARK")); // Always be by that name 159 | saveConfiguration(bankConfigFile, bankConfig); // Save to FS 160 | 161 | // Setup GPIOs for led if those are defined 162 | #ifdef LEDS_USED 163 | setup_leds(); 164 | #endif 165 | 166 | // Debug - On my Heltec module, leaving this unconnected pin hanging causes 167 | // a display issue where the screen dims, returning if touched. 168 | pinMode(21,OUTPUT); 169 | 170 | // Manually toggle the /RST pin to add Heltec module functionality 171 | // but without the Heltec library 172 | pinMode(16,OUTPUT); 173 | digitalWrite(16, LOW); 174 | delay(50); 175 | digitalWrite(16, HIGH); 176 | 177 | // Initialize device OLED display, and flip screen, as OLED library starts upside-down 178 | ui.setTargetFPS(35); 179 | ui.disableAllIndicators(); 180 | ui.setFrames(frames, frameCount); 181 | ui.setFrameAnimation(SLIDE_UP); 182 | ui.setTimePerTransition(TRANSITION_TIME); 183 | ui.setOverlays(overlays, overlaysCount); 184 | ui.disableAutoTransition(); 185 | 186 | // Initialising the UI will init the display too. 187 | ui.init(); 188 | oled.flipScreenVertically(); // This allows to flip all the UI upside down depending on your h/w components palacement 189 | ESP_on(); // Show startup animation 190 | 191 | // Set pushbutton inputs to pull-downs or pull-ups depending on h/w variant 192 | for (i = 0; i < NUM_SWITCHES; i++) { 193 | #ifdef ACTIVE_HIGH 194 | pinMode(switchPins[i], INPUT_PULLDOWN); 195 | #else 196 | pinMode(switchPins[i], INPUT_PULLUP); 197 | #endif 198 | } 199 | 200 | // Accumulate battery voltage measurements to average the result 201 | for(i=0; i 10 | 11 | 12 | FileManager 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
 
22 |
 
23 |
 
24 |
 
25 | 26 |
27 |
 
28 |
29 |
 
30 |
31 |
32 |
33 | File
34 | Drop
35 | Zone
36 |
37 |
38 |
 
39 |
40 |
 
41 |
42 |
43 | 44 |
 
45 | 46 |
47 |
Loading...
48 |
 
49 |
50 | File list should appear here. 51 |
52 |
53 | 54 | 55 | 56 | )==x=="; 57 | 58 | static const char ESPxWebFlMgrWpjavascript[] PROGMEM = R"==x==( 59 | 60 | var rootdir="/"; 61 | 62 | function compressurlfile(source) { 63 | msgline("Fetching file..."); 64 | var request = new XMLHttpRequest(); 65 | request.onreadystatechange = function () { 66 | var DONE = this.DONE || 4; 67 | if (this.readyState === DONE) { 68 | var data = this.responseText; 69 | var gzip = require('gzip-js'), options = { level: 9, name: source, timestamp: parseInt(Date.now() / 1000, 10) }; 70 | var out = gzip.zip(data, options); 71 | var bout = new Uint8Array(out); // out is 16 bits... 72 | 73 | msgline("Sending compressed file..."); 74 | var sendback = new XMLHttpRequest(); 75 | sendback.onreadystatechange = function () { 76 | var DONE = this.DONE || 4; 77 | if (this.readyState === DONE) { 78 | getfileinsert(); 79 | } 80 | }; 81 | sendback.open('POST', '/r'); 82 | var formdata = new FormData(); 83 | var blob = new Blob([bout], { type: "application/octet-binary" }); 84 | formdata.append(source + '.gz', blob, source + '.gz'); 85 | sendback.send(formdata); 86 | } 87 | }; 88 | request.open('GET', source, true); 89 | request.send(null); 90 | } 91 | 92 | function getfileinsert() { 93 | msgline("Fetching files infos..."); 94 | var request = new XMLHttpRequest(); 95 | request.onreadystatechange = function () { 96 | var DONE = this.DONE || 4; 97 | if (this.readyState === DONE) { 98 | var res = this.responseText.split("##"); 99 | document.getElementById('fi').innerHTML = res[0]; 100 | document.getElementById("o3").innerHTML = res[1]; 101 | document.getElementById("o2").innerHTML = rootdir; 102 | msgline(""); 103 | } 104 | }; 105 | document.getElementById('fi').innerHTML = "
Loading..."; 106 | request.open('GET', '/i?dir=' + rootdir, true); 107 | request.send(null); 108 | } 109 | 110 | function changedir(newdir) { 111 | rootdir = newdir; 112 | getfileinsert(); 113 | document.getElementById("o2").innerHTML = rootdir; 114 | } 115 | 116 | function executecommand(command) { 117 | var xhr = new XMLHttpRequest(); 118 | xhr.onreadystatechange = function () { 119 | var DONE = this.DONE || 4; 120 | if (this.readyState === DONE) { 121 | getfileinsert(); 122 | } 123 | }; 124 | xhr.open('GET', '/c?' + command, true); 125 | xhr.send(null); 126 | } 127 | 128 | function downloadfile(filename) { 129 | window.location.href = "/c?dwn=" + filename; 130 | } 131 | 132 | function deletefile(filename) { 133 | if (confirm("Really delete " + filename)) { 134 | msgline("Please wait. Delete in progress..."); 135 | executecommand("del=" + filename); 136 | } 137 | } 138 | 139 | function renamefile(filename) { 140 | var newname = prompt("new name for " + filename, filename); 141 | if (newname != null) { 142 | msgline("Please wait. Rename in progress..."); 143 | executecommand("ren=" + filename + "&new=" + newname); 144 | } 145 | } 146 | 147 | var editxhr; 148 | 149 | function editfile(filename) { 150 | msgline("Please wait. Creating editor..."); 151 | 152 | editxhr = new XMLHttpRequest(); 153 | editxhr.onreadystatechange = function () { 154 | var DONE = this.DONE || 4; 155 | if (this.readyState === DONE) { 156 | document.getElementById('fi').innerHTML = editxhr.responseText; 157 | document.getElementById("o3").innerHTML = "Edit " + filename; 158 | msgline(""); 159 | } 160 | }; 161 | editxhr.open('GET', '/e?edit=' + filename, true); 162 | editxhr.send(null); 163 | } 164 | 165 | function sved(filename) { 166 | var content = document.getElementById('tect').value; 167 | // utf-8 168 | content = unescape(encodeURIComponent(content)); 169 | 170 | var xhr = new XMLHttpRequest(); 171 | 172 | xhr.open("POST", "/r", true); 173 | 174 | var boundary = '-----whatever'; 175 | xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary); 176 | 177 | var body = "" + 178 | '--' + boundary + '\r\n' + 179 | 'Content-Disposition: form-data; name="uploadfile"; filename="' + filename + '"' + '\r\n' + 180 | 'Content-Type: text/plain' + '\r\n' + 181 | '' + '\r\n' + 182 | content + '\r\n' + 183 | '--' + boundary + '--\r\n' + // \r\n fixes upload delay in ESP8266WebServer 184 | ''; 185 | 186 | // ajax does not do xhr.setRequestHeader("Content-length", body.length); 187 | 188 | xhr.onreadystatechange = function () { 189 | var DONE = this.DONE || 4; 190 | if (this.readyState === DONE) { 191 | getfileinsert(); 192 | } 193 | } 194 | 195 | xhr.send(body); 196 | } 197 | 198 | function abed() { 199 | getfileinsert(); 200 | } 201 | 202 | var uploaddone = true; // hlpr for multiple file uploads 203 | 204 | function uploadFile(file, islast) { 205 | uploaddone = false; 206 | file.name = file.name; 207 | var xhr = new XMLHttpRequest(); 208 | xhr.onreadystatechange = function () { 209 | // console.log(xhr.status); 210 | var DONE = this.DONE || 4; 211 | if (this.readyState === DONE) { 212 | if (islast) { 213 | getfileinsert(); 214 | console.log('last file'); 215 | } 216 | uploaddone = true; 217 | } 218 | }; 219 | xhr.open('POST', '/r'); 220 | var formdata = new FormData(); 221 | formdata.append('dir', rootdir); 222 | formdata.append('uploadfile', file); 223 | formdata.append('dummy', 'dummy'); 224 | xhr.send(formdata); 225 | } 226 | 227 | var globaldropfilelisthlpr = null; // read-only-list, no shift() 228 | var transferitem = 0; 229 | var uploadFileProzessorhndlr = null; 230 | 231 | function uploadFileProzessor() { 232 | if (uploaddone) { 233 | if (transferitem==globaldropfilelisthlpr.length) { 234 | clearInterval(uploadFileProzessorhndlr); 235 | } else { 236 | var file = globaldropfilelisthlpr[transferitem]; 237 | msgline("Please wait. Transferring file "+file.name+"..."); 238 | console.log('process file ' + file.name); 239 | transferitem++; 240 | uploadFile(file,transferitem==globaldropfilelisthlpr.length); 241 | } 242 | } 243 | } 244 | 245 | /* 246 | function dropHandlerALT(ev) { 247 | console.log('File(s) dropped'); 248 | 249 | document.getElementById('msg').innerHTML = "Please wait. Transferring file..."; 250 | 251 | // Prevent default behavior (Prevent file from being opened) 252 | ev.preventDefault(); 253 | 254 | if (ev.dataTransfer.items) { 255 | // Use DataTransferItemList interface to access the file(s) 256 | for (var i = 0; i < ev.dataTransfer.items.length; i++) { 257 | // If dropped items aren't files, reject them 258 | if (ev.dataTransfer.items[i].kind === 'file') { 259 | var file = ev.dataTransfer.items[i].getAsFile(); 260 | uploadFile(file); 261 | console.log('.1. file[' + i + '].name = ' + file.name); 262 | } 263 | } 264 | } else { 265 | // Use DataTransfer interface to access the file(s) 266 | for (var i = 0; i < ev.dataTransfer.files.length; i++) { 267 | console.log('.2. file[' + i + '].name = ' + ev.dataTransfer.files[i].name); 268 | } 269 | } 270 | } 271 | */ 272 | 273 | function dropHandler(ev) { 274 | console.log('File(s) dropped'); 275 | 276 | globaldropfilelisthlpr = ev.dataTransfer; 277 | transferitem = 0; 278 | 279 | msgline("Please wait. Transferring file..."); 280 | 281 | // Prevent default behavior (Prevent file from being opened) 282 | ev.preventDefault(); 283 | 284 | if (ev.dataTransfer.items) { 285 | var data = ev.dataTransfer; 286 | globaldropfilelisthlpr = data.files; 287 | uploadFileProzessorhndlr = setInterval(uploadFileProzessor,1000); 288 | console.log('Init upload list.'); 289 | } else { 290 | // Use DataTransfer interface to access the file(s) 291 | for (var i = 0; i < ev.dataTransfer.files.length; i++) { 292 | console.log('.2. file[' + i + '].name = ' + ev.dataTransfer.files[i].name); 293 | } 294 | } 295 | } 296 | 297 | function dragOverHandler(ev) { 298 | console.log('File(s) in drop zone'); 299 | 300 | // Prevent default behavior (Prevent file from being opened) 301 | ev.preventDefault(); 302 | } 303 | 304 | function msgline(msg) { 305 | document.getElementById('msg').innerHTML = msg; 306 | } 307 | 308 | function downloadall() { 309 | msgline("Sending all files in one zip."); 310 | window.location.href = "/c?za=all"; 311 | msgline(""); 312 | } 313 | 314 | //-> 315 | window.onload = getfileinsert; 316 | 317 | )==x=="; 318 | 319 | 320 | //***************************************************************************************************** 321 | static const char ESPxWebFlMgrWpcss[] PROGMEM = R"==g==( 322 | 323 | body, button, div { 324 | font-size: 19px; 325 | } 326 | 327 | div { 328 | margin: 0 1px 1px 0; 329 | font-family: 'Segoe UI', Verdana, sans-serif; 330 | } 331 | 332 | #gc { 333 | display: grid; 334 | grid-template-columns: 80px 25% auto 30px; 335 | grid-template-rows: 3vw 30px auto 30px 20px; 336 | grid-template-areas: "o1 o2 o3 o4" "m1 c c c" "m2 c c c" "m3 c c c" "u1 u2 u3 u4"; 337 | } 338 | 339 | .o1 { 340 | grid-area: o1; 341 | background-color: #9999CC; 342 | border-top-left-radius: 20px; 343 | margin-bottom: 0px; 344 | } 345 | 346 | .o2 { 347 | grid-area: o2; 348 | background-color: #9999FF; 349 | margin-bottom: 0px; 350 | white-space: nowrap; 351 | line-height: 1; 352 | padding: 0 0 0 6px; 353 | } 354 | 355 | .o3 { 356 | grid-area: o3; 357 | background-color: #CC99CC; 358 | margin-bottom: 0px; 359 | white-space: nowrap; 360 | line-height: 1; 361 | padding: 0 0 0 6px; 362 | } 363 | 364 | .o4 { 365 | grid-area: o4; 366 | background-color: #CC6699; 367 | border-radius: 0 10px 10px 0; 368 | margin-bottom: 0px; 369 | } 370 | 371 | .m1 { 372 | grid-area: m1; 373 | margin-top: 0px; 374 | background-color: #9999CC; 375 | display: grid; 376 | grid-template-columns: 60px 20px; 377 | grid-template-rows: 20px; 378 | grid-template-areas: "s11 s12"; 379 | } 380 | 381 | .s12 { 382 | margin: 0px; 383 | background-color: #9999CC; 384 | } 385 | 386 | .s13 { 387 | margin: 0px; 388 | border-top-left-radius: 20px; 389 | height: 30px; 390 | } 391 | 392 | .m2 { 393 | display: flex; 394 | justify-content: center; 395 | align-items: center; 396 | grid-area: m2; 397 | background-color: #CC6699; 398 | width: 60px; 399 | min-height: 100px; 400 | } 401 | 402 | .m3 { 403 | grid-area: m3; 404 | margin-bottom: 0px; 405 | background-color: #9999CC; 406 | display: grid; 407 | grid-template-columns: 60px 20px; 408 | grid-template-rows: 20px; 409 | grid-template-areas: "s31 s32"; 410 | } 411 | 412 | .s32 { 413 | margin: 0px; 414 | background-color: #9999CC; 415 | } 416 | 417 | .s33 { 418 | margin: 0px; 419 | border-bottom-left-radius: 20px; 420 | height: 30px; 421 | } 422 | 423 | .u1 { 424 | grid-area: u1; 425 | background-color: #9999CC; 426 | border-bottom-left-radius: 20px; 427 | margin-top: 0px; 428 | } 429 | 430 | .u2 { 431 | grid-area: u2; 432 | cursor: pointer; 433 | background-color: #CC6666; 434 | margin-top: 0px; 435 | padding-left: 10px; 436 | vertical-align: middle; 437 | font-size: 80%; 438 | } 439 | 440 | .u2:hover { 441 | background-color: #9999FF; 442 | color: white; 443 | } 444 | 445 | .u3 { 446 | grid-area: u3; 447 | padding-left: 10px; 448 | background-color: #FF9966; 449 | font-size: 80%; 450 | margin-top: 0px; 451 | } 452 | 453 | .u4 { 454 | grid-area: u4; 455 | background-color: #FF9900; 456 | border-radius: 0 10px 10px 0; 457 | margin-top: 0px; 458 | } 459 | 460 | .c { 461 | grid-area: c; 462 | } 463 | 464 | #fi .b { 465 | background-color: Transparent; 466 | border: 1px solid #9999FF; 467 | border-radius: 1px; 468 | padding: 0px; 469 | width: 30px; 470 | cursor: pointer; 471 | } 472 | 473 | #fi .b:hover { 474 | background-color: #9999FF; 475 | color: white; 476 | } 477 | 478 | .cc { 479 | width: min-content; 480 | margin: 10px 0px; 481 | } 482 | 483 | .gc div { 484 | padding: 1px 8px 3px 8px; 485 | min-width: 65px; 486 | } 487 | 488 | .ccg, .ccu, .dcu, .dcg { 489 | height: 1.5em; 490 | } 491 | 492 | .ccg { 493 | background-color: #A5A5FF; 494 | } 495 | 496 | .ccu { 497 | background-color: #AfAfFF; 498 | } 499 | 500 | .dcg { 501 | background-color: #FE9A00; 502 | } 503 | 504 | .dcu { 505 | background-color: #FfA510; 506 | } 507 | 508 | .ccl { 509 | border-radius: 5px 0 0 5px; 510 | cursor: pointer; 511 | } 512 | 513 | .ccl:hover { 514 | border-radius: 5px 0 0 5px; 515 | color: white; 516 | cursor: pointer; 517 | } 518 | 519 | .ccr { 520 | border-radius: 0 5px 5px 0; 521 | } 522 | 523 | .cct { 524 | text-align: right; 525 | } 526 | 527 | #tect { 528 | font-size: 17px; 529 | } 530 | .gc { 531 | display: grid; 532 | grid-template-columns: 11em 6em 17em 7em; 533 | } 534 | )==g=="; 535 | 536 | 537 | #endif 538 | -------------------------------------------------------------------------------- /SparkComms.ino: -------------------------------------------------------------------------------- 1 | #include "Spark.h" 2 | #include "SparkComms.h" 3 | 4 | const uint8_t notifyOn[] = {0x1, 0x0}; 5 | 6 | // client callback for connection to Spark 7 | 8 | class MyClientCallback : public BLEClientCallbacks 9 | { 10 | void onConnect(BLEClient *pclient) 11 | { 12 | DEBUG("callback: Spark connected"); 13 | set_conn_status_connected(SPK); 14 | //oled.displayOn(); //debug 15 | } 16 | 17 | void onDisconnect(BLEClient *pclient) 18 | { 19 | // if (pclient->isConnected()) { 20 | 21 | connected_sp = false; 22 | DEBUG("callback: Spark disconnected"); 23 | set_conn_status_disconnected(SPK); 24 | } 25 | 26 | #ifndef CLASSIC 27 | bool onConnParamsUpdateRequest(BLEClient* pClient, const ble_gap_upd_params* params) { 28 | DEBUG("Connection callback"); 29 | got_param_callback = true; 30 | return true; 31 | }; 32 | #endif 33 | }; 34 | 35 | // server callback for connection to BLE app 36 | 37 | class MyServerCallback : public BLEServerCallbacks 38 | { 39 | void onConnect(BLEServer *pserver) 40 | { 41 | if (pserver->getConnectedCount() == 1) { 42 | set_conn_status_connected(APP); 43 | DEBUG("callback: BLE app connected and is connected"); 44 | } 45 | else { 46 | DEBUG("callback: BLE app connected and is not really connected"); 47 | } 48 | } 49 | 50 | void onDisconnect(BLEServer *pserver) 51 | { 52 | // if (pserver->getConnectedCount() == 1) { 53 | DEBUG("callback: BLE app disconnected"); 54 | set_conn_status_disconnected(APP); 55 | } 56 | }; 57 | 58 | // BLE MIDI 59 | #ifdef BLE_APP_MIDI 60 | class MyMIDIServerCallback : public BLEServerCallbacks 61 | { 62 | void onConnect(BLEServer *pserver) 63 | { 64 | //set_conn_status_connected(APP); 65 | DEBUG("callback: BLE MIDI connected"); 66 | } 67 | 68 | void onDisconnect(BLEServer *pserver) 69 | { 70 | DEBUG("callback: BLE MIDI disconnected"); 71 | //set_conn_status_disconnected(APP); 72 | } 73 | }; 74 | #endif 75 | 76 | #ifdef CLASSIC 77 | // server callback for connection to BT classic app 78 | 79 | void bt_callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param){ 80 | if(event == ESP_SPP_SRV_OPEN_EVT){ 81 | DEBUG("callback: Classic BT Spark app connected"); 82 | set_conn_status_connected(APP); 83 | } 84 | 85 | if(event == ESP_SPP_CLOSE_EVT ){ 86 | DEBUG("callback: Classic BT Spark app disconnected"); 87 | set_conn_status_disconnected(APP); 88 | } 89 | } 90 | #endif 91 | 92 | void notifyCB_sp(BLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { 93 | 94 | int i; 95 | byte b; 96 | 97 | for (i = 0; i < length; i++) { 98 | b = pData[i]; 99 | ble_in.add(b); 100 | } 101 | ble_in.commit(); 102 | } 103 | 104 | #ifdef BLE_CONTROLLER 105 | // This works with IK Multimedia iRig Blueboard and the Akai LPD8 wireless - interestingly they have the same UUIDs 106 | void notifyCB_pedal(BLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){ 107 | 108 | int i; 109 | byte b; 110 | 111 | for (i = 0; i < length; i++) { 112 | b = pData[i]; 113 | midi_in.add(b); 114 | } 115 | midi_in.commit(); 116 | 117 | set_conn_received(BLE_MIDI); 118 | } 119 | #endif 120 | 121 | class CharacteristicCallbacks: public BLECharacteristicCallbacks { 122 | void onWrite(BLECharacteristic* pCharacteristic) { 123 | int j, l; 124 | const char *p; 125 | byte b; 126 | l = pCharacteristic->getValue().length(); 127 | p = pCharacteristic->getValue().c_str(); 128 | for (j=0; j < l; j++) { 129 | b = p[j]; 130 | ble_app_in.add(b); 131 | } 132 | ble_app_in.commit(); 133 | }; 134 | }; 135 | 136 | static CharacteristicCallbacks chrCallbacks_s, chrCallbacks_r; 137 | 138 | 139 | // BLE APP MIDI 140 | #ifdef BLE_APP_MIDI 141 | class MIDICharacteristicCallbacks: public BLECharacteristicCallbacks { 142 | void onWrite(BLECharacteristic* pCharacteristic) { 143 | int j, l; 144 | const char *p; 145 | byte b; 146 | l = pCharacteristic->getValue().length(); 147 | p = pCharacteristic->getValue().c_str(); 148 | for (j=0; j < l; j++) { 149 | b = p[j]; 150 | ble_midi_in.add(b); 151 | } 152 | ble_midi_in.commit(); 153 | }; 154 | }; 155 | 156 | static MIDICharacteristicCallbacks chrCallbacksMIDI; 157 | #endif 158 | 159 | BLEUUID SpServiceUuid(C_SERVICE); 160 | #ifdef BLE_CONTROLLER 161 | BLEUUID PedalServiceUuid(PEDAL_SERVICE); 162 | #endif 163 | 164 | 165 | void connect_spark() { 166 | if (found_sp && !connected_sp) { 167 | if (pClient_sp != nullptr && pClient_sp->isConnected()) 168 | DEBUG("HMMMM - connect_spark() SAYS I WAS CONNECTED ANYWAY"); 169 | 170 | if (pClient_sp->connect(sp_device)) { 171 | #if defined CLASSIC && !defined HELTEC_WIFI 172 | pClient_sp->setMTU(517); 173 | #endif 174 | connected_sp = true; 175 | pService_sp = pClient_sp->getService(SpServiceUuid); 176 | if (pService_sp != nullptr) { 177 | pSender_sp = pService_sp->getCharacteristic(C_CHAR1); 178 | pReceiver_sp = pService_sp->getCharacteristic(C_CHAR2); 179 | if (pReceiver_sp && pReceiver_sp->canNotify()) { 180 | #ifdef CLASSIC 181 | pReceiver_sp->registerForNotify(notifyCB_sp); 182 | p2902_sp = pReceiver_sp->getDescriptor(BLEUUID((uint16_t)0x2902)); 183 | if (p2902_sp != nullptr) 184 | p2902_sp->writeValue((uint8_t*)notifyOn, 2, true); 185 | #else 186 | if (!pReceiver_sp->subscribe(true, notifyCB_sp, true)) { 187 | connected_sp = false; 188 | DEBUG("Spark disconnected"); 189 | NimBLEDevice::deleteClient(pClient_sp); 190 | } 191 | #endif 192 | } 193 | } 194 | DEBUG("connect_spark(): Spark connected"); 195 | } 196 | } 197 | } 198 | 199 | 200 | #ifdef BLE_CONTROLLER 201 | void connect_pedal() { 202 | if (found_pedal && !connected_pedal) { 203 | if (pClient_pedal->connect(pedal_device)) { 204 | #if defined CLASSIC && !defined HELTEC_WIFI 205 | pClient_sp->setMTU(517); 206 | #endif 207 | connected_pedal = true; 208 | pService_pedal = pClient_pedal->getService(PedalServiceUuid); 209 | if (pService_pedal != nullptr) { 210 | pReceiver_pedal = pService_pedal->getCharacteristic(PEDAL_CHAR); 211 | 212 | if (pReceiver_pedal && pReceiver_pedal->canNotify()) { 213 | #ifdef CLASSIC 214 | pReceiver_pedal->registerForNotify(notifyCB_pedal); 215 | p2902_pedal = pReceiver_pedal->getDescriptor(BLEUUID((uint16_t)0x2902)); 216 | if(p2902_pedal != nullptr) 217 | p2902_pedal->writeValue((uint8_t*)notifyOn, 2, true); 218 | #else 219 | if (!pReceiver_pedal->subscribe(true, notifyCB_pedal, true)) { 220 | connected_pedal = false; 221 | DEBUG("Pedal disconnected"); 222 | NimBLEDevice::deleteClient(pClient_pedal); 223 | } 224 | #endif 225 | } 226 | } 227 | DEBUG("connect_pedal(): pedal connected"); 228 | set_conn_status_connected(BLE_MIDI); 229 | } 230 | } 231 | } 232 | #endif 233 | 234 | bool connect_to_all() { 235 | int i, j; 236 | int counts; 237 | uint8_t b; 238 | unsigned long t; 239 | 240 | // set up connection status tracking array 241 | t = millis(); 242 | for (i = 0; i < NUM_CONNS; i++) { 243 | conn_status[i] = false; 244 | for (j = 0; j < 3; j++) 245 | conn_last_changed[j][i] = t; 246 | } 247 | 248 | is_ble = true; 249 | got_param_callback = false; 250 | 251 | BLEDevice::init(SPARK_BT_NAME); 252 | pClient_sp = BLEDevice::createClient(); 253 | pClient_sp->setClientCallbacks(new MyClientCallback()); 254 | BLEDevice::setMTU(517); 255 | 256 | #ifdef BLE_CONTROLLER 257 | pClient_pedal = BLEDevice::createClient(); 258 | #endif 259 | pScan = BLEDevice::getScan(); 260 | 261 | pServer = BLEDevice::createServer(); 262 | pServer->setCallbacks(new MyServerCallback()); 263 | pService = pServer->createService(S_SERVICE); 264 | 265 | #ifdef CLASSIC 266 | pCharacteristic_receive = pService->createCharacteristic(S_CHAR1, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); 267 | pCharacteristic_send = pService->createCharacteristic(S_CHAR2, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); 268 | #else 269 | pCharacteristic_receive = pService->createCharacteristic(S_CHAR1, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR); 270 | pCharacteristic_send = pService->createCharacteristic(S_CHAR2, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); 271 | #endif 272 | 273 | pCharacteristic_receive->setCallbacks(&chrCallbacks_r); 274 | pCharacteristic_send->setCallbacks(&chrCallbacks_s); 275 | #ifdef CLASSIC 276 | pCharacteristic_send->addDescriptor(new BLE2902()); 277 | #endif 278 | 279 | 280 | #ifdef BLE_APP_MIDI 281 | // pServerMIDI = BLEDevice::createServer(); 282 | // pServerMIDI->setCallbacks(new MyMIDIServerCallback()); 283 | pServiceMIDI = pServer->createService(MIDI_SERVICE); 284 | 285 | #ifdef CLASSIC 286 | pCharacteristicMIDI = pServiceMIDI->createCharacteristic(MIDI_CHAR, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR | BLECharacteristic::PROPERTY_NOTIFY); 287 | #else 288 | pCharacteristicMIDI = pServiceMIDI->createCharacteristic(MIDI_CHAR, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::NOTIFY); 289 | #endif 290 | 291 | pCharacteristicMIDI->setCallbacks(&chrCallbacksMIDI); 292 | #ifdef CLASSIC 293 | pCharacteristicMIDI->addDescriptor(new BLE2902()); 294 | #endif 295 | #endif 296 | 297 | 298 | pService->start(); 299 | #ifdef BLE_APP_MIDI 300 | pServiceMIDI->start(); 301 | #endif 302 | 303 | #ifndef CLASSIC 304 | pServer->start(); 305 | #endif 306 | 307 | pAdvertising = BLEDevice::getAdvertising(); // create advertising instance 308 | pAdvertising->addServiceUUID(pService->getUUID()); // tell advertising the UUID of our service 309 | #ifdef BLE_APP_MIDI 310 | pAdvertising->addServiceUUID(pServiceMIDI->getUUID()); // tell advertising the UUID of our service 311 | #endif 312 | pAdvertising->setScanResponse(true); 313 | 314 | // Connect to Spark 315 | connected_sp = false; 316 | found_sp = false; 317 | 318 | #ifdef BLE_CONTROLLER 319 | connected_pedal = false; 320 | found_pedal = false; 321 | #endif 322 | 323 | DEBUG("Scanning..."); 324 | 325 | counts = 0; 326 | while (!found_sp && counts < MAX_SCAN_COUNT) { // assume we only use a pedal if on already and hopefully found at same time as Spark, don't wait for it 327 | counts++; 328 | pResults = pScan->start(4); 329 | 330 | for(i = 0; i < pResults.getCount() && !found_sp; i++) { 331 | device = pResults.getDevice(i); 332 | 333 | if (device.isAdvertisingService(SpServiceUuid)) { 334 | DEBUG("Found Spark"); 335 | found_sp = true; 336 | connected_sp = false; 337 | sp_device = new BLEAdvertisedDevice(device); 338 | } 339 | 340 | #ifdef BLE_CONTROLLER 341 | if (device.isAdvertisingService(PedalServiceUuid) || strcmp(device.getName().c_str(),"iRig BlueBoard") == 0) { 342 | DEBUG("Found pedal"); 343 | found_pedal = true; 344 | connected_pedal = false; 345 | pedal_device = new BLEAdvertisedDevice(device); 346 | } 347 | #endif 348 | } 349 | } 350 | 351 | if (!found_sp) return false; // failed to find the Spark within the number of counts allowed (MAX_SCAN_COUNT) 352 | 353 | // Set up client 354 | connect_spark(); 355 | #ifdef BLE_CONTROLLER 356 | connect_pedal(); 357 | #endif 358 | 359 | #ifdef CLASSIC 360 | DEBUG("Starting classic bluetooth"); 361 | // now advertise Serial Bluetooth 362 | bt = new BluetoothSerial(); 363 | bt->register_callback(bt_callback); 364 | if (!bt->begin (SPARK_BT_NAME)) { 365 | DEBUG("Classic bluetooth init fail"); 366 | while (true); 367 | } 368 | 369 | // flush anything read from App - just in case 370 | while (bt->available()) 371 | b = bt->read(); 372 | DEBUG("Spark 40 Audio set up"); 373 | #endif 374 | 375 | DEBUG("Available for app to connect..."); 376 | pAdvertising->start(); 377 | return true; 378 | } 379 | 380 | 381 | // app_available both returns whether any data is available but also selects which type of bluetooth to use based 382 | // on whether there is any input there 383 | bool app_available() { 384 | if (!ble_app_in.is_empty()) { 385 | is_ble = true; 386 | return true; 387 | } 388 | #ifdef CLASSIC 389 | if (bt->available()) { 390 | is_ble = false; 391 | return true; 392 | } 393 | #endif 394 | // if neither have input, then there definitely is no input 395 | return false; 396 | } 397 | 398 | uint8_t app_read() { 399 | set_conn_received(APP); 400 | if (is_ble) { 401 | uint8_t b; 402 | ble_app_in.get(&b); 403 | return b; 404 | } 405 | #ifdef CLASSIC 406 | else { 407 | return bt->read(); 408 | } 409 | #endif 410 | // Never gets here 411 | return(0); 412 | } 413 | 414 | void app_write(byte *buf, int len) { 415 | set_conn_sent(APP); 416 | if (is_ble) { 417 | pCharacteristic_send->setValue(buf, len); 418 | pCharacteristic_send->notify(true); 419 | } 420 | #ifdef CLASSIC 421 | else { 422 | bt->write(buf, len); 423 | } 424 | #endif 425 | } 426 | 427 | 428 | void app_write_timed(byte *buf, int len) { // same as app_write but with a slight delay for classic bluetooth - it seems to need it 429 | set_conn_sent(APP); 430 | if (is_ble) { 431 | pCharacteristic_send->setValue(buf, len); 432 | pCharacteristic_send->notify(true); 433 | } 434 | #ifdef CLASSIC 435 | else { 436 | bt->write(buf, len); 437 | delay(50); // this helps the timing of a 'fake' store hardware preset 438 | } 439 | #endif 440 | } 441 | 442 | bool sp_available() { 443 | return !ble_in.is_empty(); 444 | } 445 | 446 | uint8_t sp_read() { 447 | uint8_t b; 448 | 449 | set_conn_received(SPK); 450 | ble_in.get(&b); 451 | return b; 452 | } 453 | 454 | void sp_write(byte *buf, int len) { 455 | set_conn_sent(SPK); 456 | pSender_sp->writeValue(buf, len, false); 457 | } 458 | 459 | // for some reason getRssi() crashes with two clients! 460 | int ble_getRSSI() { 461 | #ifdef BLE_CONTROLLER 462 | return 0; 463 | #else 464 | return pClient_sp->getRssi(); 465 | #endif 466 | } 467 | 468 | 469 | // Code to enable UI changes 470 | 471 | 472 | void set_conn_received(int connection) { 473 | conn_last_changed[FROM][connection] = millis(); 474 | } 475 | 476 | void set_conn_sent(int connection) { 477 | conn_last_changed[TO][connection] = millis(); 478 | } 479 | 480 | void set_conn_status_connected(int connection) { 481 | if (conn_status[connection] == false) { 482 | conn_status[connection] = true; 483 | conn_last_changed[STATUS][connection] = millis(); 484 | } 485 | } 486 | 487 | void set_conn_status_disconnected(int connection) { 488 | if (conn_status[connection] == true) { 489 | conn_status[connection] = false; 490 | conn_last_changed[STATUS][connection] = millis(); 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /SparkPresets.h: -------------------------------------------------------------------------------- 1 | // ======================================================================================== 2 | // ======================================================================================== 3 | // ======================================================================================== 4 | // ============== Taken from here https://github.com/paulhamsh/SparkIO ==================== 5 | // ============================= Slightly modified ======================================== 6 | // ======================================================================================== 7 | // ======================================================================================== 8 | // ======================================================================================== 9 | 10 | #include 11 | #include "Spark.h" 12 | 13 | const SparkPreset preset0{0x0,0x7f,"07079063-94A9-41B1-AB1D-02CBC5D00790","Silver Ship","0.7","1-Clean","icon.png",120.000000,{ 14 | {"bias.noisegate", false, 3, {0.138313, 0.224643, 0.000000}}, 15 | {"LA2AComp", true, 3, {0.000000, 0.852394, 0.373072}}, 16 | {"Booster", false, 1, {0.722592}}, 17 | {"RolandJC120", true, 5, {0.632231, 0.281820, 0.158359, 0.671320, 0.805785}}, 18 | {"Cloner", true, 2, {0.199593, 0.000000}}, 19 | {"VintageDelay", false, 4, {0.378739, 0.425745, 0.419816, 1.000000}}, 20 | {"bias.reverb", true, 7, {0.285714, 0.408354, 0.289489, 0.388317, 0.582143, 0.650000, 0.200000}} },0xb4 }; 21 | 22 | const SparkPreset preset1{0x0,0x7f,"CDE99591-C05D-4AE0-9E34-EC4A81F3F84F","Sweet Memory","0.7","1-Clean","icon.png",120.000000,{ 23 | {"bias.noisegate", false, 3, {0.099251, 0.570997, 0.000000}}, 24 | {"BlueComp", false, 4, {0.430518, 0.663291, 0.355048, 0.557014}}, 25 | {"DistortionTS9", false, 3, {0.058011, 0.741722, 0.595924}}, 26 | {"94MatchDCV2", true, 5, {0.528926, 0.500905, 0.246163, 0.417119, 0.782293}}, 27 | {"Flanger", false, 3, {0.413793, 0.663043, 0.655172}}, 28 | {"DelayRe201", true, 5, {0.097778, 0.312182, 0.485182, 0.369640, 1.000000}}, 29 | {"bias.reverb", true, 7, {0.561185, 0.506659, 0.417857, 0.300847, 0.602287, 0.594118, 0.000000}} },0xeb }; 30 | 31 | const SparkPreset preset2{0x0,0x7f,"F577F7F3-E8E0-4D35-8975-0427C2054DCE","Dancing in the room","0.7","Description for Blues Preset 1","icon.png",120.000000,{ 32 | {"bias.noisegate", false, 2, {0.283019, 0.304245}}, 33 | {"Compressor", true, 2, {0.325460, 0.789062}}, 34 | {"Booster", false, 1, {0.666735}}, 35 | {"Twin", true, 5, {0.613433, 0.371715, 0.453167, 0.676660, 0.805785}}, 36 | {"ChorusAnalog", true, 4, {0.185431, 0.086409, 0.485027, 0.567797}}, 37 | {"DelayEchoFilt", false, 5, {0.533909, 0.275554, 0.455372, 0.457702, 1.000000}}, 38 | {"bias.reverb", true, 7, {0.508871, 0.317935, 0.461957, 0.349689, 0.339286, 0.481753, 0.700000}} },0x48 }; 39 | 40 | const SparkPreset preset3{0x0,0x7f,"D8757D67-98EA-4888-86E5-5F1FD96A30C3","Royal Crown","0.7","1-Clean","icon.png",120.000000,{ 41 | {"bias.noisegate", true, 3, {0.211230, 0.570997, 0.000000}}, 42 | {"Compressor", true, 2, {0.172004, 0.538197}}, 43 | {"DistortionTS9", false, 3, {0.703110, 0.278146, 0.689846}}, 44 | {"ADClean", true, 5, {0.677083, 0.501099, 0.382828, 0.585946, 0.812231}}, 45 | {"ChorusAnalog", true, 4, {0.519976, 0.402152, 0.240642, 0.740579}}, 46 | {"DelayMono", true, 5, {0.173729, 0.233051, 0.493579, 0.600000, 1.000000}}, 47 | {"bias.reverb", true, 7, {0.688801, 0.392857, 0.461138, 0.693705, 0.488235, 0.466387, 0.300000}} },0xa2 }; 48 | 49 | const SparkPreset preset4{0x0,0x7f,"9D2F2AA3-4EC5-4BD7-A3CD-A76FD55698DB","Wooden Bridge","0.7","Description for Blues Preset 1","icon.png",120.000000,{ 50 | {"bias.noisegate", true, 2, {0.316873, 0.304245}}, 51 | {"Compressor", false, 2, {0.341085, 0.665754}}, 52 | {"Booster", true, 1, {0.661412}}, 53 | {"Bassman", true, 5, {0.768152, 0.491509, 0.476547, 0.284314, 0.389779}}, 54 | {"UniVibe", false, 3, {0.500000, 1.000000, 0.700000}}, 55 | {"VintageDelay", true, 4, {0.152219, 0.663314, 0.144982, 1.000000}}, 56 | {"bias.reverb", true, 7, {0.120109, 0.150000, 0.500000, 0.406755, 0.299253, 0.768478, 0.100000}} },0x12 }; 57 | 58 | const SparkPreset preset5{0x0,0x7f,"B08F2421-0686-484E-B6EC-8F660A9344FC","Stone Breaker","0.7","Description for Blues Preset 1","icon.png",120.000000,{ 59 | {"bias.noisegate", true, 2, {0.105936, 0.231329}}, 60 | {"Compressor", true, 2, {0.341085, 0.665754}}, 61 | {"DistortionTS9", false, 3, {0.117948, 0.390437, 0.583560}}, 62 | {"TwoStoneSP50", true, 5, {0.634593, 0.507692, 0.664699, 0.519608, 0.714050}}, 63 | {"Tremolator", false, 3, {0.330000, 0.500000, 1.000000}}, 64 | {"DelayRe201", true, 5, {0.324783, 0.204820, 0.460643, 0.304200, 1.000000}}, 65 | {"bias.reverb", true, 7, {0.554974, 0.842373, 0.783898, 0.385087, 0.659664, 0.294118, 0.000000}} },0x59 }; 66 | 67 | const SparkPreset preset6{0x0,0x7f,"55D60EB5-1735-4746-B0C4-16C53D8CA203","Country road","0.7","Description for Blues Preset 1","icon.png",120.000000,{ 68 | {"bias.noisegate", true, 2, {0.283019, 0.304245}}, 69 | {"Compressor", false, 2, {0.461066, 0.608902}}, 70 | {"DistortionTS9", true, 3, {0.200747, 0.216084, 0.583560}}, 71 | {"AC Boost", true, 5, {0.707792, 0.591124, 0.383605, 0.532821, 0.195119}}, 72 | {"Tremolo", false, 3, {0.454134, 0.699934, 0.596154}}, 73 | {"DelayRe201", false, 5, {0.331450, 0.348991, 0.672299, 0.453144, 1.000000}}, 74 | {"bias.reverb", true, 7, {0.622826, 0.150000, 0.500000, 0.621429, 0.369905, 0.350000, 0.100000}} },0x57 }; 75 | 76 | const SparkPreset preset7{0x0,0x7f,"2E2928B5-D87E-4346-B58F-145B88C581BE","Blues Ark","0.7","1-Clean","icon.png",120.000000,{ 77 | {"bias.noisegate", true, 3, {0.127897, 0.313185, 0.000000}}, 78 | {"LA2AComp", true, 3, {0.000000, 0.832474, 0.304124}}, 79 | {"DistortionTS9", true, 3, {0.570513, 0.549669, 0.706421}}, 80 | {"Twin", true, 5, {0.679549, 0.371715, 0.593663, 0.676660, 0.479191}}, 81 | {"ChorusAnalog", false, 4, {0.377119, 0.310128, 0.510580, 0.455357}}, 82 | {"DelayMono", true, 5, {0.173729, 0.239186, 0.521186, 0.606780, 1.000000}}, 83 | {"bias.reverb", true, 7, {0.325512, 0.392857, 0.461138, 0.100520, 0.488235, 0.466387, 0.300000}} },0xa3 }; 84 | 85 | const SparkPreset preset8{0x0,0x7f,"BB40E550-77D0-40B1-B0D3-D15D3D0C19EE","Modern Stone","0.7","Description for Rock Preset 1","icon.png",120.000000,{ 86 | {"bias.noisegate", true, 2, {0.271226, 0.370283}}, 87 | {"BlueComp", true, 4, {0.389830, 0.665254, 0.305085, 0.644068}}, 88 | {"Overdrive", false, 3, {0.586207, 0.500288, 0.530172}}, 89 | {"AmericanHighGain", true, 5, {0.616274, 0.431090, 0.419846, 0.495112, 0.850637}}, 90 | {"ChorusAnalog", false, 4, {0.120593, 0.279661, 0.185763, 0.485297}}, 91 | {"DelayMono", false, 5, {0.271017, 0.190613, 0.355976, 0.555932, 0.000000}}, 92 | {"bias.reverb", true, 7, {0.175725, 0.680389, 0.761837, 0.177584, 0.302521, 0.408933, 0.300000}} },0xe6 }; 93 | 94 | const SparkPreset preset9{0x0,0x7f,"BFCFC107-6E80-4F26-8549-F44638856241","Crazy Crue","0.7","1-Clean","icon.png",120.000000,{ 95 | {"bias.noisegate", true, 3, {0.104459, 0.232455, 0.000000}}, 96 | {"Compressor", false, 2, {0.683015, 0.327260}}, 97 | {"DistortionTS9", true, 3, {0.355043, 0.314570, 0.554487}}, 98 | {"YJM100", true, 5, {0.562574, 0.485294, 0.317001, 0.250528, 0.576942}}, 99 | {"ChorusAnalog", false, 4, {0.127396, 0.172299, 0.745763, 0.750884}}, 100 | {"DelayRe201", true, 5, {0.071111, 0.223224, 0.285796, 0.537533, 1.000000}}, 101 | {"bias.reverb", true, 7, {0.147366, 0.506659, 0.417857, 0.268239, 0.602287, 0.594118, 0.000000}} },0xcc }; 102 | 103 | const SparkPreset preset10{0x0,0x7f,"6AF9D829-CEA7-4189-AC80-B3364A563EB4","Dark Soul","0.7","1-Clean","icon.png",120.000000,{ 104 | {"bias.noisegate", true, 3, {0.116817, 0.128289, 0.000000}}, 105 | {"BBEOpticalComp", false, 3, {0.712698, 0.370691, 0.000000}}, 106 | {"Overdrive", true, 3, {0.586207, 0.334725, 0.256692}}, 107 | {"SLO100", true, 5, {0.590909, 0.512066, 0.583825, 0.287179, 0.507674}}, 108 | {"Flanger", false, 3, {0.413793, 0.663043, 0.655172}}, 109 | {"DelayMono", false, 5, {0.215111, 0.192443, 0.478663, 0.400000, 1.000000}}, 110 | {"bias.reverb", true, 7, {0.340062, 0.809783, 0.295483, 0.149187, 0.582143, 0.650000, 0.200000}} },0x58 }; 111 | 112 | const SparkPreset preset11{0x0,0x7f,"7984DF4F-5885-4FE2-9786-F3F31E322E44","British Accent","0.7","Description for Pop Preset 1","icon.png",120.000000,{ 113 | {"bias.noisegate", true, 2, {0.095322, 0.242286}}, 114 | {"Compressor", false, 2, {0.499939, 0.337629}}, 115 | {"Booster", true, 1, {0.602726}}, 116 | {"OrangeAD30", true, 5, {0.620474, 0.312894, 0.484227, 0.527442, 0.492836}}, 117 | {"Cloner", false, 2, {0.500000, 0.000000}}, 118 | {"DelayRe201", false, 5, {0.224783, 0.324451, 0.153894, 0.488644, 1.000000}}, 119 | {"bias.reverb", true, 7, {0.205106, 0.721662, 0.656790, 0.193705, 0.488235, 0.466387, 0.300000}} },0xca }; 120 | 121 | const SparkPreset preset12{0x0,0x7f,"96E26248-0AA3-4D45-B767-8BB9337346C9","Iron Hammer","0.7","Description for Metal Preset 1","icon.png",120.000000,{ 122 | {"bias.noisegate", true, 2, {0.217472, 0.000000}}, 123 | {"Compressor", true, 2, {0.547004, 0.647572}}, 124 | {"DistortionTS9", false, 3, {0.147861, 0.400662, 0.640123}}, 125 | {"SwitchAxeLead", true, 5, {0.572609, 0.352941, 0.374004, 0.460784, 0.705431}}, 126 | {"UniVibe", false, 3, {0.500000, 1.000000, 0.700000}}, 127 | {"DelayRe201", false, 5, {0.207006, 0.281507, 0.411563, 0.461977, 1.000000}}, 128 | {"bias.reverb", false, 7, {0.103649, 0.720854, 0.531337, 0.189948, 0.631056, 0.380978, 0.200000}} },0x49 }; 129 | 130 | const SparkPreset preset13{0x0,0x7f,"82C5B8E8-7889-4302-AC19-74DF543872E1","Millenial Lead","0.7","Description for Metal Preset 1","icon.png",120.000000,{ 131 | {"bias.noisegate", true, 2, {0.209660, 0.064809}}, 132 | {"BlueComp", false, 4, {0.392063, 0.665254, 0.452324, 0.597193}}, 133 | {"DistortionTS9", true, 3, {0.046116, 0.357616, 0.769957}}, 134 | {"BE101", true, 5, {0.587958, 0.343137, 0.475797, 0.394193, 0.875443}}, 135 | {"ChorusAnalog", false, 4, {0.761324, 0.132422, 0.491161, 0.567797}}, 136 | {"DelayMono", true, 5, {0.161777, 0.180514, 0.500135, 0.600000, 1.000000}}, 137 | {"bias.reverb", true, 7, {0.095109, 0.660326, 0.692935, 0.285326, 0.500000, 0.500000, 0.300000}} },0x1 }; 138 | 139 | const SparkPreset preset14{0x0,0x7f,"5F27120E-5119-4923-ADA9-42CCB5B01A95","Heavy Axe","0.7","Description for Metal Preset 1","icon.png",120.000000,{ 140 | {"bias.noisegate", true, 2, {0.108097, 0.051788}}, 141 | {"BBEOpticalComp", false, 3, {0.712698, 0.441540, 0.000000}}, 142 | {"DistortionTS9", true, 3, {0.080701, 0.298013, 0.554487}}, 143 | {"EVH", true, 5, {0.599518, 0.467647, 0.407468, 0.357744, 0.820512}}, 144 | {"Phaser", false, 2, {0.331250, 0.620000}}, 145 | {"DelayMono", false, 5, {0.228444, 0.180514, 0.463325, 0.400000, 1.000000}}, 146 | {"bias.reverb", false, 7, {0.285714, 0.709984, 0.582967, 0.388317, 0.582143, 0.650000, 0.200000}} },0xe4 }; 147 | 148 | const SparkPreset preset15{0x0,0x7f,"961F7F40-77C3-4E98-A694-DF9CA4069955","Dual Train","0.7","Description for Rock Preset 1","icon.png",120.000000,{ 149 | {"bias.noisegate", true, 2, {0.148831, 0.000000}}, 150 | {"BBEOpticalComp", true, 3, {0.707544, 0.526591, 0.000000}}, 151 | {"DistortionTS9", true, 3, {0.016884, 0.370637, 0.719230}}, 152 | {"Rectifier", true, 5, {0.706630, 0.425070, 0.450462, 0.498249, 0.795350}}, 153 | {"Cloner", false, 2, {0.330986, 0.000000}}, 154 | {"DelayMono", false, 5, {0.226572, 0.140636, 0.550847, 0.555932, 0.000000}}, 155 | {"bias.reverb", false, 7, {0.366912, 0.672237, 0.314634, 0.284543, 0.302521, 0.433390, 0.300000}} },0xc6 }; 156 | 157 | const SparkPreset preset16{0x0,0x7f,"94109418-E7D9-4B99-83F7-DDB11CA5847D","Spooky Melody","0.7","Description for Alternative Preset 1","icon.png",120.000000,{ 158 | {"bias.noisegate", true, 2, {0.500000, 1.000000}}, 159 | {"Compressor", true, 2, {0.351691, 0.354167}}, 160 | {"DistortionTS9", false, 3, {0.272170, 0.642384, 0.595924}}, 161 | {"Twin", true, 5, {0.613433, 0.489362, 0.453167, 0.505091, 0.580000}}, 162 | {"UniVibe", true, 3, {0.636598, 0.000000, 0.493814}}, 163 | {"DelayEchoFilt", true, 5, {0.231858, 0.555041, 0.529055, 0.308814, 0.000000}}, 164 | {"bias.reverb", true, 7, {0.963044, 0.232082, 0.176398, 0.224767, 0.228167, 0.357143, 0.500000}} },0x19 }; 165 | 166 | const SparkPreset preset17{0x0,0x7f,"E237C4CF-172B-4D68-AA5B-659F57715658","Fuzzy Jam","0.7","Description for Alternative Preset 1","icon.png",120.000000,{ 167 | {"bias.noisegate", true, 2, {0.500000, 1.000000}}, 168 | {"Compressor", true, 2, {0.435025, 0.647572}}, 169 | {"Fuzz", true, 2, {0.436505, 1.000000}}, 170 | {"ADClean", true, 5, {0.677083, 0.364470, 0.353902, 0.341186, 0.680000}}, 171 | {"UniVibe", false, 3, {0.500000, 1.000000, 0.700000}}, 172 | {"VintageDelay", false, 4, {0.293103, 0.646739, 0.284055, 1.000000}}, 173 | {"bias.reverb", true, 7, {0.493323, 0.293282, 0.520823, 0.398143, 0.469538, 0.455462, 0.600000}} },0x13 }; 174 | 175 | const SparkPreset preset18{0x0,0x7f,"A3601E1D-8018-42A8-9A19-9B6F0DAB6F46","Angry Monkey","0.7","Description for Alternative Preset 1","icon.png",120.000000,{ 176 | {"bias.noisegate", true, 2, {0.500000, 1.000000}}, 177 | {"BlueComp", true, 4, {0.389830, 0.665254, 0.305085, 0.644068}}, 178 | {"GuitarMuff", true, 3, {0.619421, 0.692053, 0.805691}}, 179 | {"94MatchDCV2", true, 5, {0.512397, 0.557982, 0.415584, 0.438462, 0.351240}}, 180 | {"Flanger", true, 3, {1.000000, 0.338540, 0.245856}}, 181 | {"DelayMono", true, 5, {0.096667, 0.101227, 0.395705, 0.320000, 1.000000}}, 182 | {"bias.reverb", true, 7, {0.554341, 0.308929, 0.237733, 0.738432, 0.265140, 0.276786, 0.400000}} },0xe3 }; 183 | 184 | const SparkPreset preset19{0x0,0x7f,"50A3B945-1A86-4E06-B10B-550E3226DDF2","Hide and Seek","0.7","Description for Alternative Preset 1","icon.png",120.000000,{ 185 | {"bias.noisegate", true, 2, {0.500000, 1.000000}}, 186 | {"BlueComp", false, 4, {0.590723, 0.665254, 0.305085, 0.644068}}, 187 | {"Booster", true, 1, {0.668454}}, 188 | {"Bogner", true, 5, {0.655844, 0.626593, 0.640734, 0.351588, 0.338571}}, 189 | {"MiniVibe", false, 2, {0.047057, 0.117188}}, 190 | {"DelayMultiHead", true, 5, {0.706667, 0.644172, 0.564417, 0.650000, 1.000000}}, 191 | {"bias.reverb", true, 7, {0.448518, 0.405932, 0.566185, 0.648674, 0.302521, 0.180672, 0.300000}} },0x45 }; 192 | 193 | const SparkPreset preset20{0x0,0x7f,"3013444B-9929-499F-964D-707E9D8F5FA0","Bass Driver","0.7","Description for Bass Preset 1","icon.png",120.000000,{ 194 | {"bias.noisegate", true, 2, {0.419271, 0.226562}}, 195 | {"BassComp", true, 2, {0.372727, 0.530303}}, 196 | {"SABdriver", true, 4, {0.535256, 1.000000, 0.724359, 1.000000}}, 197 | {"W600", true, 5, {0.664699, 0.423077, 0.276884, 0.415083, 0.448052}}, 198 | {"UniVibe", false, 3, {0.500000, 1.000000, 0.700000}}, 199 | {"VintageDelay", false, 4, {0.359402, 0.400883, 0.350280, 1.000000}}, 200 | {"bias.reverb", true, 7, {0.253261, 0.462500, 0.234401, 0.137733, 0.293818, 0.638043, 0.100000}} },0x91 }; 201 | 202 | const SparkPreset preset21{0x0,0x7f,"D99DC07A-C997-4ABD-833A-0C13EA8BEE5A","Comped Cleaner","0.7","Description for Bass Preset 1","icon.png",120.000000,{ 203 | {"bias.noisegate", true, 2, {0.205729, 0.226562}}, 204 | {"BassComp", true, 2, {0.193040, 0.334991}}, 205 | {"MaestroBassmaster", false, 3, {0.698052, 0.276184, 0.566086}}, 206 | {"GK800", true, 5, {0.688351, 0.407152, 0.399197, 0.746875, 0.774234}}, 207 | {"Cloner", false, 2, {0.248888, 0.000000}}, 208 | {"DelayMono", false, 5, {0.163333, 0.214724, 0.355828, 0.320000, 1.000000}}, 209 | {"bias.reverb", true, 7, {0.168478, 0.744565, 0.130435, 0.288043, 0.323370, 0.293478, 0.600000}} },0x54 }; 210 | 211 | const SparkPreset preset22{0x0,0x7f,"57AF2690-F7C8-4766-9A92-F3A51629B959","Cozy Serenade","0.7","Description for Acoustic Preset 1","icon.png",120.000000,{ 212 | {"bias.noisegate", true, 2, {0.143229, 0.000000}}, 213 | {"BlueComp", true, 4, {0.506411, 0.356543, 0.348913, 0.407461}}, 214 | {"Booster", false, 1, {0.567515}}, 215 | {"FatAcousticV2", true, 5, {0.453955, 0.292760, 0.565172, 0.575339, 0.829431}}, 216 | {"ChorusAnalog", false, 4, {0.761324, 0.236716, 0.745763, 0.431636}}, 217 | {"DelayMono", false, 5, {0.187778, 0.211656, 0.334356, 0.400000, 1.000000}}, 218 | {"bias.reverb", true, 7, {0.315883, 0.652174, 0.111413, 0.357842, 0.339286, 0.489905, 0.700000}} },0x2f }; 219 | 220 | const SparkPreset preset23{0x0,0x7f,"DEFBB271-B3EE-4C7E-A623-2E5CA53B6DDA","Studio Session","0.7","Description for Acoustic Preset 1","icon.png",120.000000,{ 221 | {"bias.noisegate", false, 2, {0.500000, 0.346698}}, 222 | {"BBEOpticalComp", true, 3, {0.758266, 0.258550, 0.000000}}, 223 | {"DistortionTS9", false, 3, {0.139574, 0.407285, 0.689846}}, 224 | {"Acoustic", true, 5, {0.639823, 0.385056, 0.383449, 0.599397, 0.519480}}, 225 | {"ChorusAnalog", true, 4, {0.841681, 0.227514, 0.935947, 0.351279}}, 226 | {"DelayMono", false, 5, {0.223999, 0.211189, 0.490933, 0.600000, 1.000000}}, 227 | {"bias.reverb", true, 7, {0.722819, 0.326169, 0.275776, 0.360714, 0.343944, 0.486025, 0.400000}} },0x23 }; 228 | 229 | const SparkPreset *my_presets[]{&preset0, &preset1, &preset2, &preset3, &preset4, &preset5, &preset6, &preset7, &preset8, &preset9, 230 | &preset10, &preset11, &preset12, &preset13, &preset14, &preset15, &preset16, &preset17, &preset18, &preset19, 231 | &preset20, &preset21, &preset22, &preset23}; 232 | -------------------------------------------------------------------------------- /Spark.ino: -------------------------------------------------------------------------------- 1 | ///// ROUTINES TO SYNC TO AMP SETTINGS 2 | 3 | unsigned long sync_timer; 4 | int selected_preset; 5 | int preset_requested; 6 | bool preset_received; 7 | 8 | int hw_preset_requested; // for UI sync of bank of presets 9 | bool hw_preset_received; 10 | unsigned long hw_preset_timer; 11 | 12 | int get_effect_index(char *str) { 13 | int ind, i; 14 | 15 | ind = -1; 16 | for (i = 0; ind == -1 && i <= 6; i++) { 17 | if (strcmp(presets[5].effects[i].EffectName, str) == 0) { 18 | ind = i; 19 | } 20 | } 21 | return ind; 22 | } 23 | 24 | bool spark_state_tracker_start() { 25 | spark_state = SPARK_DISCONNECTED; 26 | // try to find and connect to Spark - returns false if failed to find Spark 27 | if (!connect_to_all()) return false; 28 | 29 | spark_start(false); // set up the classes to communicate with Spark and app 30 | spark_state = SPARK_CONNECTED; // it has to be to have reached here 31 | 32 | spark_ping_timer = millis(); 33 | preset_received = false; 34 | hw_preset_timer = spark_ping_timer; 35 | hw_preset_received = false; 36 | selected_preset = 0; 37 | ui_update_in_progress = UI_NONE;; 38 | 39 | return true; 40 | } 41 | 42 | // get changes from app or Spark and update internal state to reflect this 43 | // this function has the side-effect of loading cmdsub, msg and preset which can be used later 44 | 45 | bool update_spark_state() { 46 | int pres, ind; 47 | 48 | // sort out connection and sync progress 49 | if (conn_status[SPK] == false && spark_state != SPARK_DISCONNECTED) { 50 | spark_state = SPARK_DISCONNECTED; 51 | spark_ping_timer = millis(); 52 | DEBUG("Spark disconnected, try to reconnect..."); 53 | } 54 | 55 | 56 | if (spark_state == SPARK_DISCONNECTED) { 57 | if (millis() - spark_ping_timer > 500) { 58 | spark_ping_timer = millis(); 59 | connect_spark(); // reconnects if any disconnects happen 60 | } 61 | } 62 | 63 | if (conn_status[SPK] == true && spark_state == SPARK_DISCONNECTED) { 64 | spark_state = SPARK_CONNECTED; 65 | DEBUG("Spark connected"); 66 | } 67 | 68 | if (spark_state == SPARK_CONNECTED) { 69 | if (millis() - spark_ping_timer > 500) { 70 | // every 0.5s ping the Spark amp to see if it will respond 71 | spark_ping_timer = millis(); 72 | spark_msg_out.get_serial(); 73 | DEBUG("Pinging Spark"); 74 | } 75 | } 76 | 77 | if (spark_state == SPARK_SYNCING) { 78 | if (preset_received == true || millis() - sync_timer > 2000) { // we got the preset we were after or time out, and need to ask for the next one 79 | if (preset_received == false) 80 | DEBUG("**** Preset not received, trying again"); 81 | sync_timer = millis(); 82 | spark_msg_out.get_preset_details((preset_requested == 5) ? 0x0100 : preset_requested); 83 | // if (preset_requested <= 3) 84 | // spark_msg_out.change_hardware_preset(0, preset_requested); 85 | preset_received = false; 86 | DEB("Requested a preset: "); 87 | DEBUG(preset_requested); 88 | } 89 | } 90 | 91 | if (spark_state == SPARK_COMMUNICATING) { 92 | spark_msg_out.get_firmware(); 93 | spark_msg_out.get_checksum_info(); 94 | preset_requested = 0; 95 | preset_received = true; 96 | spark_state = SPARK_CHECKSUM; 97 | } 98 | 99 | if ((ui_update_in_progress == UI_HARDWARE) && hw_preset_received && ((millis() - hw_preset_timer) > 1000)) { 100 | hw_preset_received = false; 101 | 102 | DEB("Asking for preset "); 103 | DEBUG(hw_preset_requested); 104 | 105 | app_msg_out.save_hardware_preset(0x00, hw_preset_requested); 106 | app_process(); 107 | //hw_preset_timer = millis(); // only use if wanting delay between each request 108 | hw_preset_requested++; 109 | } 110 | 111 | spark_process(); 112 | app_process(); 113 | 114 | // K&R: Expressions connected by && or || are evaluated left to right, 115 | // and it is guaranteed that evaluation will stop as soon as the truth or falsehood is known. 116 | 117 | if (spark_msg_in.get_message(&cmdsub, &msg, &preset) || app_msg_in.get_message(&cmdsub, &msg, & preset)) { 118 | DEB("Message: "); 119 | DEBUG(cmdsub, HEX); 120 | 121 | // all the processing for sync 122 | switch (cmdsub) { 123 | // if serial number response then we know the connection is good 124 | case 0x0323: 125 | if (spark_state == SPARK_CONNECTED) { 126 | spark_state = SPARK_COMMUNICATING; 127 | DEBUG("Received serial number, got connection"); 128 | } 129 | break; 130 | case 0x032a: 131 | if (spark_state == SPARK_CHECKSUM) { 132 | spark_state = SPARK_SYNCING; 133 | sync_timer = millis(); 134 | DEBUG("Got checksum"); 135 | } 136 | break; 137 | // full preset details 138 | case 0x0101: 139 | case 0x0301: 140 | pres = (preset.preset_num == 0x7f) ? 4 : preset.preset_num; 141 | if (preset.curr_preset == 0x01) 142 | pres = 5; 143 | 144 | presets[pres] = preset; 145 | dump_preset_detail(presets[pres]); 146 | 147 | DEB("Got preset "); 148 | DEBUG(pres); 149 | 150 | if (spark_state == SPARK_SYNCING) { 151 | if (!preset_received) { 152 | if (preset_requested == pres) { 153 | preset_received = true; 154 | preset_requested++; 155 | if (preset_requested == 4) // can't ask for 4! 156 | preset_requested++; 157 | } 158 | } 159 | if (pres == 5) { 160 | spark_state = SPARK_SYNCED; 161 | sp_bin.pass_through = true; 162 | app_bin.pass_through = true; 163 | DEBUG("Fully synced now"); 164 | } 165 | } 166 | 167 | // Only update the displayed preset number for HW presets 168 | if (pres < 4){ 169 | display_preset_num = pres; 170 | } 171 | #ifdef DUMP_ON 172 | DEB("Send / receive new preset: "); 173 | DEBUG(p, HEX); 174 | dump_preset_detail(preset); 175 | #endif 176 | /* 177 | if (cmdsub == 0x0301 && ui_update_in_progress == UI_HARDWARE) 178 | { 179 | int p = (msg.param2 == 0x7f) ? 4 : msg.param2; 180 | if (msg.param1 == 0x01) // shouldn't happen for UI_HARDWARE 181 | p = 5; 182 | 183 | DEBUG("Updating UI - hardware"); 184 | DEB("Preset received "); 185 | DEBUG(p); 186 | 187 | if (p == 3 || hw_preset_requested > 3) { // last preset received 188 | ui_update_in_progress = UI_NONE; 189 | } 190 | else { 191 | delay(2000); 192 | DEB("Asking for preset "); 193 | DEBUG(hw_preset_requested); 194 | app_msg_out.save_hardware_preset(0x00, hw_preset_requested++); 195 | app_process(); 196 | } 197 | } 198 | */ 199 | break; 200 | // change of amp model 201 | case 0x0306: 202 | strcpy(presets[5].effects[3].EffectName, msg.str2); 203 | break; 204 | // change of effect 205 | case 0x0106: 206 | ind = get_effect_index(msg.str1); 207 | if (ind >= 0) strcpy(presets[5].effects[ind].EffectName, msg.str2); 208 | setting_modified = true; 209 | break; 210 | // effect on/off 211 | case 0x0115: 212 | case 0x0315: 213 | ind = get_effect_index(msg.str1); 214 | if (ind >= 0) presets[5].effects[ind].OnOff = msg.onoff; 215 | setting_modified = true; 216 | break; 217 | // change parameter value 218 | case 0x0104: 219 | case 0x0337: 220 | //expression_target = false; 221 | ind = get_effect_index(msg.str1); 222 | if (ind >= 0) presets[5].effects[ind].Parameters[msg.param1] = msg.val; 223 | strcpy(param_str, msg.str1); 224 | param = msg.param1; 225 | setting_modified = true; 226 | break; 227 | 228 | // Send licence key 229 | case 0x0170: 230 | break; 231 | 232 | // change to preset 233 | case 0x0138: 234 | case 0x0338: 235 | selected_preset = (msg.param2 == 0x7f) ? 4 : msg.param2; 236 | presets[5] = presets[selected_preset]; 237 | setting_modified = false; 238 | // Only update the displayed preset number for HW presets 239 | if (selected_preset < 4){ 240 | display_preset_num = selected_preset; 241 | } 242 | break; 243 | // store to preset 244 | case 0x0327: 245 | selected_preset = (msg.param2 == 0x7f) ? 4 : msg.param2; 246 | presets[selected_preset] = presets[5]; 247 | setting_modified = false; 248 | // Only update the displayed preset number for HW presets 249 | if (selected_preset < 4){ 250 | display_preset_num = selected_preset; 251 | } 252 | break; 253 | // current selected preset 254 | case 0x0310: 255 | selected_preset = (msg.param2 == 0x7f) ? 4 : msg.param2; 256 | if (msg.param1 == 0x01) 257 | selected_preset = 5; 258 | presets[5] = presets[selected_preset]; 259 | // Only update the displayed preset number for HW presets 260 | if (selected_preset < 4){ 261 | display_preset_num = selected_preset; 262 | } 263 | break; 264 | // Refresh preset info based on app-requested change 265 | 266 | case 0x0364: 267 | isTunerMode = true; 268 | break; 269 | 270 | case 0x0365: 271 | case 0x0465: 272 | isTunerMode = false; 273 | break; 274 | 275 | case 0x0438: 276 | setting_modified = false; 277 | break; 278 | default: 279 | break; 280 | } 281 | 282 | // all the processing for UI update 283 | switch (cmdsub) { 284 | case 0x0201: 285 | if (ui_update_in_progress == UI_CUSTOM) { 286 | DEBUG("Updating UI - custom"); 287 | 288 | strcpy(presets[5].Name, "SyncPreset"); 289 | strcpy(presets[5].UUID, "F00DF00D-FEED-0123-4567-987654321000"); 290 | presets[5].curr_preset = 0x00; 291 | presets[5].preset_num = 0x03; 292 | app_msg_out.create_preset(&presets[5]); 293 | app_process(); 294 | delay(100); 295 | 296 | app_msg_out.change_hardware_preset(0x00, 0x00); 297 | app_process(); 298 | app_msg_out.change_hardware_preset(0x00, 0x03); 299 | app_process(); 300 | 301 | sp_bin.pass_through = true; 302 | app_bin.pass_through = true; 303 | ui_update_in_progress = UI_NONE; 304 | } 305 | 306 | else if (ui_update_in_progress == UI_HARDWARE) 307 | { 308 | int p = (msg.param2 == 0x7f) ? 4 : msg.param2; 309 | if (msg.param1 == 0x01) // shouldn't happen for UI_HARDWARE 310 | p = 5; 311 | 312 | DEBUG("Updating UI - hardware"); 313 | DEB("Preset received "); 314 | DEBUG(p); 315 | hw_preset_received = true; 316 | 317 | presets[p].curr_preset = 0; 318 | presets[p].preset_num = ((p == 4) ? 0x7f : p); 319 | app_msg_out.create_preset(&presets[p]); 320 | DEB("Preset generated "); 321 | DEBUG(p); 322 | dump_preset_detail(presets[p]); 323 | app_process(); 324 | 325 | if (p >= 3 || hw_preset_requested >= 4) { // last preset received 326 | ui_update_in_progress = UI_NONE; 327 | // flip presets to refresh UI 328 | 329 | // this will add a delay to bank changes so may not be great for a live performance 330 | app_msg_out.change_hardware_preset(0x00, 0x03); 331 | app_process(); 332 | app_msg_out.change_hardware_preset(0x00, 0x00); 333 | app_process(); 334 | 335 | sp_bin.pass_through = true; 336 | app_bin.pass_through = true; 337 | } 338 | } 339 | break; 340 | } 341 | 342 | return true; 343 | } 344 | else 345 | return false; 346 | } 347 | 348 | void update_ui() { 349 | sp_bin.pass_through = false; 350 | app_bin.pass_through = false; 351 | 352 | ui_update_in_progress = UI_CUSTOM; 353 | app_msg_out.save_hardware_preset(0x00, 0x03); 354 | app_process(); 355 | } 356 | 357 | void update_ui_hardware() 358 | { 359 | // prime the loop for asking for the presets 360 | hw_preset_requested = 0; 361 | hw_preset_received = true; 362 | hw_preset_timer = millis(); 363 | ui_update_in_progress = UI_HARDWARE; 364 | 365 | sp_bin.pass_through = false; 366 | app_bin.pass_through = false; 367 | 368 | /* 369 | DEB("Asking for preset "); 370 | DEBUG(hw_preset_requested); 371 | 372 | app_msg_out.save_hardware_preset(0x00, hw_preset_requested); // prime the requests 373 | app_process(); 374 | 375 | hw_preset_requested++; 376 | */ 377 | } 378 | 379 | ///// ROUTINES TO CHANGE AMP SETTINGS 380 | 381 | void change_generic_model(char *new_eff, int slot) { 382 | if (strcmp(presets[5].effects[slot].EffectName, new_eff) != 0) { 383 | spark_msg_out.change_effect(presets[5].effects[slot].EffectName, new_eff); 384 | strcpy(presets[5].effects[slot].EffectName, new_eff); 385 | spark_process(); 386 | delay(100); 387 | } 388 | } 389 | 390 | void change_comp_model(char *new_eff) { 391 | change_generic_model(new_eff, 1); 392 | } 393 | 394 | void change_drive_model(char *new_eff) { 395 | change_generic_model(new_eff, 2); 396 | } 397 | 398 | void change_amp_model(char *new_eff) { 399 | if (strcmp(presets[5].effects[3].EffectName, new_eff) != 0) { 400 | spark_msg_out.change_effect(presets[5].effects[3].EffectName, new_eff); 401 | app_msg_out.change_effect(presets[5].effects[3].EffectName, new_eff); 402 | strcpy(presets[5].effects[3].EffectName, new_eff); 403 | spark_process(); 404 | app_process(); 405 | delay(100); 406 | } 407 | } 408 | 409 | void change_mod_model(char *new_eff) { 410 | change_generic_model(new_eff, 4); 411 | } 412 | 413 | void change_delay_model(char *new_eff) { 414 | change_generic_model(new_eff, 5); 415 | } 416 | 417 | void change_generic_onoff(int slot,bool onoff) { 418 | spark_msg_out.turn_effect_onoff(presets[5].effects[slot].EffectName, onoff); 419 | app_msg_out.turn_effect_onoff(presets[5].effects[slot].EffectName, onoff); 420 | presets[5].effects[slot].OnOff = onoff; 421 | spark_process(); 422 | app_process(); 423 | } 424 | 425 | void change_noisegate_onoff(bool onoff) { 426 | change_generic_onoff(0, onoff); 427 | } 428 | 429 | void change_comp_onoff(bool onoff) { 430 | change_generic_onoff(1, onoff); 431 | } 432 | 433 | void change_drive_onoff(bool onoff) { 434 | change_generic_onoff(2, onoff); 435 | } 436 | 437 | void change_amp_onoff(bool onoff) { 438 | change_generic_onoff(3, onoff); 439 | } 440 | 441 | void change_mod_onoff(bool onoff) { 442 | change_generic_onoff(4, onoff); 443 | } 444 | 445 | void change_delay_onoff(bool onoff) { 446 | change_generic_onoff(5, onoff); 447 | } 448 | 449 | void change_reverb_onoff(bool onoff) { 450 | change_generic_onoff(6, onoff); 451 | } 452 | 453 | void change_generic_toggle(int slot) { 454 | bool new_onoff; 455 | 456 | new_onoff = !presets[5].effects[slot].OnOff; 457 | 458 | spark_msg_out.turn_effect_onoff(presets[5].effects[slot].EffectName, new_onoff); 459 | app_msg_out.turn_effect_onoff(presets[5].effects[slot].EffectName, new_onoff); 460 | presets[5].effects[slot].OnOff = new_onoff; 461 | spark_process(); 462 | app_process(); 463 | } 464 | 465 | void change_noisegate_toggle() { 466 | change_generic_toggle(0); 467 | } 468 | 469 | void change_comp_toggle() { 470 | change_generic_toggle(1); 471 | } 472 | 473 | void change_drive_toggle() { 474 | change_generic_toggle(2); 475 | } 476 | 477 | void change_amp_toggle() { 478 | change_generic_toggle(3); 479 | } 480 | 481 | void change_mod_toggle() { 482 | change_generic_toggle(4); 483 | } 484 | 485 | void change_delay_toggle() { 486 | change_generic_toggle(5); 487 | } 488 | 489 | void change_reverb_toggle() { 490 | change_generic_toggle(6); 491 | } 492 | 493 | void change_generic_param(int slot, int param, float val) { 494 | /* float diff; 495 | 496 | // some code to reduce the number of changes 497 | diff = presets[5].effects[slot].Parameters[param] - val; 498 | if (diff < 0) diff = -diff; 499 | if (diff > 0.04) { 500 | */ 501 | { 502 | spark_msg_out.change_effect_parameter(presets[5].effects[slot].EffectName, param, val); 503 | app_msg_out.change_effect_parameter(presets[5].effects[slot].EffectName, param, val); 504 | presets[5].effects[slot].Parameters[param] = val; 505 | spark_process(); 506 | app_process(); 507 | } 508 | } 509 | 510 | void change_noisegate_param(int param, float val) { 511 | change_generic_param(0, param, val); 512 | } 513 | 514 | void change_comp_param(int param, float val) { 515 | change_generic_param(1, param, val); 516 | } 517 | 518 | void change_drive_param(int param, float val) { 519 | change_generic_param(2, param, val); 520 | } 521 | 522 | void change_amp_param(int param, float val) { 523 | change_generic_param(3, param, val); 524 | } 525 | 526 | void change_mod_param(int param, float val) { 527 | change_generic_param(4, param, val); 528 | } 529 | 530 | void change_delay_param(int param, float val) { 531 | change_generic_param(5, param, val); 532 | } 533 | 534 | void change_reverb_param(int param, float val){ 535 | change_generic_param(6, param, val); 536 | } 537 | 538 | void change_hardware_preset(int pres_num) { 539 | if (pres_num >= 0 && pres_num <= 3) { 540 | presets[5] = presets[pres_num]; 541 | 542 | spark_msg_out.change_hardware_preset(0, pres_num); 543 | app_msg_out.change_hardware_preset(0, pres_num); 544 | spark_process(); 545 | app_process(); 546 | } 547 | } 548 | 549 | void change_custom_preset(SparkPreset *preset, int pres_num) { 550 | if (pres_num >= 0 && pres_num <= 4) { 551 | preset->preset_num = (pres_num < 4) ? pres_num : 0x7f; 552 | presets[5] = *preset; 553 | presets[pres_num] = *preset; 554 | 555 | spark_msg_out.create_preset(preset); 556 | spark_msg_out.change_hardware_preset(0, preset->preset_num); 557 | } 558 | } 559 | 560 | void tuner_on_off(bool on_off) { 561 | spark_msg_out.tuner_on_off(on_off); 562 | spark_process(); 563 | } 564 | -------------------------------------------------------------------------------- /bitmaps.h: -------------------------------------------------------------------------------- 1 | // Icon bitmap definitions - David Thompson 2021 2 | 3 | #ifndef TWOCOLOUR 4 | // Full-sized tuner version 5 | #define tuner_width 128 6 | #define tuner_height 64 7 | const uint8_t tuner_bits[] = { 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x7F, 0x00, 0x00, 0x00, 9 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 10 | 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0xC0, 0xFF, 0x60, 0x06, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x60, 0x06, 0xE0, 0x1F, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x08, 0x60, 14 | 0x06, 0x10, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0xC0, 0x0F, 0x00, 0x60, 0x06, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x60, 0x06, 0x00, 0xC0, 0x0F, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x60, 18 | 0x06, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x9F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF9, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x80, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC0, 21 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x06, 0x00, 0x00, 22 | 0x00, 0x00, 0x60, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 28 | 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 32 | 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 36 | 0x00, 0x00, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x00, 0xE7, 0x00, 0x00, 0x00, 0x80, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x01, 0x00, 0x00, 0xC0, 0x01, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 40 | 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x70, 0x00, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 44 | 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x1C, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1C, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 48 | 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x34, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 50 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x07, 0x00, 0x00, 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 52 | 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 56 | 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x80, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x00, 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 60 | 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 0x00, 0x00, 0x00, 0x07, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0xF0, 0x03, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 64 | 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0xC0, 0x0C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 68 | 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0x18, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0C, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 72 | 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 74 | 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0E, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0xC0, 0x03, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 76 | 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x07, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x68, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 78 | 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0x38, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 80 | 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 82 | 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 84 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x60, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 86 | 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x06, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 88 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 90 | 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 92 | 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0xFF, }; 94 | #else 95 | // Slim tuner version 96 | #define tuner_width 128 97 | #define tuner_height 48 98 | const uint8_t tuner_bits[] = 99 | 100 | { 101 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 102 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x60, 103 | 0x06, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 104 | 0x00, 0xF8, 0x03, 0x60, 0x06, 0xC0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 105 | 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x04, 0x60, 0x06, 0x20, 0xF0, 0x03, 106 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x60, 107 | 0x06, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 108 | 0xFC, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 109 | 0x00, 0x00, 0x00, 0x80, 0xC3, 0x01, 0x00, 0x60, 0x06, 0x00, 0x80, 0xC3, 110 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x81, 0x01, 0x00, 0x60, 111 | 0x06, 0x00, 0x80, 0x81, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 112 | 0x80, 0x01, 0x00, 0x60, 0x06, 0x00, 0x80, 0x01, 0x1E, 0x00, 0x00, 0x00, 113 | 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 114 | 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x27, 0x00, 0x00, 0x00, 0x00, 115 | 0x00, 0x00, 0x00, 0x00, 0xE4, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 116 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 117 | 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 118 | 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 119 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 120 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 121 | 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 122 | 0x00, 0xEE, 0x00, 0x00, 0x00, 0x80, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0x01, 0x00, 0x00, 0xC0, 0xC1, 0x00, 124 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x03, 0x00, 125 | 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 126 | 0x00, 0x00, 0x06, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 127 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x38, 0x00, 0x00, 128 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 129 | 0x00, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 130 | 0x00, 0x00, 0x32, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 131 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x06, 0x00, 0x00, 132 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 133 | 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 134 | 0x00, 0x00, 0xC0, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x80, 0x01, 0x00, 0x00, 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 137 | 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 139 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x01, 0x00, 0x00, 140 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 141 | 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 142 | 0x00, 0x00, 0xC0, 0x0F, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 143 | 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0C, 0x38, 0x00, 0x00, 0x00, 144 | 0x00, 0x00, 0x00, 0xFE, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 145 | 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0F, 0xF0, 0x01, 0x00, 0x00, 146 | 0x00, 0x00, 0x00, 0x18, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 147 | 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0C, 0x00, 0x00, 0x00, 148 | 0x00, 0x00, 0xE0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 149 | 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00, 150 | 0x00, 0x00, 0x00, 0x30, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 151 | 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x0E, 0x00, 0x00, 0x00, 152 | 0x00, 0x00, 0x1C, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 153 | 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x00, 154 | 0x00, 0x00, 0x00, 0x68, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 155 | 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 156 | 0x00, 0x00, 0x07, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 157 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 158 | 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 159 | 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 160 | 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 161 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 162 | 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 163 | 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 164 | 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0xF8, 0xFF, 165 | }; 166 | 167 | 168 | /*{ 169 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 170 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x70, 171 | 0x0E, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172 | 0x00, 0xF8, 0x0F, 0x70, 0x0E, 0xF0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 173 | 0x00, 0x00, 0x00, 0x00, 0xC0, 0x0F, 0x08, 0x70, 0x0E, 0x10, 0xF0, 0x03, 174 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x08, 0x70, 175 | 0x0E, 0x10, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 176 | 0xFC, 0x01, 0x10, 0x70, 0x0E, 0x08, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 177 | 0x00, 0x00, 0x00, 0x80, 0x03, 0x03, 0x10, 0x70, 0x0E, 0x08, 0xC0, 0xC0, 178 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x06, 0x10, 0x70, 179 | 0x0E, 0x08, 0x60, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 180 | 0x00, 0x06, 0x10, 0x70, 0x0E, 0x08, 0x60, 0x00, 0x1E, 0x00, 0x00, 0x00, 181 | 0x00, 0x00, 0x00, 0x4E, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 182 | 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x87, 0x00, 0x0C, 0x00, 0x00, 183 | 0x00, 0x00, 0x30, 0x00, 0xE1, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x81, 184 | 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x81, 0x03, 0x00, 0x00, 185 | 0x00, 0x00, 0x70, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 186 | 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x02, 0x00, 0x00, 0x00, 187 | 0x00, 0x00, 0x00, 0x40, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 188 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x38, 0x00, 0x00, 189 | 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 190 | 0x00, 0xEE, 0x00, 0x00, 0x00, 0x80, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 191 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0x01, 0x00, 0x00, 0xC0, 0xC1, 0x01, 192 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x83, 0x03, 0x00, 193 | 0x00, 0x60, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 194 | 0xE0, 0x00, 0x06, 0x00, 0x00, 0x70, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 195 | 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x0E, 0x00, 0x00, 0x38, 0x00, 0x00, 196 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 197 | 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 198 | 0x00, 0x00, 0x34, 0x00, 0x00, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 199 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x06, 0x03, 0x00, 200 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x60, 0x00, 201 | 0x00, 0x03, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 202 | 0x00, 0x30, 0xC0, 0x00, 0x80, 0x03, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 203 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xC0, 0x01, 0x80, 0x01, 0x00, 0x00, 204 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 205 | 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 206 | 0x00, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 207 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x60, 0x00, 0x00, 0x00, 208 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 209 | 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 210 | 0x00, 0x00, 0xE0, 0x0F, 0x30, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 211 | 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x0C, 0x38, 0xFC, 0x00, 0x00, 212 | 0x00, 0x00, 0x00, 0xFE, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x1C, 213 | 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0F, 0xF0, 0x01, 0x00, 0x00, 214 | 0x00, 0x00, 0x00, 0x18, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 215 | 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0C, 0x00, 0x00, 0x00, 216 | 0x00, 0x00, 0xE0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 217 | 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00, 218 | 0x00, 0x00, 0x00, 0x30, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 219 | 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xFE, 0x00, 0x00, 0x00, 220 | 0x00, 0x00, 0x1C, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 221 | 0x86, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x70, 0x00, 0x00, 222 | 0x00, 0x00, 0xF8, 0x61, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 223 | 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 224 | 0x00, 0x00, 0x07, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 225 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00, 226 | 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 227 | 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 228 | 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 229 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 230 | 0x00, 0x00, 0x00, 0xC0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 231 | 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 232 | 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0xF8, 0xFF, 233 | };*/ 234 | #endif 235 | 236 | // bitmaps (Copych's add-on 2022) 237 | #define s_bt_width 9 238 | #define s_bt_height 16 239 | const uint8_t s_bt_bits[] = { 240 | 0x00,0xfe,0x00,0xfe,0x78,0xfe,0x04,0xfe,0x38,0xfe,0x40,0xfe, 241 | 0x3c,0xfe,0x00,0xfe,0x10,0xfe,0x30,0xfe,0x58,0xfe,0x30,0xfe, 242 | 0x58,0xfe,0x30,0xfe,0x10,0xfe,0x00,0xfe}; 243 | 244 | #define a_bt_width 9 245 | #define a_bt_height 16 246 | const uint8_t a_bt_bits[] = { 247 | 0x00,0xfe,0x00,0xfe,0x10,0xfe,0x28,0xfe,0x44,0xfe,0x7c,0xfe, 248 | 0x44,0xfe,0x00,0xfe,0x10,0xfe,0x30,0xfe,0x58,0xfe,0x30,0xfe, 249 | 0x58,0xfe,0x30,0xfe,0x10,0xfe,0x00,0xfe}; 250 | 251 | #define wifi_width 9 252 | #define wifi_height 16 253 | const uint8_t wifi_bits[] = { 254 | 0x00,0xfe,0x00,0xfe,0x44,0xfe,0x54,0xfe,0x54,0xfe,0x28,0xfe, 255 | 0x28,0xfe,0x00,0xfe,0x00,0xfe,0x7c,0xfe,0x82,0xfe,0x38,0xfe, 256 | 0x44,0xfe,0x10,0xfe,0x00,0xfe,0x00,0xfe}; 257 | 258 | #define dr_width 16 259 | #define dr_height 16 260 | const uint8_t dr_bits[] = { 261 | 0x00,0x00,0x00,0x00,0x7c,0x00,0x88,0x00,0x88,0x00,0x88,0x00, 262 | 0x88,0x00,0x88,0x00,0x88,0x00,0x7c,0x1a,0x00,0x26,0x00,0x02, 263 | 0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00}; 264 | 265 | #define md_width 16 266 | #define md_height 16 267 | const uint8_t md_bits[] = { 268 | 0x00,0x00,0x00,0x00,0x04,0x01,0x8c,0x01,0x54,0x01,0x24,0x01, 269 | 0x04,0x21,0x04,0x21,0x04,0x21,0x00,0x3c,0x00,0x22,0x00,0x22, 270 | 0x00,0x22,0x00,0x3c,0x00,0x00,0x00,0x00}; 271 | 272 | #define rv_width 16 273 | #define rv_height 16 274 | const uint8_t rv_bits[] = { 275 | 0x00,0x00,0x00,0x00,0x7c,0x00,0x84,0x00,0x84,0x00,0x84,0x00, 276 | 0x7c,0x00,0x24,0x00,0x44,0x00,0x84,0x22,0x00,0x22,0x00,0x22, 277 | 0x00,0x14,0x00,0x08,0x00,0x00,0x00,0x00}; 278 | 279 | #define dy_width 16 280 | #define dy_height 16 281 | const uint8_t dy_bits[] = { 282 | 0x00,0x00,0x00,0x00,0x7c,0x00,0x88,0x00,0x88,0x00,0x88,0x00, 283 | 0x88,0x00,0x88,0x00,0x88,0x22,0x7c,0x22,0x00,0x22,0x00,0x3c, 284 | 0x00,0x20,0x00,0x1c,0x00,0x00,0x00,0x00}; 285 | 286 | #define chrg_width 9 287 | #define chrg_height 16 288 | const uint8_t chrg_bits[] = { 289 | 0x00,0xfe,0x00,0xfe,0x00,0xfe,0xf8,0xfe,0x78,0xfe,0x3c,0xfe, 290 | 0x1c,0xfe,0x7e,0xfe,0x38,0xfe,0x18,0xfe,0x0c,0xfe,0x04,0xfe, 291 | 0x02,0xfe,0x00,0xfe,0x00,0xfe,0x00,0xfe}; 292 | -------------------------------------------------------------------------------- /Parsing.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Parsing.cpp - HTTP request parsing. 3 | 4 | Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) 20 | */ 21 | 22 | #include 23 | #include 24 | #include "WiFiServer.h" 25 | #include "WiFiClient.h" 26 | #include "WebServer.h" 27 | #include "mimetable.h" 28 | 29 | #ifndef WEBSERVER_MAX_POST_ARGS 30 | #define WEBSERVER_MAX_POST_ARGS 32 31 | #endif 32 | 33 | static const char Content_Type[] PROGMEM = "Content-Type"; 34 | static const char filename[] PROGMEM = "filename"; 35 | 36 | static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms) 37 | { 38 | char *buf = nullptr; 39 | dataLength = 0; 40 | while (dataLength < maxLength) { 41 | int tries = timeout_ms; 42 | size_t newLength; 43 | while (!(newLength = client.available()) && tries--) delay(1); 44 | if (!newLength) { 45 | break; 46 | } 47 | if (!buf) { 48 | buf = (char *) malloc(newLength + 1); 49 | if (!buf) { 50 | return nullptr; 51 | } 52 | } 53 | else { 54 | char* newBuf = (char *) realloc(buf, dataLength + newLength + 1); 55 | if (!newBuf) { 56 | free(buf); 57 | return nullptr; 58 | } 59 | buf = newBuf; 60 | } 61 | client.readBytes(buf + dataLength, newLength); 62 | dataLength += newLength; 63 | buf[dataLength] = '\0'; 64 | } 65 | return buf; 66 | } 67 | 68 | bool WebServer::_parseRequest(WiFiClient& client) { 69 | // Read the first line of HTTP request 70 | String req = client.readStringUntil('\r'); 71 | client.readStringUntil('\n'); 72 | //reset header value 73 | for (int i = 0; i < _headerKeysCount; ++i) { 74 | _currentHeaders[i].value =String(); 75 | } 76 | 77 | // First line of HTTP request looks like "GET /path HTTP/1.1" 78 | // Retrieve the "/path" part by finding the spaces 79 | int addr_start = req.indexOf(' '); 80 | int addr_end = req.indexOf(' ', addr_start + 1); 81 | if (addr_start == -1 || addr_end == -1) { 82 | log_e("Invalid request: %s", req.c_str()); 83 | return false; 84 | } 85 | 86 | String methodStr = req.substring(0, addr_start); 87 | String url = req.substring(addr_start + 1, addr_end); 88 | String versionEnd = req.substring(addr_end + 8); 89 | _currentVersion = atoi(versionEnd.c_str()); 90 | String searchStr = ""; 91 | int hasSearch = url.indexOf('?'); 92 | if (hasSearch != -1){ 93 | searchStr = url.substring(hasSearch + 1); 94 | url = url.substring(0, hasSearch); 95 | } 96 | _currentUri = url; 97 | _chunked = false; 98 | 99 | HTTPMethod method = HTTP_GET; 100 | if (methodStr == F("POST")) { 101 | method = HTTP_POST; 102 | } else if (methodStr == F("DELETE")) { 103 | method = HTTP_DELETE; 104 | } else if (methodStr == F("OPTIONS")) { 105 | method = HTTP_OPTIONS; 106 | } else if (methodStr == F("PUT")) { 107 | method = HTTP_PUT; 108 | } else if (methodStr == F("PATCH")) { 109 | method = HTTP_PATCH; 110 | } 111 | _currentMethod = method; 112 | 113 | log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str()); 114 | 115 | //attach handler 116 | RequestHandler* handler; 117 | for (handler = _firstHandler; handler; handler = handler->next()) { 118 | if (handler->canHandle(_currentMethod, _currentUri)) 119 | break; 120 | } 121 | _currentHandler = handler; 122 | 123 | String formData; 124 | // below is needed only when POST type request 125 | if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){ 126 | String boundaryStr; 127 | String headerName; 128 | String headerValue; 129 | bool isForm = false; 130 | bool isEncoded = false; 131 | uint32_t contentLength = 0; 132 | //parse headers 133 | while(1){ 134 | req = client.readStringUntil('\r'); 135 | client.readStringUntil('\n'); 136 | if (req == "") break;//no moar headers 137 | int headerDiv = req.indexOf(':'); 138 | if (headerDiv == -1){ 139 | break; 140 | } 141 | headerName = req.substring(0, headerDiv); 142 | headerValue = req.substring(headerDiv + 1); 143 | headerValue.trim(); 144 | _collectHeader(headerName.c_str(),headerValue.c_str()); 145 | 146 | log_v("headerName: %s", headerName.c_str()); 147 | log_v("headerValue: %s", headerValue.c_str()); 148 | 149 | if (headerName.equalsIgnoreCase(FPSTR(Content_Type))){ 150 | using namespace mime; 151 | if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))){ 152 | isForm = false; 153 | } else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))){ 154 | isForm = false; 155 | isEncoded = true; 156 | } else if (headerValue.startsWith(F("multipart/"))){ 157 | boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); 158 | boundaryStr.replace("\"",""); 159 | isForm = true; 160 | } 161 | } else if (headerName.equalsIgnoreCase(F("Content-Length"))){ 162 | contentLength = headerValue.toInt(); 163 | } else if (headerName.equalsIgnoreCase(F("Host"))){ 164 | _hostHeader = headerValue; 165 | } 166 | } 167 | 168 | if (!isForm){ 169 | size_t plainLength; 170 | char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT); 171 | if (plainLength < contentLength) { 172 | free(plainBuf); 173 | return false; 174 | } 175 | if (contentLength > 0) { 176 | if(isEncoded){ 177 | //url encoded form 178 | if (searchStr != "") searchStr += '&'; 179 | searchStr += plainBuf; 180 | } 181 | _parseArguments(searchStr); 182 | if(!isEncoded){ 183 | //plain post json or other data 184 | RequestArgument& arg = _currentArgs[_currentArgCount++]; 185 | arg.key = F("plain"); 186 | arg.value = String(plainBuf); 187 | } 188 | 189 | log_v("Plain: %s", plainBuf); 190 | free(plainBuf); 191 | } else { 192 | // No content - but we can still have arguments in the URL. 193 | _parseArguments(searchStr); 194 | } 195 | } 196 | 197 | if (isForm){ 198 | _parseArguments(searchStr); 199 | if (!_parseForm(client, boundaryStr, contentLength)) { 200 | return false; 201 | } 202 | } 203 | } else { 204 | String headerName; 205 | String headerValue; 206 | //parse headers 207 | while(1){ 208 | req = client.readStringUntil('\r'); 209 | client.readStringUntil('\n'); 210 | if (req == "") break;//no moar headers 211 | int headerDiv = req.indexOf(':'); 212 | if (headerDiv == -1){ 213 | break; 214 | } 215 | headerName = req.substring(0, headerDiv); 216 | headerValue = req.substring(headerDiv + 2); 217 | _collectHeader(headerName.c_str(),headerValue.c_str()); 218 | 219 | log_v("headerName: %s", headerName.c_str()); 220 | log_v("headerValue: %s", headerValue.c_str()); 221 | 222 | if (headerName.equalsIgnoreCase("Host")){ 223 | _hostHeader = headerValue; 224 | } 225 | } 226 | _parseArguments(searchStr); 227 | } 228 | client.flush(); 229 | 230 | log_v("Request: %s", url.c_str()); 231 | log_v(" Arguments: %s", searchStr.c_str()); 232 | 233 | return true; 234 | } 235 | 236 | bool WebServer::_collectHeader(const char* headerName, const char* headerValue) { 237 | for (int i = 0; i < _headerKeysCount; i++) { 238 | if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { 239 | _currentHeaders[i].value=headerValue; 240 | return true; 241 | } 242 | } 243 | return false; 244 | } 245 | 246 | void WebServer::_parseArguments(String data) { 247 | log_v("args: %s", data.c_str()); 248 | if (_currentArgs) 249 | delete[] _currentArgs; 250 | _currentArgs = 0; 251 | if (data.length() == 0) { 252 | _currentArgCount = 0; 253 | _currentArgs = new RequestArgument[1]; 254 | return; 255 | } 256 | _currentArgCount = 1; 257 | 258 | for (int i = 0; i < (int)data.length(); ) { 259 | i = data.indexOf('&', i); 260 | if (i == -1) 261 | break; 262 | ++i; 263 | ++_currentArgCount; 264 | } 265 | log_v("args count: %d", _currentArgCount); 266 | 267 | _currentArgs = new RequestArgument[_currentArgCount+1]; 268 | int pos = 0; 269 | int iarg; 270 | for (iarg = 0; iarg < _currentArgCount;) { 271 | int equal_sign_index = data.indexOf('=', pos); 272 | int next_arg_index = data.indexOf('&', pos); 273 | log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index); 274 | if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) { 275 | log_e("arg missing value: %d", iarg); 276 | if (next_arg_index == -1) 277 | break; 278 | pos = next_arg_index + 1; 279 | continue; 280 | } 281 | RequestArgument& arg = _currentArgs[iarg]; 282 | arg.key = urlDecode(data.substring(pos, equal_sign_index)); 283 | arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index)); 284 | log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str()); 285 | ++iarg; 286 | if (next_arg_index == -1) 287 | break; 288 | pos = next_arg_index + 1; 289 | } 290 | _currentArgCount = iarg; 291 | log_v("args count: %d", _currentArgCount); 292 | 293 | } 294 | 295 | void WebServer::_uploadWriteByte(uint8_t b){ 296 | if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN){ 297 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 298 | _currentHandler->upload(*this, _currentUri, *_currentUpload); 299 | _currentUpload->totalSize += _currentUpload->currentSize; 300 | _currentUpload->currentSize = 0; 301 | } 302 | _currentUpload->buf[_currentUpload->currentSize++] = b; 303 | } 304 | 305 | int WebServer::_uploadReadByte(WiFiClient& client){ 306 | if (!client.connected()) return -1; 307 | int res = client.read(); 308 | if(res < 0) { 309 | // keep trying until you either read a valid byte or timeout 310 | unsigned long startMillis = millis(); 311 | long timeoutIntervalMillis = client.getTimeout(); 312 | boolean timedOut = false; 313 | for(;;) { 314 | // loosely modeled after blinkWithoutDelay pattern 315 | while(!timedOut && !client.available() && client.connected()){ 316 | delay(2); 317 | timedOut = millis() - startMillis >= timeoutIntervalMillis; 318 | } 319 | 320 | res = client.read(); 321 | if(res >= 0) { 322 | return res; // exit on a valid read 323 | } 324 | // NOTE: it is possible to get here and have all of the following 325 | // assertions hold true 326 | // 327 | // -- client.available() > 0 328 | // -- client.connected == true 329 | // -- res == -1 330 | // 331 | // a simple retry strategy overcomes this which is to say the 332 | // assertion is not permanent, but the reason that this works 333 | // is elusive, and possibly indicative of a more subtle underlying 334 | // issue 335 | 336 | timedOut = millis() - startMillis >= timeoutIntervalMillis; 337 | if(timedOut) { 338 | return res; // exit on a timeout 339 | } 340 | } 341 | } 342 | 343 | return res; 344 | } 345 | 346 | bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){ 347 | (void) len; 348 | log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len); 349 | String line; 350 | int retry = 0; 351 | do { 352 | line = client.readStringUntil('\r'); 353 | ++retry; 354 | } while (line.length() == 0 && retry < 3); 355 | 356 | client.readStringUntil('\n'); 357 | //start reading the form 358 | if (line == ("--"+boundary)){ 359 | if(_postArgs) delete[] _postArgs; 360 | _postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS]; 361 | _postArgsLen = 0; 362 | while(1){ 363 | String argName; 364 | String argValue; 365 | String argType; 366 | String argFilename; 367 | bool argIsFile = false; 368 | 369 | line = client.readStringUntil('\r'); 370 | client.readStringUntil('\n'); 371 | if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ 372 | int nameStart = line.indexOf('='); 373 | if (nameStart != -1){ 374 | argName = line.substring(nameStart+2); 375 | nameStart = argName.indexOf('='); 376 | if (nameStart == -1){ 377 | argName = argName.substring(0, argName.length() - 1); 378 | } else { 379 | argFilename = argName.substring(nameStart+2, argName.length() - 1); 380 | argName = argName.substring(0, argName.indexOf('"')); 381 | argIsFile = true; 382 | log_v("PostArg FileName: %s",argFilename.c_str()); 383 | //use GET to set the filename if uploading using blob 384 | if (argFilename == F("blob") && hasArg(FPSTR(filename))) 385 | argFilename = arg(FPSTR(filename)); 386 | } 387 | log_v("PostArg Name: %s", argName.c_str()); 388 | using namespace mime; 389 | argType = FPSTR(mimeTable[txt].mimeType); 390 | line = client.readStringUntil('\r'); 391 | client.readStringUntil('\n'); 392 | if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){ 393 | argType = line.substring(line.indexOf(':')+2); 394 | //skip next line 395 | client.readStringUntil('\r'); 396 | client.readStringUntil('\n'); 397 | } 398 | log_v("PostArg Type: %s", argType.c_str()); 399 | if (!argIsFile){ 400 | while(1){ 401 | line = client.readStringUntil('\r'); 402 | client.readStringUntil('\n'); 403 | if (line.startsWith("--"+boundary)) break; 404 | if (argValue.length() > 0) argValue += "\n"; 405 | argValue += line; 406 | } 407 | log_v("PostArg Value: %s", argValue.c_str()); 408 | 409 | RequestArgument& arg = _postArgs[_postArgsLen++]; 410 | arg.key = argName; 411 | arg.value = argValue; 412 | 413 | if (line == ("--"+boundary+"--")){ 414 | log_v("Done Parsing POST"); 415 | break; 416 | } 417 | } else { 418 | _currentUpload.reset(new HTTPUpload()); 419 | _currentUpload->status = UPLOAD_FILE_START; 420 | _currentUpload->name = argName; 421 | _currentUpload->filename = argFilename; 422 | _currentUpload->type = argType; 423 | _currentUpload->totalSize = 0; 424 | _currentUpload->currentSize = 0; 425 | log_v("Start File: %s Type: %s", _currentUpload->filename.c_str(), _currentUpload->type.c_str()); 426 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 427 | _currentHandler->upload(*this, _currentUri, *_currentUpload); 428 | _currentUpload->status = UPLOAD_FILE_WRITE; 429 | int argByte = _uploadReadByte(client); 430 | readfile: 431 | 432 | while(argByte != 0x0D){ 433 | if(argByte < 0) return _parseFormUploadAborted(); 434 | _uploadWriteByte(argByte); 435 | argByte = _uploadReadByte(client); 436 | } 437 | 438 | argByte = _uploadReadByte(client); 439 | if(argByte < 0) return _parseFormUploadAborted(); 440 | if (argByte == 0x0A){ 441 | argByte = _uploadReadByte(client); 442 | if(argByte < 0) return _parseFormUploadAborted(); 443 | if ((char)argByte != '-'){ 444 | //continue reading the file 445 | _uploadWriteByte(0x0D); 446 | _uploadWriteByte(0x0A); 447 | goto readfile; 448 | } else { 449 | argByte = _uploadReadByte(client); 450 | if(argByte < 0) return _parseFormUploadAborted(); 451 | if ((char)argByte != '-'){ 452 | //continue reading the file 453 | _uploadWriteByte(0x0D); 454 | _uploadWriteByte(0x0A); 455 | _uploadWriteByte((uint8_t)('-')); 456 | goto readfile; 457 | } 458 | } 459 | 460 | uint8_t endBuf[boundary.length()]; 461 | client.readBytes(endBuf, boundary.length()); 462 | 463 | if (strstr((const char*)endBuf, boundary.c_str()) != NULL){ 464 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 465 | _currentHandler->upload(*this, _currentUri, *_currentUpload); 466 | _currentUpload->totalSize += _currentUpload->currentSize; 467 | _currentUpload->status = UPLOAD_FILE_END; 468 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 469 | _currentHandler->upload(*this, _currentUri, *_currentUpload); 470 | log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), _currentUpload->totalSize); 471 | line = client.readStringUntil(0x0D); 472 | client.readStringUntil(0x0A); 473 | if (line == "--"){ 474 | log_v("Done Parsing POST"); 475 | break; 476 | } 477 | continue; 478 | } else { 479 | _uploadWriteByte(0x0D); 480 | _uploadWriteByte(0x0A); 481 | _uploadWriteByte((uint8_t)('-')); 482 | _uploadWriteByte((uint8_t)('-')); 483 | uint32_t i = 0; 484 | while(i < boundary.length()){ 485 | _uploadWriteByte(endBuf[i++]); 486 | } 487 | argByte = _uploadReadByte(client); 488 | goto readfile; 489 | } 490 | } else { 491 | _uploadWriteByte(0x0D); 492 | goto readfile; 493 | } 494 | break; 495 | } 496 | } 497 | } 498 | } 499 | 500 | int iarg; 501 | int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount)?(WEBSERVER_MAX_POST_ARGS - _postArgsLen):_currentArgCount; 502 | for (iarg = 0; iarg < totalArgs; iarg++){ 503 | RequestArgument& arg = _postArgs[_postArgsLen++]; 504 | arg.key = _currentArgs[iarg].key; 505 | arg.value = _currentArgs[iarg].value; 506 | } 507 | if (_currentArgs) delete[] _currentArgs; 508 | _currentArgs = new RequestArgument[_postArgsLen]; 509 | for (iarg = 0; iarg < _postArgsLen; iarg++){ 510 | RequestArgument& arg = _currentArgs[iarg]; 511 | arg.key = _postArgs[iarg].key; 512 | arg.value = _postArgs[iarg].value; 513 | } 514 | _currentArgCount = iarg; 515 | if (_postArgs) { 516 | delete[] _postArgs; 517 | _postArgs=nullptr; 518 | _postArgsLen = 0; 519 | } 520 | return true; 521 | } 522 | log_e("Error: line: %s", line.c_str()); 523 | return false; 524 | } 525 | 526 | String WebServer::urlDecode(const String& text) 527 | { 528 | String decoded = ""; 529 | char temp[] = "0x00"; 530 | unsigned int len = text.length(); 531 | unsigned int i = 0; 532 | while (i < len) 533 | { 534 | char decodedChar; 535 | char encodedChar = text.charAt(i++); 536 | if ((encodedChar == '%') && (i + 1 < len)) 537 | { 538 | temp[2] = text.charAt(i++); 539 | temp[3] = text.charAt(i++); 540 | 541 | decodedChar = strtol(temp, NULL, 16); 542 | } 543 | else { 544 | if (encodedChar == '+') 545 | { 546 | decodedChar = ' '; 547 | } 548 | else { 549 | decodedChar = encodedChar; // normal ascii char 550 | } 551 | } 552 | decoded += decodedChar; 553 | } 554 | return decoded; 555 | } 556 | 557 | bool WebServer::_parseFormUploadAborted(){ 558 | _currentUpload->status = UPLOAD_FILE_ABORTED; 559 | if(_currentHandler && _currentHandler->canUpload(_currentUri)) 560 | _currentHandler->upload(*this, _currentUri, *_currentUpload); 561 | return false; 562 | } 563 | -------------------------------------------------------------------------------- /WebServer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | WebServer.cpp - Dead simple web-server. 3 | Supports only one simultaneous client, knows how to handle GET and POST. 4 | 5 | Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) 21 | */ 22 | 23 | 24 | #include 25 | #include 26 | #include 27 | #include "WiFiServer.h" 28 | #include "WiFiClient.h" 29 | #include "WebServer.h" 30 | #include "FS.h" 31 | #include "RequestHandlersImpl.h" 32 | #include "mimetable.h" 33 | #include "mbedtls/md5.h" 34 | 35 | 36 | static const char AUTHORIZATION_HEADER[] = "Authorization"; 37 | static const char qop_auth[] = "qop=auth"; 38 | static const char WWW_Authenticate[] = "WWW-Authenticate"; 39 | static const char Content_Length[] = "Content-Length"; 40 | 41 | 42 | WebServer::WebServer(IPAddress addr, int port) 43 | : _corsEnabled(false) 44 | , _server(addr, port) 45 | , _currentMethod(HTTP_ANY) 46 | , _currentVersion(0) 47 | , _currentStatus(HC_NONE) 48 | , _statusChange(0) 49 | , _currentHandler(nullptr) 50 | , _firstHandler(nullptr) 51 | , _lastHandler(nullptr) 52 | , _currentArgCount(0) 53 | , _currentArgs(nullptr) 54 | , _postArgsLen(0) 55 | , _postArgs(nullptr) 56 | , _headerKeysCount(0) 57 | , _currentHeaders(nullptr) 58 | , _contentLength(0) 59 | , _chunked(false) 60 | { 61 | } 62 | 63 | WebServer::WebServer(int port) 64 | : _corsEnabled(false) 65 | , _server(port) 66 | , _currentMethod(HTTP_ANY) 67 | , _currentVersion(0) 68 | , _currentStatus(HC_NONE) 69 | , _statusChange(0) 70 | , _currentHandler(nullptr) 71 | , _firstHandler(nullptr) 72 | , _lastHandler(nullptr) 73 | , _currentArgCount(0) 74 | , _currentArgs(nullptr) 75 | , _postArgsLen(0) 76 | , _postArgs(nullptr) 77 | , _headerKeysCount(0) 78 | , _currentHeaders(nullptr) 79 | , _contentLength(0) 80 | , _chunked(false) 81 | { 82 | } 83 | 84 | WebServer::~WebServer() { 85 | _server.close(); 86 | if (_currentHeaders) 87 | delete[]_currentHeaders; 88 | RequestHandler* handler = _firstHandler; 89 | while (handler) { 90 | RequestHandler* next = handler->next(); 91 | delete handler; 92 | handler = next; 93 | } 94 | } 95 | 96 | void WebServer::begin() { 97 | close(); 98 | _server.begin(); 99 | _server.setNoDelay(true); 100 | } 101 | 102 | void WebServer::begin(uint16_t port) { 103 | close(); 104 | _server.begin(port); 105 | _server.setNoDelay(true); 106 | } 107 | 108 | String WebServer::_extractParam(String& authReq,const String& param,const char delimit){ 109 | int _begin = authReq.indexOf(param); 110 | if (_begin == -1) 111 | return ""; 112 | return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); 113 | } 114 | 115 | static String md5str(String &in){ 116 | char out[33] = {0}; 117 | mbedtls_md5_context _ctx; 118 | uint8_t i; 119 | uint8_t * _buf = (uint8_t*)malloc(16); 120 | if(_buf == NULL) 121 | return String(out); 122 | memset(_buf, 0x00, 16); 123 | mbedtls_md5_init(&_ctx); 124 | mbedtls_md5_starts(&_ctx); 125 | mbedtls_md5_update(&_ctx, (const uint8_t *)in.c_str(), in.length()); 126 | mbedtls_md5_finish(&_ctx, _buf); 127 | for(i = 0; i < 16; i++) { 128 | sprintf(out + (i * 2), "%02x", _buf[i]); 129 | } 130 | out[32] = 0; 131 | free(_buf); 132 | return String(out); 133 | } 134 | 135 | bool WebServer::authenticate(const char * username, const char * password){ 136 | if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) { 137 | String authReq = header(FPSTR(AUTHORIZATION_HEADER)); 138 | if(authReq.startsWith(F("Basic"))){ 139 | authReq = authReq.substring(6); 140 | authReq.trim(); 141 | char toencodeLen = strlen(username)+strlen(password)+1; 142 | char *toencode = new char[toencodeLen + 1]; 143 | if(toencode == NULL){ 144 | authReq = ""; 145 | return false; 146 | } 147 | char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; 148 | if(encoded == NULL){ 149 | authReq = ""; 150 | delete[] toencode; 151 | return false; 152 | } 153 | sprintf(toencode, "%s:%s", username, password); 154 | if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { 155 | authReq = ""; 156 | delete[] toencode; 157 | delete[] encoded; 158 | return true; 159 | } 160 | delete[] toencode; 161 | delete[] encoded; 162 | } else if(authReq.startsWith(F("Digest"))) { 163 | authReq = authReq.substring(7); 164 | log_v("%s", authReq.c_str()); 165 | String _username = _extractParam(authReq,F("username=\"")); 166 | if(!_username.length() || _username != String(username)) { 167 | authReq = ""; 168 | return false; 169 | } 170 | // extracting required parameters for RFC 2069 simpler Digest 171 | String _realm = _extractParam(authReq, F("realm=\"")); 172 | String _nonce = _extractParam(authReq, F("nonce=\"")); 173 | String _uri = _extractParam(authReq, F("uri=\"")); 174 | String _response = _extractParam(authReq, F("response=\"")); 175 | String _opaque = _extractParam(authReq, F("opaque=\"")); 176 | 177 | if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) { 178 | authReq = ""; 179 | return false; 180 | } 181 | if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) { 182 | authReq = ""; 183 | return false; 184 | } 185 | // parameters for the RFC 2617 newer Digest 186 | String _nc,_cnonce; 187 | if(authReq.indexOf(FPSTR(qop_auth)) != -1) { 188 | _nc = _extractParam(authReq, F("nc="), ','); 189 | _cnonce = _extractParam(authReq, F("cnonce=\"")); 190 | } 191 | String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); 192 | log_v("Hash of user:realm:pass=%s", _H1.c_str()); 193 | String _H2 = ""; 194 | if(_currentMethod == HTTP_GET){ 195 | _H2 = md5str(String(F("GET:")) + _uri); 196 | }else if(_currentMethod == HTTP_POST){ 197 | _H2 = md5str(String(F("POST:")) + _uri); 198 | }else if(_currentMethod == HTTP_PUT){ 199 | _H2 = md5str(String(F("PUT:")) + _uri); 200 | }else if(_currentMethod == HTTP_DELETE){ 201 | _H2 = md5str(String(F("DELETE:")) + _uri); 202 | }else{ 203 | _H2 = md5str(String(F("GET:")) + _uri); 204 | } 205 | log_v("Hash of GET:uri=%s", _H2.c_str()); 206 | String _responsecheck = ""; 207 | if(authReq.indexOf(FPSTR(qop_auth)) != -1) { 208 | _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); 209 | } else { 210 | _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); 211 | } 212 | log_v("The Proper response=%s", _responsecheck.c_str()); 213 | if(_response == _responsecheck){ 214 | authReq = ""; 215 | return true; 216 | } 217 | } 218 | authReq = ""; 219 | } 220 | return false; 221 | } 222 | 223 | String WebServer::_getRandomHexString() { 224 | char buffer[33]; // buffer to hold 32 Hex Digit + /0 225 | int i; 226 | for(i = 0; i < 4; i++) { 227 | sprintf (buffer + (i*8), "%08x", esp_random()); 228 | } 229 | return String(buffer); 230 | } 231 | 232 | void WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg) { 233 | if(realm == NULL) { 234 | _srealm = String(F("Login Required")); 235 | } else { 236 | _srealm = String(realm); 237 | } 238 | if(mode == BASIC_AUTH) { 239 | sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\""))); 240 | } else { 241 | _snonce=_getRandomHexString(); 242 | _sopaque=_getRandomHexString(); 243 | sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\""))); 244 | } 245 | using namespace mime; 246 | send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg); 247 | } 248 | 249 | void WebServer::on(const String &uri, WebServer::THandlerFunction handler) { 250 | on(uri, HTTP_ANY, handler); 251 | } 252 | 253 | void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn) { 254 | on(uri, method, fn, _fileUploadHandler); 255 | } 256 | 257 | void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) { 258 | _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method)); 259 | } 260 | 261 | void WebServer::addHandler(RequestHandler* handler) { 262 | _addRequestHandler(handler); 263 | } 264 | 265 | void WebServer::_addRequestHandler(RequestHandler* handler) { 266 | if (!_lastHandler) { 267 | _firstHandler = handler; 268 | _lastHandler = handler; 269 | } 270 | else { 271 | _lastHandler->next(handler); 272 | _lastHandler = handler; 273 | } 274 | } 275 | 276 | void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) { 277 | _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header)); 278 | } 279 | 280 | void WebServer::handleClient() { 281 | if (_currentStatus == HC_NONE) { 282 | WiFiClient client = _server.available(); 283 | if (!client) { 284 | return; 285 | } 286 | 287 | log_v("New client"); 288 | 289 | _currentClient = client; 290 | _currentStatus = HC_WAIT_READ; 291 | _statusChange = millis(); 292 | } 293 | 294 | bool keepCurrentClient = false; 295 | bool callYield = false; 296 | 297 | if (_currentClient.connected()) { 298 | switch (_currentStatus) { 299 | case HC_NONE: 300 | // No-op to avoid C++ compiler warning 301 | break; 302 | case HC_WAIT_READ: 303 | // Wait for data from client to become available 304 | if (_currentClient.available()) { 305 | if (_parseRequest(_currentClient)) { 306 | // because HTTP_MAX_SEND_WAIT is expressed in milliseconds, 307 | // it must be divided by 1000 308 | _currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000); 309 | _contentLength = CONTENT_LENGTH_NOT_SET; 310 | _handleRequest(); 311 | 312 | /* this comment fixes the chrome browser net::ERR_CONNECTION_RESET problem 313 | if (_currentClient.connected()) { 314 | _currentStatus = HC_WAIT_CLOSE; 315 | _statusChange = millis(); 316 | keepCurrentClient = true; 317 | } 318 | */ 319 | } 320 | } else { // !_currentClient.available() 321 | if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) { 322 | keepCurrentClient = true; 323 | } 324 | callYield = true; 325 | } 326 | break; 327 | case HC_WAIT_CLOSE: 328 | // Wait for client to close the connection 329 | if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) { 330 | keepCurrentClient = true; 331 | callYield = true; 332 | } 333 | } 334 | } 335 | 336 | if (!keepCurrentClient) { 337 | _currentClient = WiFiClient(); 338 | _currentStatus = HC_NONE; 339 | _currentUpload.reset(); 340 | } 341 | 342 | if (callYield) { 343 | yield(); 344 | } 345 | } 346 | 347 | void WebServer::close() { 348 | _server.close(); 349 | _currentStatus = HC_NONE; 350 | if(!_headerKeysCount) 351 | collectHeaders(0, 0); 352 | } 353 | 354 | void WebServer::stop() { 355 | close(); 356 | } 357 | 358 | void WebServer::sendHeader(const String& name, const String& value, bool first) { 359 | String headerLine = name; 360 | headerLine += F(": "); 361 | headerLine += value; 362 | headerLine += "\r\n"; 363 | 364 | if (first) { 365 | _responseHeaders = headerLine + _responseHeaders; 366 | } 367 | else { 368 | _responseHeaders += headerLine; 369 | } 370 | } 371 | 372 | void WebServer::setContentLength(const size_t contentLength) { 373 | _contentLength = contentLength; 374 | } 375 | 376 | void WebServer::enableCORS(boolean value) { 377 | _corsEnabled = value; 378 | } 379 | 380 | void WebServer::enableCrossOrigin(boolean value) { 381 | enableCORS(value); 382 | } 383 | 384 | void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { 385 | response = String(F("HTTP/1.")) + String(_currentVersion) + ' '; 386 | response += String(code); 387 | response += ' '; 388 | response += _responseCodeToString(code); 389 | response += "\r\n"; 390 | 391 | using namespace mime; 392 | if (!content_type) 393 | content_type = mimeTable[html].mimeType; 394 | 395 | sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true); 396 | if (_contentLength == CONTENT_LENGTH_NOT_SET) { 397 | sendHeader(String(FPSTR(Content_Length)), String(contentLength)); 398 | } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { 399 | sendHeader(String(FPSTR(Content_Length)), String(_contentLength)); 400 | } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client 401 | //let's do chunked 402 | _chunked = true; 403 | sendHeader(String(F("Accept-Ranges")),String(F("none"))); 404 | sendHeader(String(F("Transfer-Encoding")),String(F("chunked"))); 405 | } 406 | if (_corsEnabled) { 407 | sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*")); 408 | } 409 | sendHeader(String(F("Connection")), String(F("close"))); 410 | 411 | response += _responseHeaders; 412 | response += "\r\n"; 413 | _responseHeaders = ""; 414 | } 415 | 416 | void WebServer::send(int code, const char* content_type, const String& content) { 417 | String header; 418 | // Can we asume the following? 419 | //if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET) 420 | // _contentLength = CONTENT_LENGTH_UNKNOWN; 421 | _prepareHeader(header, code, content_type, content.length()); 422 | _currentClientWrite(header.c_str(), header.length()); 423 | if(content.length()) 424 | sendContent(content); 425 | } 426 | 427 | void WebServer::send_P(int code, PGM_P content_type, PGM_P content) { 428 | size_t contentLength = 0; 429 | 430 | if (content != NULL) { 431 | contentLength = strlen_P(content); 432 | } 433 | 434 | String header; 435 | char type[64]; 436 | memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type)); 437 | _prepareHeader(header, code, (const char* )type, contentLength); 438 | _currentClientWrite(header.c_str(), header.length()); 439 | sendContent_P(content); 440 | } 441 | 442 | void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) { 443 | String header; 444 | char type[64]; 445 | memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type)); 446 | _prepareHeader(header, code, (const char* )type, contentLength); 447 | sendContent(header); 448 | sendContent_P(content, contentLength); 449 | } 450 | 451 | void WebServer::send(int code, char* content_type, const String& content) { 452 | send(code, (const char*)content_type, content); 453 | } 454 | 455 | void WebServer::send(int code, const String& content_type, const String& content) { 456 | send(code, (const char*)content_type.c_str(), content); 457 | } 458 | 459 | void WebServer::sendContent(const String& content) { 460 | const char * footer = "\r\n"; 461 | size_t len = content.length(); 462 | if(_chunked) { 463 | char * chunkSize = (char *)malloc(11); 464 | if(chunkSize){ 465 | sprintf(chunkSize, "%x%s", len, footer); 466 | _currentClientWrite(chunkSize, strlen(chunkSize)); 467 | free(chunkSize); 468 | } 469 | } 470 | _currentClientWrite(content.c_str(), len); 471 | if(_chunked){ 472 | _currentClient.write(footer, 2); 473 | if (len == 0) { 474 | _chunked = false; 475 | } 476 | } 477 | } 478 | 479 | void WebServer::sendContent_P(PGM_P content) { 480 | sendContent_P(content, strlen_P(content)); 481 | } 482 | 483 | void WebServer::sendContent_P(PGM_P content, size_t size) { 484 | const char * footer = "\r\n"; 485 | if(_chunked) { 486 | char * chunkSize = (char *)malloc(11); 487 | if(chunkSize){ 488 | sprintf(chunkSize, "%x%s", size, footer); 489 | _currentClientWrite(chunkSize, strlen(chunkSize)); 490 | free(chunkSize); 491 | } 492 | } 493 | _currentClientWrite_P(content, size); 494 | if(_chunked){ 495 | _currentClient.write(footer, 2); 496 | if (size == 0) { 497 | _chunked = false; 498 | } 499 | } 500 | } 501 | 502 | 503 | void WebServer::_streamFileCore(const size_t fileSize, const String & fileName, const String & contentType) 504 | { 505 | using namespace mime; 506 | setContentLength(fileSize); 507 | if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) && 508 | contentType != String(FPSTR(mimeTable[gz].mimeType)) && 509 | contentType != String(FPSTR(mimeTable[none].mimeType))) { 510 | sendHeader(F("Content-Encoding"), F("gzip")); 511 | } 512 | send(200, contentType, ""); 513 | } 514 | 515 | String WebServer::pathArg(unsigned int i) { 516 | if (_currentHandler != nullptr) 517 | return _currentHandler->pathArg(i); 518 | return ""; 519 | } 520 | 521 | String WebServer::arg(String name) { 522 | for (int j = 0; j < _postArgsLen; ++j) { 523 | if ( _postArgs[j].key == name ) 524 | return _postArgs[j].value; 525 | } 526 | for (int i = 0; i < _currentArgCount; ++i) { 527 | if ( _currentArgs[i].key == name ) 528 | return _currentArgs[i].value; 529 | } 530 | return ""; 531 | } 532 | 533 | String WebServer::arg(int i) { 534 | if (i < _currentArgCount) 535 | return _currentArgs[i].value; 536 | return ""; 537 | } 538 | 539 | String WebServer::argName(int i) { 540 | if (i < _currentArgCount) 541 | return _currentArgs[i].key; 542 | return ""; 543 | } 544 | 545 | int WebServer::args() { 546 | return _currentArgCount; 547 | } 548 | 549 | bool WebServer::hasArg(String name) { 550 | for (int j = 0; j < _postArgsLen; ++j) { 551 | if (_postArgs[j].key == name) 552 | return true; 553 | } 554 | for (int i = 0; i < _currentArgCount; ++i) { 555 | if (_currentArgs[i].key == name) 556 | return true; 557 | } 558 | return false; 559 | } 560 | 561 | 562 | String WebServer::header(String name) { 563 | for (int i = 0; i < _headerKeysCount; ++i) { 564 | if (_currentHeaders[i].key.equalsIgnoreCase(name)) 565 | return _currentHeaders[i].value; 566 | } 567 | return ""; 568 | } 569 | 570 | void WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { 571 | _headerKeysCount = headerKeysCount + 1; 572 | if (_currentHeaders) 573 | delete[]_currentHeaders; 574 | _currentHeaders = new RequestArgument[_headerKeysCount]; 575 | _currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER); 576 | for (int i = 1; i < _headerKeysCount; i++){ 577 | _currentHeaders[i].key = headerKeys[i-1]; 578 | } 579 | } 580 | 581 | String WebServer::header(int i) { 582 | if (i < _headerKeysCount) 583 | return _currentHeaders[i].value; 584 | return ""; 585 | } 586 | 587 | String WebServer::headerName(int i) { 588 | if (i < _headerKeysCount) 589 | return _currentHeaders[i].key; 590 | return ""; 591 | } 592 | 593 | int WebServer::headers() { 594 | return _headerKeysCount; 595 | } 596 | 597 | bool WebServer::hasHeader(String name) { 598 | for (int i = 0; i < _headerKeysCount; ++i) { 599 | if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0)) 600 | return true; 601 | } 602 | return false; 603 | } 604 | 605 | String WebServer::hostHeader() { 606 | return _hostHeader; 607 | } 608 | 609 | void WebServer::onFileUpload(THandlerFunction fn) { 610 | _fileUploadHandler = fn; 611 | } 612 | 613 | void WebServer::onNotFound(THandlerFunction fn) { 614 | _notFoundHandler = fn; 615 | } 616 | 617 | void WebServer::_handleRequest() { 618 | bool handled = false; 619 | if (!_currentHandler){ 620 | log_e("request handler not found"); 621 | } 622 | else { 623 | handled = _currentHandler->handle(*this, _currentMethod, _currentUri); 624 | if (!handled) { 625 | log_e("request handler failed to handle request"); 626 | } 627 | } 628 | if (!handled && _notFoundHandler) { 629 | _notFoundHandler(); 630 | handled = true; 631 | } 632 | if (!handled) { 633 | using namespace mime; 634 | send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri); 635 | handled = true; 636 | } 637 | if (handled) { 638 | _finalizeResponse(); 639 | } 640 | _currentUri = ""; 641 | } 642 | 643 | 644 | void WebServer::_finalizeResponse() { 645 | if (_chunked) { 646 | sendContent(""); 647 | } 648 | } 649 | 650 | String WebServer::_responseCodeToString(int code) { 651 | switch (code) { 652 | case 100: return F("Continue"); 653 | case 101: return F("Switching Protocols"); 654 | case 200: return F("OK"); 655 | case 201: return F("Created"); 656 | case 202: return F("Accepted"); 657 | case 203: return F("Non-Authoritative Information"); 658 | case 204: return F("No Content"); 659 | case 205: return F("Reset Content"); 660 | case 206: return F("Partial Content"); 661 | case 300: return F("Multiple Choices"); 662 | case 301: return F("Moved Permanently"); 663 | case 302: return F("Found"); 664 | case 303: return F("See Other"); 665 | case 304: return F("Not Modified"); 666 | case 305: return F("Use Proxy"); 667 | case 307: return F("Temporary Redirect"); 668 | case 400: return F("Bad Request"); 669 | case 401: return F("Unauthorized"); 670 | case 402: return F("Payment Required"); 671 | case 403: return F("Forbidden"); 672 | case 404: return F("Not Found"); 673 | case 405: return F("Method Not Allowed"); 674 | case 406: return F("Not Acceptable"); 675 | case 407: return F("Proxy Authentication Required"); 676 | case 408: return F("Request Time-out"); 677 | case 409: return F("Conflict"); 678 | case 410: return F("Gone"); 679 | case 411: return F("Length Required"); 680 | case 412: return F("Precondition Failed"); 681 | case 413: return F("Request Entity Too Large"); 682 | case 414: return F("Request-URI Too Large"); 683 | case 415: return F("Unsupported Media Type"); 684 | case 416: return F("Requested range not satisfiable"); 685 | case 417: return F("Expectation Failed"); 686 | case 500: return F("Internal Server Error"); 687 | case 501: return F("Not Implemented"); 688 | case 502: return F("Bad Gateway"); 689 | case 503: return F("Service Unavailable"); 690 | case 504: return F("Gateway Time-out"); 691 | case 505: return F("HTTP Version not supported"); 692 | default: return F(""); 693 | } 694 | } 695 | --------------------------------------------------------------------------------