├── CheatSheet.pdf ├── SID.code-workspace ├── .gitignore ├── install └── sid-A10001986.ino.nodemcu-32s.bin ├── .vscode └── extensions.json ├── LICENSE ├── platformio.ini ├── src ├── src │ └── arduinoFFT │ │ ├── types.h │ │ ├── defs.h │ │ ├── arduinoFFT.h │ │ └── arduinoFFT.cpp ├── sid_sa.h ├── sid_snake.h ├── sid_siddly.h ├── sid_wifi.h ├── sid_main.h ├── siddisplay.h ├── sid_global.h ├── input.h ├── sid_settings.h ├── mqtt.h ├── sid_snake.cpp ├── input.cpp ├── sid-A10001986.ino ├── sid_sa.cpp ├── sid_siddly.cpp ├── siddisplay.cpp ├── sid_font.h └── mqtt.cpp └── README.md /CheatSheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/SID_old/main/CheatSheet.pdf -------------------------------------------------------------------------------- /SID.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | /.vscode 7 | -------------------------------------------------------------------------------- /install/sid-A10001986.ino.nodemcu-32s.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/SID_old/main/install/sid-A10001986.ino.nodemcu-32s.bin -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Thomas Winischhofer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = esp32dev 13 | 14 | [env:esp32dev] 15 | platform = espressif32 ;@ 6.3.2 16 | framework = arduino 17 | board = nodemcu-32s 18 | platform_packages = 19 | framework-arduinoespressif32 ;@ 2.0.6 20 | board_build.f_cpu = 240000000L 21 | board_build.flash_mode = qio 22 | lib_deps = 23 | wnatth3/WiFiManager @ 2.0.16-rc.2 ;https://github.com/tzapu/WiFiManager.git 24 | ArduinoJson @ ^6.19.4 25 | upload_speed = 921600 26 | monitor_speed = 115200 27 | build_flags = 28 | -std=gnu++11 29 | -mtarget-align 30 | ;see sid_global.h for full explanations of these options 31 | -DSID_DBG ;enables serial debug 32 | #-DUSE_SPIFFS ;use SPIFFS for arduinoespressif32 < 2.0, otherwise use LittleFS - If LittleFS uncomment board_build.filesystem below 33 | 34 | board_build.filesystem = LittleFS ;uncomment if using LittleFS - make sure USE_SPIFFS IS commented above 35 | build_src_flags = 36 | -DDEBUG_PORT=Serial 37 | -ggdb 38 | ;uncomment the following to use the esp32 exception decoder 39 | #monitor_filters = esp32_exception_decoder 40 | #build_type = debug -------------------------------------------------------------------------------- /src/src/arduinoFFT/types.h: -------------------------------------------------------------------------------- 1 | //useful things to include in code 2 | 3 | #ifndef TYPES_H 4 | #define TYPES_H 5 | 6 | #ifndef WIN32 7 | // true/false defines 8 | #define FALSE 0 9 | #define TRUE -1 10 | #endif 11 | 12 | // datatype definitions macros 13 | typedef unsigned char u08; 14 | typedef signed char s08; 15 | typedef unsigned short u16; 16 | typedef signed short s16; 17 | typedef unsigned long u32; 18 | typedef signed long s32; 19 | typedef unsigned long long u64; 20 | typedef signed long long s64; 21 | 22 | // #ifndef __AVR__ 23 | #ifdef __MBED__ 24 | // use inttypes.h instead 25 | // C99 standard integer type definitions 26 | typedef unsigned char uint8_t; 27 | typedef signed char int8_t; 28 | typedef unsigned short uint16_t; 29 | typedef signed short int16_t; 30 | /*typedef unsigned long uint32_t; 31 | typedef signed long int32_t; 32 | typedef unsigned long uint64_t; 33 | typedef signed long int64_t; 34 | */ 35 | #endif 36 | 37 | // maximum value that can be held 38 | // by unsigned data types (8,16,32bits) 39 | #define MAX_U08 255 40 | #define MAX_U16 65535 41 | #define MAX_U32 4294967295 42 | 43 | // maximum values that can be held 44 | // by signed data types (8,16,32bits) 45 | #define MIN_S08 -128 46 | #define MAX_S08 127 47 | #define MIN_S16 -32768 48 | #define MAX_S16 32767 49 | #define MIN_S32 -2147483648 50 | #define MAX_S32 2147483647 51 | 52 | #ifndef WIN32 53 | // more type redefinitions 54 | typedef unsigned char BOOL; 55 | typedef unsigned char BYTE; 56 | typedef unsigned int WORD; 57 | typedef unsigned long DWORD; 58 | 59 | typedef unsigned char UCHAR; 60 | typedef unsigned int UINT; 61 | typedef unsigned short USHORT; 62 | typedef unsigned long ULONG; 63 | 64 | typedef char CHAR; 65 | typedef int INT; 66 | typedef long LONG; 67 | #endif 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/sid_sa.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Spectrum Analyzer 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | #ifndef _SID_SA_H 33 | #define _SID_SA_H 34 | 35 | extern bool saActive; // Read only! 36 | extern bool doPeaks; 37 | 38 | #define SA_START_DELAY 1000 // Delay to skip the mic's startup noise 39 | 40 | void sa_activate(bool init = true, unsigned long start_Delay = SA_START_DELAY); 41 | void sa_deactivate(); 42 | 43 | int sa_setAmpFact(int newAmpFact); 44 | 45 | void sa_loop(); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/sid_snake.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Snake 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #ifndef _SID_SN_H 34 | #define _SID_SN_H 35 | 36 | extern bool snActive; // read only!!! 37 | 38 | void sn_init(); // start game 39 | void sn_loop(); // game loop 40 | void sn_end(); // end game (quit) 41 | void sn_newGame(); // restart game (when active) 42 | void sn_pause(); // pause game (toggle) 43 | void sn_moveRight(); // user input: move right 44 | void sn_moveLeft(); // user input: move left 45 | void sn_moveDown(); // user input: move down 46 | void sn_moveUp(); // user input: move up 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/sid_siddly.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Siddly 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #ifndef _SID_SI_H 34 | #define _SID_SI_H 35 | 36 | extern bool siActive; // read only!!! 37 | 38 | void si_init(); // start game 39 | void si_loop(); // game loop 40 | void si_end(); // end game (quit) 41 | void si_newGame(); // restart game (when active) 42 | void si_pause(); // pause game (toggle) 43 | void si_moveRight(); // user input: move right 44 | void si_moveLeft(); // user input: move left 45 | void si_rotate(); // user input: rotate (left) 46 | void si_moveDown(); // user input: move down 47 | void si_fallDown(); // user input: fall down 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/sid_wifi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * WiFi and Config Portal handling 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #ifndef _SID_WIFI_H 34 | #define _SID_WIFI_H 35 | 36 | extern bool wifiSetupDone; 37 | extern bool wifiIsOff; 38 | extern bool wifiAPIsOff; 39 | extern bool wifiInAPMode; 40 | 41 | void wifi_setup(); 42 | void wifi_setup2(); 43 | void wifi_loop(); 44 | void wifiOff(); 45 | void wifiOn(unsigned long newDelay = 0, bool alsoInAPMode = false, bool deferConfigPortal = false); 46 | bool wifiIsOn(); 47 | void wifiStartCP(); 48 | 49 | void updateConfigPortalValues(); 50 | void updateConfigPortalStrictValue(); 51 | 52 | bool wifi_getIP(uint8_t& a, uint8_t& b, uint8_t& c, uint8_t& d); 53 | bool isIp(char *str); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/src/arduinoFFT/defs.h: -------------------------------------------------------------------------------- 1 | /*! \file avrlibdefs.h \brief AVRlib global defines and macros. */ 2 | //***************************************************************************** 3 | // 4 | // File Name : 'avrlibdefs.h' 5 | // Title : AVRlib global defines and macros include file 6 | // Author : Pascal Stang 7 | // Created : 7/12/2001 8 | // Revised : 9/30/2002 9 | // Version : 1.1 10 | // Target MCU : Atmel AVR series 11 | // Editor Tabs : 4 12 | // 13 | // Description : This include file is designed to contain items useful to all 14 | // code files and projects, regardless of specific implementation. 15 | // 16 | // This code is distributed under the GNU Public License 17 | // which can be found at http://www.gnu.org/licenses/gpl.txt 18 | // 19 | //***************************************************************************** 20 | 21 | 22 | #ifndef AVRLIBDEFS_H 23 | #define AVRLIBDEFS_H 24 | 25 | //#define F_CPU 4000000 26 | #define MEM_TYPE 1 27 | 28 | // Code compatibility to new AVR-libc 29 | // outb(), inb(), inw(), outw(), BV(), sbi(), cbi(), sei(), cli() 30 | #ifndef outb 31 | #define outb(addr, data) addr = (data) 32 | #endif 33 | #ifndef inb 34 | #define inb(addr) (addr) 35 | #endif 36 | #ifndef outw 37 | #define outw(addr, data) addr = (data) 38 | #endif 39 | #ifndef inw 40 | #define inw(addr) (addr) 41 | #endif 42 | #ifndef BV 43 | #define BV(bit) (1<<(bit)) 44 | #endif 45 | //#ifndef cbi 46 | // #define cbi(reg,bit) reg &= ~(BV(bit)) 47 | //#endif 48 | //#ifndef sbi 49 | // #define sbi(reg,bit) reg |= (BV(bit)) 50 | //#endif 51 | #ifndef cli 52 | #define cli() __asm__ __volatile__ ("cli" ::) 53 | #endif 54 | #ifndef sei 55 | #define sei() __asm__ __volatile__ ("sei" ::) 56 | #endif 57 | 58 | // support for individual port pin naming in the mega128 59 | // see port128.h for details 60 | #ifdef __AVR_ATmega128__ 61 | // not currently necessary due to inclusion 62 | // of these defines in newest AVR-GCC 63 | // do a quick test to see if include is needed 64 | #ifndef PD0 65 | //#include "port128.h" 66 | #endif 67 | #endif 68 | 69 | // use this for packed structures 70 | // (this is seldom necessary on an 8-bit architecture like AVR, 71 | // but can assist in code portability to AVR) 72 | #define GNUC_PACKED __attribute__((packed)) 73 | 74 | // port address helpers 75 | #define DDR(x) ((x)-1) // address of data direction register of port x 76 | #define PIN(x) ((x)-2) // address of input register of port x 77 | 78 | // MIN/MAX/ABS macros 79 | #define MIN(a,b) ((ab)?(a):(b)) 81 | #define ABS(x) ((x>0)?(x):(-x)) 82 | 83 | // constants 84 | #define PI 3.14159265359 85 | 86 | //Math 87 | #define sq(x) ((x)*(x)) 88 | #define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/sid_main.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Main controller 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | #ifndef _SID_MAIN_H 33 | #define _SID_MAIN_H 34 | 35 | #include "siddisplay.h" 36 | 37 | extern unsigned long powerupMillis; 38 | 39 | extern sidDisplay sid; 40 | 41 | #define SID_MAX_IDLE_MODE 5 42 | extern uint16_t idleMode; 43 | extern bool strictMode; 44 | 45 | // Number of IR keys 46 | #define NUM_IR_KEYS 17 47 | 48 | extern bool irLocked; 49 | 50 | extern bool TCDconnected; 51 | 52 | extern bool FPBUnitIsOn; 53 | extern bool sidNM; 54 | 55 | extern bool TTrunning; 56 | extern bool IRLearning; 57 | 58 | extern bool networkTimeTravel; 59 | extern bool networkTCDTT; 60 | extern bool networkReentry; 61 | extern bool networkAbort; 62 | extern bool networkAlarm; 63 | extern uint16_t networkLead; 64 | 65 | void main_boot(); 66 | void main_setup(); 67 | void main_loop(); 68 | 69 | void showWaitSequence(); 70 | void endWaitSequence(); 71 | void allOff(); 72 | 73 | void populateIRarray(uint32_t *irkeys, int index); 74 | void copyIRarray(uint32_t *irkeys, int index); 75 | 76 | void showWordSequence(const char *text, int speed = 3); 77 | 78 | void mydelay(unsigned long mydel, bool withIR); 79 | 80 | void prepareTT(); 81 | void wakeup(); 82 | 83 | void setIdleMode(int idleNo); 84 | 85 | void switch_to_idle(); 86 | void switch_to_sa(); 87 | 88 | void bttfn_loop(); 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/siddisplay.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * SIDDisplay Class: Handles the SID LEDs 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #ifndef _SIDDISPLAY_H 34 | #define _SIDDISPLAY_H 35 | 36 | #define SD_BUF_SIZE 16 // Buffer size in words (16bit) 37 | 38 | class sidDisplay { 39 | 40 | public: 41 | 42 | sidDisplay(uint8_t address1, uint8_t address2); 43 | void begin(); 44 | void on(); 45 | void off(); 46 | 47 | void lampTest(); 48 | 49 | void clearBuf(); 50 | 51 | uint8_t setBrightness(uint8_t level, bool setInitial = false); 52 | void resetBrightness(); 53 | uint8_t setBrightnessDirect(uint8_t level); 54 | uint8_t getBrightness(); 55 | 56 | void show(); 57 | 58 | void clearDisplayDirect(); 59 | 60 | void drawBar(uint8_t bar, uint8_t bottom, uint8_t top); 61 | void drawBarWithHeight(uint8_t bar, uint8_t height); 62 | void clearBar(uint8_t bar); 63 | void drawDot(uint8_t bar, uint8_t dot_y); 64 | 65 | void drawFieldAndShow(uint8_t *fieldData); 66 | 67 | void drawLetterAndShow(char alpha, int x = 0, int y = 8); 68 | void drawLetterMask(char alpha, int x, int y); 69 | 70 | private: 71 | void directCmd(uint8_t val); 72 | 73 | uint8_t _address[2] = { 0, 0 }; 74 | 75 | uint8_t _brightness = 15; // current display brightness 76 | uint8_t _origBrightness = 15; // value from settings 77 | 78 | uint16_t _displayBuffer[SD_BUF_SIZE]; 79 | 80 | }; 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /src/sid_global.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Global definitions 9 | */ 10 | 11 | #ifndef _SID_GLOBAL_H 12 | #define _SID_GLOBAL_H 13 | 14 | // Version strings. 15 | #define SID_VERSION "V1.05" 16 | #define SID_VERSION_EXTRA "NOV052023" 17 | 18 | //#define SID_DBG // debug output on Serial 19 | 20 | /************************************************************************* 21 | *** mDNS (Bonjour) support *** 22 | *************************************************************************/ 23 | 24 | // Supply mDNS service 25 | // Allows accessing the Config Portal via http://hostname.local 26 | // is configurable in the Config Portal 27 | // This needs to be commented if WiFiManager provides mDNS 28 | #define SID_MDNS 29 | 30 | // Uncomment when using WiFiManager 2.0.17 or later 31 | //#define WIFIMANAGER_2_0_17 32 | 33 | // Uncomment this if WiFiManager has mDNS enabled 34 | //#define SID_WM_HAS_MDNS 35 | 36 | /************************************************************************* 37 | *** Configuration for peripherals/features *** 38 | *************************************************************************/ 39 | 40 | // Uncomment for HomeAssistant MQTT protocol support 41 | #define SID_HAVEMQTT 42 | 43 | // --- end of config options 44 | 45 | /************************************************************************* 46 | *** Miscellaneous *** 47 | *************************************************************************/ 48 | 49 | // Use SPIFFS (if defined) or LittleFS (if undefined; esp32-arduino >= 2.x) 50 | //#define USE_SPIFFS 51 | 52 | // External time travel lead time, as defined by TCD firmware 53 | // If SID is connected by wire, and the option "Signal Time Travel without 5s 54 | // lead" is set on the TCD, the SID option "TCD signals without lead" must 55 | // be set, too. 56 | #define ETTO_LEAD 5000 57 | 58 | // Uncomment to include BTTFN discover support (multicast) 59 | #define BTTFN_MC 60 | 61 | /************************************************************************* 62 | *** esp32-arduino version detection *** 63 | *************************************************************************/ 64 | 65 | #if defined __has_include && __has_include() 66 | #include 67 | #ifdef ESP_ARDUINO_VERSION_MAJOR 68 | #if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(2,0,8) 69 | #define HAVE_GETNEXTFILENAME 70 | #endif 71 | #endif 72 | #endif 73 | 74 | /************************************************************************* 75 | *** GPIO pins *** 76 | *************************************************************************/ 77 | 78 | // IR Remote 79 | #define IRREMOTE_PIN 27 80 | 81 | // IR feedback 82 | #define IR_FB_PIN 17 83 | 84 | // Time Travel button (or TCD input trigger) 85 | #define TT_IN_PIN 13 86 | 87 | // I2S audio pins 88 | #define I2S_BCLK_PIN 26 89 | #define I2S_LRCLK_PIN 25 90 | #define I2S_DIN_PIN 33 91 | 92 | // SD Card pins 93 | #define SD_CS_PIN 5 94 | #define SPI_MOSI_PIN 23 95 | #define SPI_MISO_PIN 19 96 | #define SPI_SCK_PIN 18 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * FCRemote Class: Remote control handling 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #ifndef _SIDINPUT_H 34 | #define _SIDINPUT_H 35 | 36 | /* 37 | * IRRemote class 38 | */ 39 | 40 | #define IRBUFSIZE 100 41 | 42 | typedef enum { 43 | IRSTATE_IDLE, 44 | IRSTATE_LIGHT, 45 | IRSTATE_DARK, 46 | IRSTATE_STOP 47 | } IRState; 48 | 49 | class IRRemote { 50 | 51 | public: 52 | IRRemote(uint8_t timerno, uint8_t ir_pin); 53 | void begin(); 54 | 55 | bool loop(); 56 | uint32_t readHash(); 57 | void resume(); 58 | 59 | private: 60 | uint32_t compare(unsigned int oldval, unsigned int newval); 61 | bool calcHash(); 62 | 63 | uint8_t _timer_no = 0; 64 | hw_timer_t *_IRTimer = NULL; 65 | 66 | uint32_t _buflen; 67 | uint32_t _buf[IRBUFSIZE]; 68 | uint32_t _hvalue; 69 | 70 | unsigned long _prevTime; 71 | uint32_t _prevHash; 72 | }; 73 | 74 | 75 | /* 76 | * SIDButton class 77 | */ 78 | 79 | typedef enum { 80 | TCBS_IDLE, 81 | TCBS_PRESSED, 82 | TCBS_RELEASED, 83 | TCBS_LONGPRESS, 84 | TCBS_LONGPRESSEND 85 | } ButtonState; 86 | 87 | class SIDButton { 88 | 89 | public: 90 | SIDButton(const int pin, const boolean activeLow = true, const bool pullupActive = true); 91 | 92 | void setDebounceTicks(const int ticks); 93 | void setPressTicks(const int ticks); 94 | void setLongPressTicks(const int ticks); 95 | 96 | void attachPress(void (*newFunction)(void)); 97 | void attachLongPressStart(void (*newFunction)(void)); 98 | void attachLongPressStop(void (*newFunction)(void)); 99 | 100 | void scan(void); 101 | 102 | private: 103 | 104 | void reset(void); 105 | void transitionTo(ButtonState nextState); 106 | 107 | void (*_pressFunc)(void) = NULL; 108 | void (*_longPressStartFunc)(void) = NULL; 109 | void (*_longPressStopFunc)(void) = NULL; 110 | 111 | int _pin; 112 | 113 | unsigned int _debounceTicks = 50; 114 | unsigned int _pressTicks = 400; 115 | unsigned int _longPressTicks = 800; 116 | 117 | int _buttonPressed; 118 | 119 | ButtonState _state = TCBS_IDLE; 120 | ButtonState _lastState = TCBS_IDLE; 121 | 122 | unsigned long _startTime; 123 | 124 | bool _pressNotified = false; 125 | }; 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /src/sid_settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Settings handling 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #ifndef _SID_SETTINGS_H 34 | #define _SID_SETTINGS_H 35 | 36 | extern bool haveSD; 37 | extern bool FlashROMode; 38 | 39 | extern uint8_t musFolderNum; 40 | 41 | #define MS(s) XMS(s) 42 | #define XMS(s) #s 43 | 44 | // Default settings - change settings in the web interface 192.168.4.1 45 | 46 | #define DEF_SS_TIMER 0 // "Screen saver" timeout in minutes; 0 = ss off 47 | #define DEF_SA_PEAKS 0 // 1: Show peaks in SA, 0: don't 48 | 49 | #define DEF_DISDIR 0 // 0: Do not disable default IR remote control; 1: do 50 | 51 | #define DEF_HOSTNAME "sid" 52 | #define DEF_WIFI_RETRY 3 // 1-10; Default: 3 retries 53 | #define DEF_WIFI_TIMEOUT 7 // 7-25; Default: 7 seconds 54 | 55 | #define DEF_TCD_PRES 0 // 0: No TCD connected, 1: connected via GPIO 56 | #define DEF_NO_ETTO_LEAD 0 // Default: 0: TCD signals TT with ETTO_LEAD lead time; 1 without 57 | 58 | #define DEF_TCD_IP "" // TCD ip address or hostname for BTTFN 59 | #define DEF_USE_GPSS 0 // 0: Ignore GPS speed; 1: Use it for chase speed 60 | #define DEF_USE_NM 0 // 0: Ignore TCD night mode; 1: Follow TCD night mode 61 | #define DEF_USE_FPO 0 // 0: Ignore TCD fake power; 1: Follow TCD fake power 62 | 63 | #define DEF_STRICT 1 // 0: Allow random diviations from movie patterns; 1: no not 64 | #define DEF_SKIP_TTANIM 1 // 0: Don't skip tt anim; 1: do 65 | 66 | #define DEF_CFG_ON_SD 1 // Default: Save vol/spd/IR settings on SD card 67 | #define DEF_SD_FREQ 0 // SD/SPI frequency: Default 16MHz 68 | 69 | struct Settings { 70 | char ssTimer[6] = MS(DEF_SS_TIMER); 71 | char SApeaks[4] = MS(DEF_SA_PEAKS); 72 | 73 | char disDIR[4] = MS(DEF_DISDIR); 74 | 75 | char hostName[32] = DEF_HOSTNAME; 76 | char systemID[8] = ""; 77 | char appw[10] = ""; 78 | char wifiConRetries[4] = MS(DEF_WIFI_RETRY); 79 | char wifiConTimeout[4] = MS(DEF_WIFI_TIMEOUT); 80 | 81 | char TCDpresent[4] = MS(DEF_TCD_PRES); 82 | char noETTOLead[4] = MS(DEF_NO_ETTO_LEAD); 83 | 84 | char tcdIP[64] = DEF_TCD_IP; 85 | char useGPSS[4] = MS(DEF_USE_GPSS); 86 | char useNM[4] = MS(DEF_USE_NM); 87 | char useFPO[4] = MS(DEF_USE_FPO); 88 | 89 | char strictMode[4] = MS(DEF_STRICT); // saved, but overruled by idlePat config file 90 | char skipTTAnim[4] = MS(DEF_SKIP_TTANIM); 91 | 92 | #ifdef SID_HAVEMQTT 93 | char useMQTT[4] = "0"; 94 | char mqttServer[80] = ""; // ip or domain [:port] 95 | char mqttUser[128] = ""; // user[:pass] (UTF8) 96 | #endif 97 | 98 | char CfgOnSD[4] = MS(DEF_CFG_ON_SD); 99 | char sdFreq[4] = MS(DEF_SD_FREQ); 100 | }; 101 | 102 | struct IPSettings { 103 | char ip[20] = ""; 104 | char gateway[20] = ""; 105 | char netmask[20] = ""; 106 | char dns[20] = ""; 107 | }; 108 | 109 | extern struct Settings settings; 110 | extern struct IPSettings ipsettings; 111 | 112 | void settings_setup(); 113 | 114 | void unmount_fs(); 115 | 116 | void write_settings(); 117 | bool checkConfigExists(); 118 | 119 | void copySettings(); 120 | 121 | bool loadBrightness(); 122 | void saveBrightness(bool useCache = true); 123 | 124 | bool loadIdlePat(); 125 | void saveIdlePat(bool useCache = true); 126 | 127 | bool loadIRLock(); 128 | void saveIRLock(bool useCache = true); 129 | 130 | bool saveIRKeys(); 131 | void deleteIRKeys(); 132 | 133 | bool loadIpSettings(); 134 | void writeIpSettings(); 135 | void deleteIpSettings(); 136 | 137 | void formatFlashFS(); 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /src/src/arduinoFFT/arduinoFFT.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | FFT library 4 | Copyright (C) 2010 Didier Longueville 5 | Copyright (C) 2014 Enrique Condes 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program 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 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | #ifndef ArduinoFFT_h /* Prevent loading library twice */ 23 | #define ArduinoFFT_h 24 | 25 | //#define FFT_DOUBLE 26 | 27 | #ifdef FFT_DOUBLE 28 | #define FTYPE double 29 | #define FFT_COS cos 30 | #define FFT_SQRT sqrt 31 | #else 32 | #define FTYPE float 33 | #define FFT_COS cosf 34 | #define FFT_SQRT sqrtf 35 | #endif 36 | 37 | #ifdef ARDUINO 38 | #if ARDUINO >= 100 39 | #include "Arduino.h" 40 | #else 41 | #include "WProgram.h" /* This is where the standard Arduino code lies */ 42 | #endif 43 | #else 44 | #include 45 | #include 46 | 47 | #ifdef __AVR__ 48 | #include 49 | #include 50 | #endif 51 | #include "defs.h" 52 | #include "types.h" 53 | #include 54 | 55 | #endif 56 | 57 | // Define this to use a low-precision square root approximation instead of the 58 | // regular sqrt() call 59 | // This might only work for specific use cases, but is significantly faster. 60 | // Only works for ArduinoFFT. 61 | // #define FFT_SQRT_APPROXIMATION 62 | 63 | #ifdef FFT_SQRT_APPROXIMATION 64 | #include 65 | #else 66 | #define sqrt_internal sqrt 67 | #endif 68 | 69 | enum class FFTDirection { Reverse, Forward }; 70 | 71 | enum class FFTWindow { 72 | Rectangle, // rectangle (Box car) 73 | Hamming, // hamming 74 | Hann, // hann 75 | Triangle, // triangle (Bartlett) 76 | Nuttall, // nuttall 77 | Blackman, // blackman 78 | Blackman_Nuttall, // blackman nuttall 79 | Blackman_Harris, // blackman harris 80 | Flat_top, // flat top 81 | Welch // welch 82 | }; 83 | #define FFT_LIB_REV 0x15 84 | /* Custom constants */ 85 | #define FFT_FORWARD FFTDirection::Forward 86 | #define FFT_REVERSE FFTDirection::Reverse 87 | 88 | /* Windowing type */ 89 | #define FFT_WIN_TYP_RECTANGLE FFTWindow::Rectangle /* rectangle (Box car) */ 90 | #define FFT_WIN_TYP_HAMMING FFTWindow::Hamming /* hamming */ 91 | #define FFT_WIN_TYP_HANN FFTWindow::Hann /* hann */ 92 | #define FFT_WIN_TYP_TRIANGLE FFTWindow::Triangle /* triangle (Bartlett) */ 93 | #define FFT_WIN_TYP_NUTTALL FFTWindow::Nuttall /* nuttall */ 94 | #define FFT_WIN_TYP_BLACKMAN FFTWindow::Blackman /* blackman */ 95 | #define FFT_WIN_TYP_BLACKMAN_NUTTALL \ 96 | FFTWindow::Blackman_Nuttall /* blackman nuttall */ 97 | #define FFT_WIN_TYP_BLACKMAN_HARRIS \ 98 | FFTWindow::Blackman_Harris /* blackman harris*/ 99 | #define FFT_WIN_TYP_FLT_TOP FFTWindow::Flat_top /* flat top */ 100 | #define FFT_WIN_TYP_WELCH FFTWindow::Welch /* welch */ 101 | /*Mathematial constants*/ 102 | #define twoPi 6.28318531 103 | #define fourPi 12.56637061 104 | #define sixPi 18.84955593 105 | 106 | #ifdef __AVR__ 107 | static const FTYPE _c1[] PROGMEM = { 108 | 0.0000000000, 0.7071067812, 0.9238795325, 0.9807852804, 0.9951847267, 109 | 0.9987954562, 0.9996988187, 0.9999247018, 0.9999811753, 0.9999952938, 110 | 0.9999988235, 0.9999997059, 0.9999999265, 0.9999999816, 0.9999999954, 111 | 0.9999999989, 0.9999999997}; 112 | static const FTYPE _c2[] PROGMEM = { 113 | 1.0000000000, 0.7071067812, 0.3826834324, 0.1950903220, 0.0980171403, 114 | 0.0490676743, 0.0245412285, 0.0122715383, 0.0061358846, 0.0030679568, 115 | 0.0015339802, 0.0007669903, 0.0003834952, 0.0001917476, 0.0000958738, 116 | 0.0000479369, 0.0000239684}; 117 | #endif 118 | class arduinoFFT { 119 | public: 120 | /* Constructor */ 121 | arduinoFFT(void); 122 | arduinoFFT(FTYPE *vReal, FTYPE *vImag, uint16_t samples, 123 | FTYPE samplingFrequency); 124 | /* Destructor */ 125 | ~arduinoFFT(void); 126 | /* Functions */ 127 | uint8_t Revision(void); 128 | uint8_t Exponent(uint16_t value); 129 | 130 | void ComplexToMagnitude(FTYPE *vReal, FTYPE *vImag, uint16_t samples); 131 | void Compute(FTYPE *vReal, FTYPE *vImag, uint16_t samples, 132 | FFTDirection dir); 133 | void Compute(FTYPE *vReal, FTYPE *vImag, uint16_t samples, uint8_t power, 134 | FFTDirection dir); 135 | void DCRemoval(FTYPE *vData, uint16_t samples); 136 | FTYPE MajorPeak(FTYPE *vD, uint16_t samples, FTYPE samplingFrequency); 137 | void MajorPeak(FTYPE *vD, uint16_t samples, FTYPE samplingFrequency, 138 | FTYPE *f, FTYPE *v); 139 | void Windowing(FTYPE *vData, uint16_t samples, FFTWindow windowType, 140 | FFTDirection dir); 141 | 142 | void ComplexToMagnitude(); 143 | void Compute(FFTDirection dir); 144 | void DCRemoval(); 145 | FTYPE MajorPeak(); 146 | void MajorPeak(FTYPE *f, FTYPE *v); 147 | void Windowing(FFTWindow windowType, FFTDirection dir); 148 | 149 | FTYPE MajorPeakParabola(); 150 | 151 | private: 152 | /* Variables */ 153 | uint16_t _samples; 154 | FTYPE _samplingFrequency; 155 | FTYPE *_vReal; 156 | FTYPE *_vImag; 157 | uint8_t _power; 158 | /* Functions */ 159 | void Swap(FTYPE *x, FTYPE *y); 160 | void Parabola(FTYPE x1, FTYPE y1, FTYPE x2, FTYPE y2, FTYPE x3, 161 | FTYPE y3, FTYPE *a, FTYPE *b, FTYPE *c); 162 | }; 163 | 164 | #endif 165 | 166 | -------------------------------------------------------------------------------- /src/mqtt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * PubSubClient.h - A simple client for MQTT. 4 | * Nick O'Leary 5 | * http://knolleary.net 6 | * Minimized & adapted by Thomas Winischhofer (A10001986) in 2023 7 | * 8 | * Copyright (c) 2008-2020 Nicholas O'Leary 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining 11 | * a copy of this software and associated documentation files (the 12 | * "Software"), to deal in the Software without restriction, including 13 | * without limitation the rights to use, copy, modify, merge, publish, 14 | * distribute, sublicense, and/or sell copies of the Software, and to 15 | * permit persons to whom the Software is furnished to do so, subject to 16 | * the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be 19 | * included in all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | */ 30 | 31 | #ifndef PubSubClient_h 32 | #define PubSubClient_h 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | #define MQTT_VERSION_3_1 3 39 | #define MQTT_VERSION_3_1_1 4 40 | 41 | // MQTT_VERSION : Pick the version 42 | //#define MQTT_VERSION MQTT_VERSION_3_1 43 | #ifndef MQTT_VERSION 44 | #define MQTT_VERSION MQTT_VERSION_3_1_1 45 | #endif 46 | 47 | // MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize(). 48 | #ifndef MQTT_MAX_PACKET_SIZE 49 | #define MQTT_MAX_PACKET_SIZE 512 50 | #endif 51 | 52 | // MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive() 53 | #ifndef MQTT_KEEPALIVE 54 | #define MQTT_KEEPALIVE 15 55 | #endif 56 | 57 | // MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout() 58 | #ifndef MQTT_SOCKET_TIMEOUT 59 | #define MQTT_SOCKET_TIMEOUT 15 60 | #endif 61 | 62 | // MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client 63 | // in each write call. Needed for the Arduino Wifi Shield. Leave undefined to 64 | // pass the entire MQTT packet in each write call. 65 | //#define MQTT_MAX_TRANSFER_SIZE 80 66 | 67 | // Possible values for client.state() 68 | #define MQTT_CONNECTING -5 69 | #define MQTT_CONNECTION_TIMEOUT -4 70 | #define MQTT_CONNECTION_LOST -3 71 | #define MQTT_CONNECT_FAILED -2 72 | #define MQTT_DISCONNECTED -1 73 | #define MQTT_CONNECTED 0 74 | #define MQTT_CONNECT_BAD_PROTOCOL 1 75 | #define MQTT_CONNECT_BAD_CLIENT_ID 2 76 | #define MQTT_CONNECT_UNAVAILABLE 3 77 | #define MQTT_CONNECT_BAD_CREDENTIALS 4 78 | #define MQTT_CONNECT_UNAUTHORIZED 5 79 | 80 | #define MQTTCONNECT 1 << 4 // Client request to connect to Server 81 | #define MQTTCONNACK 2 << 4 // Connect Acknowledgment 82 | #define MQTTPUBLISH 3 << 4 // Publish message 83 | #define MQTTPUBACK 4 << 4 // Publish Acknowledgment 84 | #define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1) 85 | #define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2) 86 | #define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3) 87 | #define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request 88 | #define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment 89 | #define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request 90 | #define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment 91 | #define MQTTPINGREQ 12 << 4 // PING Request 92 | #define MQTTPINGRESP 13 << 4 // PING Response 93 | #define MQTTDISCONNECT 14 << 4 // Client is Disconnecting 94 | #define MQTTReserved 15 << 4 // Reserved 95 | 96 | #define MQTTQOS0 (0 << 1) 97 | #define MQTTQOS1 (1 << 1) 98 | #define MQTTQOS2 (2 << 1) 99 | 100 | // Maximum size of fixed header and variable length size header 101 | #define MQTT_MAX_HEADER_SIZE 5 102 | 103 | #define PING_ERROR -1 104 | #define PING_IDLE 0 105 | #define PING_PINGING 1 106 | 107 | #define CHECK_STRING_LENGTH(l,s) if(l+2+strnlen(s, this->bufferSize) > this->bufferSize) { _client->stop(); return false; } 108 | 109 | class PubSubClient { 110 | 111 | public: 112 | PubSubClient(WiFiClient& client); 113 | 114 | ~PubSubClient(); 115 | 116 | void setServer(IPAddress ip, uint16_t port); 117 | void setServer(const char *domain, uint16_t port); 118 | void setCallback(void (*callback)(char *, uint8_t *, unsigned int)); 119 | void setLooper(void (*looper)()); 120 | 121 | bool setBufferSize(uint16_t size); 122 | 123 | bool connect(const char *id); 124 | bool connect(const char *id, const char *user, const char *pass); 125 | bool connect(const char *id, const char *user, const char *pass, bool cleanSession); 126 | 127 | void disconnect(); 128 | 129 | bool publish(const char *topic, const uint8_t *payload, unsigned int plength, bool retained = false); 130 | 131 | bool subscribe(const char *topic, const char *topic2 = NULL, uint8_t qos = 0); 132 | bool unsubscribe(const char *topic); 133 | 134 | bool loop(); 135 | 136 | bool connected(); 137 | int state(); 138 | 139 | bool sendPing(); 140 | bool pollPing(); 141 | void cancelPing(); 142 | int pstate(); 143 | 144 | private: 145 | 146 | bool subscribe_int(bool unsubscribe, const char *topic, const char *topic2L, uint8_t qos); 147 | uint32_t readPacket(uint8_t *); 148 | bool readByte(uint8_t *result); 149 | bool readByte(uint8_t *result, uint16_t *index); 150 | bool write(uint8_t header, uint8_t *buf, uint16_t length); 151 | uint16_t writeString(const char *string, uint8_t *buf, uint16_t pos); 152 | // Build up the header ready to send 153 | // Returns the size of the header 154 | // Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start 155 | // (MQTT_MAX_HEADER_SIZE - ) bytes into the buffer 156 | size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length); 157 | 158 | WiFiClient* _client; 159 | uint8_t* buffer; 160 | uint16_t bufferSize; 161 | uint16_t keepAlive; 162 | unsigned long socketTimeout; 163 | uint16_t nextMsgId; 164 | unsigned long lastOutActivity; 165 | unsigned long lastInActivity; 166 | bool pingOutstanding; 167 | void (*callback)(char *, uint8_t *, unsigned int); 168 | void (*looper)(); 169 | 170 | IPAddress ip; 171 | const char* domain; 172 | uint16_t port; 173 | int _state; 174 | 175 | int _s; 176 | int _pstate = PING_IDLE; 177 | uint16_t _pseq_num = 34; 178 | }; 179 | 180 | #endif 181 | -------------------------------------------------------------------------------- /src/sid_snake.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Snake 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #include "sid_global.h" 34 | 35 | #include 36 | 37 | #include "sid_snake.h" 38 | #include "sid_main.h" 39 | 40 | #define WIDTH 10 41 | #define HEIGHT 20 42 | 43 | #define MAXLENGTH 100 44 | 45 | #define NUM_LEVELS 9 46 | #define APPLES_PER_LEVEL 15 47 | 48 | bool snActive = false; 49 | 50 | static int spx = 0; 51 | static int spy = 0; 52 | static int sdx = 0; 53 | static int sdy = 0; 54 | static int scl = 0; 55 | static int sml = 0; 56 | static int snake[MAXLENGTH][2] = { { 0, 0 } }; 57 | 58 | static int apx = 0; 59 | static int apy = 0; 60 | 61 | static unsigned long ldelays[NUM_LEVELS] = { 62 | 700, 600, 550, 500, 450, 400, 300, 200, 100 63 | }; 64 | 65 | static unsigned long cp_now = 0; 66 | 67 | static unsigned long snStartup = 0; // startup sequence running 68 | static bool gameOver = false; 69 | static bool gameOverShown = false; 70 | static bool pauseGame = false; 71 | static bool pauseShown = false; 72 | static int level = 0; // current level (speed) 73 | static int acnt = 0; // apple count in level 74 | 75 | static void updateDisplay() 76 | { 77 | uint8_t myField[WIDTH * HEIGHT] = { 0 }; 78 | 79 | // Snake 80 | for(int i = 0; i < scl - 1; i++) { 81 | myField[(snake[i][1] * WIDTH) + snake[i][0]] = 1; 82 | } 83 | 84 | // Apple 85 | if(apx >= 0) { 86 | myField[(apy * WIDTH) + apx] = 1; 87 | } 88 | 89 | sid.drawFieldAndShow((uint8_t *)myField); 90 | } 91 | 92 | static void shiftSnake() 93 | { 94 | for(int i = MAXLENGTH - 2; i >= 0; i--) { 95 | snake[i+1][0] = snake[i][0]; 96 | snake[i+1][1] = snake[i][1]; 97 | } 98 | } 99 | 100 | static bool appleHitsSnake() 101 | { 102 | for(int i = 0; i < scl; i++) { 103 | if(apx == snake[i][0] && apy == snake[i][1]) 104 | return true; 105 | } 106 | 107 | return false; 108 | } 109 | 110 | static void resetGame() 111 | { 112 | spx = WIDTH / 2; // pos of head 113 | spy = HEIGHT / 2; 114 | sdx = 1; // movement deltas 115 | sdy = 0; 116 | scl = 4; // current length incl head 117 | 118 | for(int i = 0; i < scl; i++) { 119 | snake[i][0] = spx - i; 120 | snake[i][1] = spy; 121 | } 122 | 123 | apx = WIDTH / 4; // apple position 124 | apy = HEIGHT / 4; 125 | 126 | acnt = 1; 127 | 128 | gameOver = gameOverShown = false; 129 | pauseGame = pauseShown = false; 130 | } 131 | 132 | void sn_init() 133 | { 134 | resetGame(); 135 | level = 0; 136 | 137 | showWordSequence("SNAKE", 2); 138 | 139 | snStartup = millis(); 140 | snActive = true; 141 | } 142 | 143 | void sn_loop() 144 | { 145 | unsigned long now = millis(); 146 | bool skipCheck = false; 147 | bool newApple = false; 148 | 149 | if(!snActive) 150 | return; 151 | 152 | if(gameOver) { 153 | if(!gameOverShown) { 154 | showWordSequence("GAME OVER ", 1); 155 | gameOverShown = true; 156 | return; 157 | } 158 | resetGame(); 159 | level = 0; 160 | updateDisplay(); 161 | cp_now = now; 162 | return; 163 | } 164 | 165 | if(snStartup) { 166 | if(now - snStartup < 1000) { 167 | return; 168 | } 169 | snStartup = 0; 170 | updateDisplay(); 171 | cp_now = now; 172 | return; 173 | } 174 | 175 | if(pauseGame) { 176 | if(!pauseShown) { 177 | sid.drawLetterAndShow('P'); 178 | pauseShown = true; 179 | } 180 | return; 181 | } 182 | 183 | if(now - cp_now < ldelays[level]) 184 | return; 185 | 186 | // Move snake 187 | spx += sdx; 188 | spy += sdy; 189 | 190 | // Wrap snake 191 | if(spx < 0) spx = WIDTH - 1; 192 | if(spx >= WIDTH) spx = 0; 193 | if(spy < 0) spy = HEIGHT - 1; 194 | if(spy >= HEIGHT) spy = 0; 195 | 196 | // Update snake[] 197 | shiftSnake(); 198 | snake[0][0] = spx; 199 | snake[0][1] = spy; 200 | 201 | // Check if head hits apple 202 | if(spx == apx && spy == apy) { 203 | scl++; 204 | acnt++; 205 | if(scl >= MAXLENGTH || acnt > APPLES_PER_LEVEL) { 206 | char lvl[2]; 207 | resetGame(); 208 | if(level < NUM_LEVELS - 1) { 209 | char lvl[2]; 210 | level++; 211 | lvl[0] = level + 1 + '0'; 212 | lvl[1] = 0; 213 | showWordSequence(lvl, 3); 214 | } else { 215 | // User won all levels; restart for now 216 | level = 0; 217 | } 218 | skipCheck = true; 219 | } else 220 | newApple = true; 221 | } 222 | 223 | // Make new apply 224 | if(newApple) { 225 | do { 226 | apx = esp_random() % WIDTH; 227 | apy = esp_random() % HEIGHT; 228 | } while(appleHitsSnake()); 229 | } 230 | 231 | // Check if any body parts of snake collide -> game over 232 | if(!skipCheck) { 233 | for(int i = 0; i < scl; i++) { 234 | int cx = snake[i][0], cy = snake[i][1]; 235 | for(int j = i + 1; j < scl; j++) { 236 | if(snake[j][0] == cx && snake[j][1] == cy) { 237 | gameOver = true; 238 | } 239 | } 240 | } 241 | } 242 | 243 | cp_now = now; 244 | updateDisplay(); 245 | } 246 | 247 | void sn_end() 248 | { 249 | if(!snActive) 250 | return; 251 | 252 | snActive = false; 253 | } 254 | 255 | void sn_newGame() 256 | { 257 | if(!snActive || snStartup) 258 | return; 259 | 260 | resetGame(); 261 | level = 0; 262 | updateDisplay(); 263 | } 264 | 265 | void sn_pause() 266 | { 267 | if(!snActive || gameOver || snStartup) 268 | return; 269 | 270 | pauseGame = !pauseGame; 271 | pauseShown = false; 272 | } 273 | 274 | void sn_moveRight() // move right 275 | { 276 | if(!snActive || gameOver || snStartup || pauseGame) 277 | return; 278 | 279 | sdx = 1; 280 | sdy = 0; 281 | } 282 | 283 | void sn_moveLeft() // move left 284 | { 285 | if(!snActive || gameOver || snStartup || pauseGame) 286 | return; 287 | 288 | sdx = -1; 289 | sdy = 0; 290 | } 291 | 292 | void sn_moveUp() // move down 293 | { 294 | if(!snActive || gameOver || snStartup || pauseGame) 295 | return; 296 | 297 | sdx = 0; 298 | sdy = -1; 299 | } 300 | 301 | void sn_moveDown() // move down 302 | { 303 | if(!snActive || gameOver || snStartup || pauseGame) 304 | return; 305 | 306 | sdx = 0; 307 | sdy = 1; 308 | } 309 | -------------------------------------------------------------------------------- /src/input.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * FCRemote Class: Remote control handling 9 | * Inspired by Ken Shirriff's IRRemote library 10 | * 11 | * ------------------------------------------------------------------- 12 | * License: MIT 13 | * 14 | * Permission is hereby granted, free of charge, to any person 15 | * obtaining a copy of this software and associated documentation 16 | * files (the "Software"), to deal in the Software without restriction, 17 | * including without limitation the rights to use, copy, modify, 18 | * merge, publish, distribute, sublicense, and/or sell copies of the 19 | * Software, and to permit persons to whom the Software is furnished to 20 | * do so, subject to the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 29 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 30 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | 34 | #include 35 | 36 | #include "input.h" 37 | 38 | /* 39 | * IRRemote class 40 | */ 41 | 42 | #define TMR_TIME 0.00005 // 0.00005s = 50us 43 | #define TMR_PRESCALE 80 44 | #define TMR_TICKS (uint64_t)(((double)TMR_TIME * 80000000.0) / (double)TMR_PRESCALE) 45 | #define TME_TIMEUS (TMR_TIME * 1000000) 46 | 47 | #define GAP_DUR 5000 // Minimum gap between transmissions in us (microseconds) 48 | #define GAP_TICKS (GAP_DUR / TME_TIMEUS) 49 | 50 | // IR receiver pin polarity 51 | #define IR_LIGHT 0 52 | #define IR_DARK 1 53 | 54 | static void IRAM_ATTR IRTimer_ISR(); 55 | 56 | static uint8_t _ir_pin; 57 | 58 | static volatile uint32_t _cnt = 0; 59 | static volatile IRState _irstate = IRSTATE_IDLE; 60 | static volatile uint32_t _irlen = 0; 61 | static volatile uint32_t _irbuf[IRBUFSIZE]; 62 | 63 | // ISR 64 | // Record duration of marks/spaces through a simple state machine 65 | static void IRAM_ATTR IRTimer_ISR() 66 | { 67 | uint8_t irpin = (uint8_t)digitalRead(_ir_pin); 68 | 69 | _cnt++; 70 | 71 | switch(_irstate) { 72 | case IRSTATE_IDLE: 73 | if(irpin == IR_LIGHT) { 74 | if(_cnt >= GAP_TICKS) { 75 | // Current gap longer than minimum gap size, 76 | // start recording. 77 | // (In case of a smaller gap, we assume being in 78 | // the middle of a transmission whose start we 79 | // missed. Do nothing then. 80 | _irstate = IRSTATE_LIGHT; 81 | _irbuf[0] = _cnt; // First is length of previous gap 82 | _irlen = 1; 83 | } 84 | _cnt = 0; 85 | } 86 | break; 87 | case IRSTATE_LIGHT: 88 | if(irpin == IR_DARK) { 89 | _irstate = IRSTATE_DARK; 90 | _irbuf[_irlen++] = _cnt; 91 | _cnt = 0; 92 | if(_irlen >= IRBUFSIZE) _irstate = IRSTATE_STOP; 93 | } 94 | break; 95 | case IRSTATE_DARK: 96 | if(irpin == IR_LIGHT) { 97 | _irstate = IRSTATE_LIGHT; 98 | _irbuf[_irlen++] = _cnt; 99 | _cnt = 0; 100 | if(_irlen >= IRBUFSIZE) _irstate = IRSTATE_STOP; 101 | } else if(_cnt > GAP_TICKS) { 102 | // Gap longer than usual space, transmission finished. 103 | _irstate = IRSTATE_STOP; 104 | } 105 | break; 106 | case IRSTATE_STOP: 107 | if(irpin == IR_LIGHT) _cnt = 0; // Reset cnt whenever we see something, even if we miss recording it 108 | break; 109 | } 110 | } 111 | 112 | // Store basic config data 113 | IRRemote::IRRemote(uint8_t timer_no, uint8_t ir_pin) 114 | { 115 | _timer_no = timer_no; 116 | _ir_pin = ir_pin; 117 | } 118 | 119 | void IRRemote::begin() 120 | { 121 | pinMode(_ir_pin, INPUT); 122 | _irstate = IRSTATE_IDLE; 123 | _irlen = 0; 124 | 125 | // Install & enable interrupt 126 | _IRTimer = timerBegin(_timer_no, TMR_PRESCALE, true); 127 | timerAttachInterrupt(_IRTimer, &IRTimer_ISR, true); 128 | timerAlarmWrite(_IRTimer, TMR_TICKS, true); 129 | timerAlarmEnable(_IRTimer); 130 | } 131 | 132 | // Decode IR signal 133 | bool IRRemote::loop() 134 | { 135 | // No new transmission, bail... 136 | if(_irstate != IRSTATE_STOP) 137 | return false; 138 | 139 | // Copy result to backup buffer 140 | _buflen = _irlen; 141 | for(uint8_t i = 0; i < _buflen; i++) { 142 | _buf[i] = _irbuf[i]; 143 | } 144 | 145 | // Continue recording 146 | resume(); 147 | 148 | // Calc hash on received "code" 149 | if(calcHash()) { 150 | //unsigned long now = millis(); 151 | // Repeat-key-avoidance hinders game play! 152 | //if(_hvalue == _prevHash) { 153 | // if(now - _prevTime < 300) { 154 | // _prevTime = now; 155 | // return false; 156 | // } 157 | //} 158 | _prevHash = _hvalue; 159 | //_prevTime = now; 160 | return true; 161 | } 162 | 163 | return false; 164 | } 165 | 166 | void IRRemote::resume() 167 | { 168 | _irstate = IRSTATE_IDLE; 169 | } 170 | 171 | uint32_t IRRemote::readHash() 172 | { 173 | return _hvalue; 174 | } 175 | 176 | 177 | /* CalcHash: Calculate hash over an arbitrary IR code 178 | * 179 | * Based on code published here: 180 | * http://arcfn.com/2010/01/using-arbitrary-remotes-with-arduino.html 181 | * 182 | */ 183 | 184 | uint32_t IRRemote::compare(uint32_t a, uint32_t b) 185 | { 186 | if(b < a * 80 / 100) return 0; 187 | if(a < b * 80 / 100) return 2; 188 | return 1; 189 | } 190 | 191 | /* Converts the raw code values into a 32-bit hash code. 192 | * Use FNV-1 (Fowler–Noll–Vo) hash algorithm. 193 | * https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function 194 | */ 195 | 196 | #define FNV_PRIME_32 16777619 197 | #define FNV_BASIS_32 2166136261 198 | 199 | bool IRRemote::calcHash() 200 | { 201 | if(_buflen < 6) 202 | return false; 203 | 204 | uint32_t hash = FNV_BASIS_32; 205 | 206 | for(int i = 1; i + 2 < _buflen; i++) { 207 | hash = (hash * FNV_PRIME_32) ^ compare(_buf[i], _buf[i+2]); 208 | } 209 | 210 | _hvalue = hash; 211 | 212 | return true; 213 | } 214 | 215 | /* 216 | * SIDButton class 217 | * 218 | * If a Long-Press-function is registered, a "press" is only reported only after 219 | * the button is released. If no such function is registered, a press is 220 | * reported immediately (after PressTicks have elapsed), regardless of a button 221 | * release. The latter mode is used for when the TCD is connected to trigger 222 | * time travels. 223 | */ 224 | 225 | /* pin: The pin to be used 226 | * activeLow: Set to true when the input level is LOW when the button is pressed, Default is true. 227 | * pullupActive: Activate the internal pullup when available. Default is true. 228 | */ 229 | SIDButton::SIDButton(const int pin, const boolean activeLow, const bool pullupActive) 230 | { 231 | _pin = pin; 232 | 233 | _buttonPressed = activeLow ? LOW : HIGH; 234 | 235 | pinMode(pin, pullupActive ? INPUT_PULLUP : INPUT); 236 | } 237 | 238 | 239 | // Number of millisec that have to pass by before a click is assumed stable. 240 | void SIDButton::setDebounceTicks(const int ticks) 241 | { 242 | _debounceTicks = ticks; 243 | } 244 | 245 | 246 | // Number of millisec that have to pass by before a short press is detected. 247 | void SIDButton::setPressTicks(const int ticks) 248 | { 249 | _pressTicks = ticks; 250 | } 251 | 252 | 253 | // Number of millisec that have to pass by before a long press is detected. 254 | void SIDButton::setLongPressTicks(const int ticks) 255 | { 256 | _longPressTicks = ticks; 257 | } 258 | 259 | // Register function for short press event 260 | void SIDButton::attachPress(void (*newFunction)(void)) 261 | { 262 | _pressFunc = newFunction; 263 | } 264 | 265 | // Register function for long press start event 266 | void SIDButton::attachLongPressStart(void (*newFunction)(void)) 267 | { 268 | _longPressStartFunc = newFunction; 269 | } 270 | 271 | // Register function for long press stop event 272 | void SIDButton::attachLongPressStop(void (*newFunction)(void)) 273 | { 274 | _longPressStopFunc = newFunction; 275 | } 276 | 277 | // Check input of the pin and advance the state machine 278 | void SIDButton::scan(void) 279 | { 280 | unsigned long now = millis(); 281 | unsigned long waitTime = now - _startTime; 282 | bool active = (digitalRead(_pin) == _buttonPressed); 283 | 284 | switch(_state) { 285 | case TCBS_IDLE: 286 | if(active) { 287 | transitionTo(TCBS_PRESSED); 288 | _startTime = now; 289 | } 290 | break; 291 | 292 | case TCBS_PRESSED: 293 | if((!active) && (waitTime < _debounceTicks)) { // de-bounce 294 | transitionTo(_lastState); 295 | } else if(!active) { 296 | transitionTo(TCBS_RELEASED); 297 | _startTime = now; 298 | } else if(active) { 299 | if(!_longPressStartFunc) { 300 | if(waitTime > _pressTicks) { 301 | if(_pressFunc) _pressFunc(); 302 | _pressNotified = true; 303 | } 304 | } else if(waitTime > _longPressTicks) { 305 | if(_longPressStartFunc) _longPressStartFunc(); 306 | transitionTo(TCBS_LONGPRESS); 307 | } 308 | } 309 | break; 310 | 311 | case TCBS_RELEASED: 312 | if((active) && (waitTime < _debounceTicks)) { // de-bounce 313 | transitionTo(_lastState); 314 | } else if((!active) && (waitTime > _pressTicks)) { 315 | if(!_pressNotified && _pressFunc) _pressFunc(); 316 | reset(); 317 | } 318 | break; 319 | 320 | case TCBS_LONGPRESS: 321 | if(!active) { 322 | transitionTo(TCBS_LONGPRESSEND); 323 | _startTime = now; 324 | } 325 | break; 326 | 327 | case TCBS_LONGPRESSEND: 328 | if((active) && (waitTime < _debounceTicks)) { // de-bounce 329 | transitionTo(_lastState); 330 | } else if(waitTime >= _debounceTicks) { 331 | if(_longPressStopFunc) _longPressStopFunc(); 332 | reset(); 333 | } 334 | break; 335 | 336 | default: 337 | transitionTo(TCBS_IDLE); 338 | break; 339 | } 340 | } 341 | 342 | /* 343 | * Private 344 | */ 345 | 346 | void SIDButton::reset(void) 347 | { 348 | _state = TCBS_IDLE; 349 | _lastState = TCBS_IDLE; 350 | _startTime = 0; 351 | _pressNotified = false; 352 | } 353 | 354 | // Advance to new state 355 | void SIDButton::transitionTo(ButtonState nextState) 356 | { 357 | _lastState = _state; 358 | _state = nextState; 359 | } 360 | 361 | -------------------------------------------------------------------------------- /src/sid-A10001986.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * License: MIT 9 | * 10 | * Permission is hereby granted, free of charge, to any person 11 | * obtaining a copy of this software and associated documentation 12 | * files (the "Software"), to deal in the Software without restriction, 13 | * including without limitation the rights to use, copy, modify, 14 | * merge, publish, distribute, sublicense, and/or sell copies of the 15 | * Software, and to permit persons to whom the Software is furnished to 16 | * do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be 19 | * included in all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | */ 29 | 30 | /* 31 | * Build instructions (for Arduino IDE) 32 | * 33 | * - Install the Arduino IDE 34 | * https://www.arduino.cc/en/software 35 | * 36 | * - This firmware requires the "ESP32-Arduino" framework. To install this framework, 37 | * in the Arduino IDE, go to "File" > "Preferences" and add the URL 38 | * https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 39 | * to "Additional Boards Manager URLs". The list is comma-separated. 40 | * 41 | * - Go to "Tools" > "Board" > "Boards Manager", then search for "esp32", and install 42 | * the latest version by Espressif Systems. 43 | * Detailed instructions for this step: 44 | * https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html 45 | * 46 | * - Go to "Tools" > "Board: ..." -> "ESP32 Arduino" and select your board model (the 47 | * CircuitSetup original boards are "NodeMCU-32S") 48 | * 49 | * - Connect your ESP32 board. 50 | * Note that NodeMCU ESP32 boards come in two flavors that differ in which serial 51 | * communications chip is used: Either SLAB CP210x USB-to-UART or CH340. Installing 52 | * a driver might be required. 53 | * Mac: 54 | * For the SLAB CP210x (which is used by NodeMCU-boards distributed by CircuitSetup) 55 | * installing a driver is required: 56 | * https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads 57 | * The port ("Tools -> "Port") is named /dev/cu.SLAB_USBtoUART, and the maximum 58 | * upload speed ("Tools" -> "Upload Speed") can be used. 59 | * The CH340 is supported out-of-the-box since Mojave. The port is named 60 | * /dev/cu.usbserial-XXXX (XXXX being some random number), and the maximum upload 61 | * speed is 460800. 62 | * Windows: No idea. Not been using Windows since 1999. 63 | * 64 | * - Install required libraries. In the Arduino IDE, go to "Tools" -> "Manage Libraries" 65 | * and install the following libraries: 66 | * - WifiManager (tablatronix, tzapu) https://github.com/tzapu/WiFiManager 67 | * (Tested with 2.0.13beta, 2.0.15-rc1, 2.0.16-rc2) 68 | * For versions 2.0.16-rc2 and below, in order to avoid a delay when powering up 69 | * several BTTFN-connected props, change _preloadwifiscan to false in WiFiManager.h 70 | * before compiling: 71 | * -boolean _preloadwifiscan = true; 72 | * +boolean _preloadwifiscan = false; 73 | * - ArduinoJSON >= 6.19: https://arduinojson.org/v6/doc/installation/ 74 | * 75 | * - Download the complete firmware source code: 76 | * https://github.com/realA10001986/SID/archive/refs/heads/main.zip 77 | * Extract this file somewhere. Enter the "sid-A10001986" folder and 78 | * double-click on "sid-A10001986.ino". This opens the firmware in the 79 | * Arduino IDE. 80 | * 81 | * - Go to "Sketch" -> "Upload" to compile and upload the firmware to your ESP32 board. 82 | */ 83 | 84 | /* Changelog 85 | * 86 | * 2023/11/05 (A10001986) 87 | * - Settings: Write JSON to buffer before file 88 | * - Fix corrupt CfgOnSD setting 89 | * 2023/11/04 (A10001986) 90 | * - Unmount filesystems before reboot 91 | * 2023/11/02 (A10001986) 92 | * - Start CP earlier to reduce network traffic delay caused by that darn WiFi 93 | * scan upon CP start. 94 | * * WiFiManager: Disable pre-scanning of WiFi networks when starting the CP. 95 | * Scanning is now only done when accessing the "Configure WiFi" page. 96 | * To do that in your own installation, set _preloadwifiscan to false 97 | * in WiFiManager.h 98 | * 2023/10/31 (A10001986) 99 | * - BTTFN: User can now enter TCD's hostname instead of IP address. If hostname 100 | * is given, TCD must be on same local network. Uses multicast, not DNS. 101 | * - Change default for "skip tt anim" - default is now movie-like. 102 | * 2023/10/29 (A10001986) 103 | * - Add "Adhere strictly to movie patterns" option, which is ON by default. 104 | * If set, only parts of the movie-extracted time travel sequence (with 105 | * interpolations) are shown when idle (modes 0-3) and when using GPS 106 | * speed. If unset, random variations will be shown. This is less boring, 107 | * but also less authentic. 108 | * This option can be changed in the CP, and by *50OK (6050). If an SD card 109 | * is present, every change will be saved. If no SD card is present, only 110 | * the setting from the CP will be persistent. 111 | * - Change command sequence for SA peaks to *51OK (6051). 112 | * - Fix saving idle mode #5 113 | * 2023/10/27 (A10001986) 114 | * - Make time tunnel animation (flicker) optional, purists might want to 115 | * disable it. 116 | * 2023/10/27 (A10001986) [1.0] 117 | * - Fix MQTT idle sequence selection 118 | * 2023/10/26 (A10001986) 119 | * - Add "Universal backlot in the early 2000s" idle sequence (#4) 120 | * - SA: Limit height during TT accoring to final pattern 121 | * 2023/10/25 (A10001986) 122 | * - SA: Make FFT in float instead of double and thereby speed it up 123 | * - SA: Redo bar scaling; tweak frequency bands 124 | * - "Disable" some lamps in tt sequence 125 | * 2023/10/24 (A10001986) 126 | * - Switch i2c speed to 400kHz 127 | * - Various fixes to (until now blindly written) Spectrum Analyzer 128 | * 2023/10/05 (A10001986) 129 | * - Add support for "wakeup" command (BTTFN/MQTT) 130 | * 2023/09/30 (A10001986) 131 | * - Extend remote commands to 32 bit 132 | * - Fix ring buffer handling for remote commands 133 | * 2023/09/26 (A10001986) 134 | * - Clean up options 135 | * - Fix TT button scan during TT 136 | * - Clear display when Config Portal settings are saved 137 | * 2023/09/25 (A10001986) 138 | * - Add option to handle TT triggers from wired TCD without 5s lead. Respective 139 | * options on TCD and external props must be set identically. 140 | * 2023/09/23 (A10001986) 141 | * - Add remote control facility through TCD keypad (requires BTTFN connection 142 | * with TCD). Commands for SID are 6000-6999. 143 | * - Changed some command sequences 144 | * 2023/09/13 (A10001986) 145 | * - Siddly: Add progress bar in red line 146 | * 2023/09/11 (A10001986) 147 | * - Make SA peaks an option (CP and *50OK) 148 | * - Guard SPIFFS/LittleFS calls with FS check 149 | * 2023/09/10 (A10001986) 150 | * - If specific config file not found on SD, read from FlashFS - but only 151 | * if it is mounted. 152 | * 2023/09/09 (A10001986) 153 | * - Switch to LittleFS by default 154 | * - *654321OK lets SID forget learned IR remote control 155 | * - Remove "Wait for TCD fake power on" option 156 | * - Fix SD pin numbers 157 | * - Save current idle pattern to SD for persistence 158 | * (only if changed via IR, not MQTT) 159 | * - If SD mount fails at 16Mhz, retry at 25Mhz 160 | * - If specific config file not found on SD, read from FlashFS 161 | * 2023/09/08 (A10001986) 162 | * - TT sequence changes 163 | * - Changed brightness adjustments from left/right to up/down 164 | * 2023/09/07 (A10001986) 165 | * - Better IR learning guidance 166 | * - Add'l TT sequence changes for idle mode 3 167 | * - Add some MQTT commands 168 | * 2023/09/06 (A10001986) 169 | * - Add alternative idle modes 170 | * - Add tt "sequence" for sa mode 171 | * 2023/09/02 (A10001986) 172 | * - Handle dynamic ETTO LEAD for BTTFN-triggered time travels 173 | * - Go back to stand-alone mode if BTTFN polling times-out 174 | * - Rework sequences 175 | * - Fixes for SA 176 | * 2023/09/01 (A10001986) 177 | * - Add TT sequence (unfinished) 178 | * 2023/08/31 (A10001986) 179 | * - Further fixes for games 180 | * - Spectrum analyzer not changed due to hardware issues 181 | * 2023/08/28 (A10001986) 182 | * - Fixes for Siddly, text output, i2c communication, and general. 183 | * - Test code in SA. 184 | * 2023/08/27 (A10001986) 185 | * - Adapt to TCD's WiFi name appendix option 186 | * - Add "AP name appendix" setting; allows unique AP names when running multiple 187 | * SIDs in AP mode in close range. 7 characters, 0-9/a-z/A-Z/- only, will be 188 | * added to "SID-AP". 189 | * - Add AP password: Allows to configure a WPA2 password for the SID's AP mode 190 | * (empty or 8 characters, 0-9/a-z/A-Z/- only) 191 | * - *123456OK not only clears static IP config (as before), but also clears AP mode 192 | * WiFi password. 193 | * 2023/08/25 (A10001986) 194 | * - Remove "Wait for TCD WiFi" option - this is not required; if the TCD is acting 195 | * access point, it is supposed to be in car mode, and a delay is not required. 196 | * (Do not let the TCD search for a configured WiFi network and have the FC rely on 197 | * the TCD falling back to AP mode; this will take long and the FC might time-out 198 | * unless the FC has a couple of connection retrys and long timeouts configured! 199 | * Use car mode, or delete the TCD's configured WiFi network if you power up the 200 | * TCD and FC at the same time.) 201 | * - Some code cleanups 202 | * - Restrict WiFi Retrys to 10 (like WiFiManager) 203 | * - Add "Wait for fake power on" option; if set, FC only boots 204 | * after it received a fake-power-on signal from the TCD 205 | * (Needs "Follow fake power" option set) 206 | * - Fix parm handling of FPO and NM in fc_wifi 207 | * 2023/08/20 (A10001986) 208 | * - Fixes for siddisplay 209 | * 2023/08/14 (A10001986) 210 | * - Add config option to disable the default IR control 211 | * 2023/08/01 (A10001986) 212 | * - Fix/enhance ABORT_TT (BTTFN/MQTT) [like FC] 213 | * 2023/07/23 (A10001986) 214 | * - First version of translator table according to current schematics 215 | * 2023/07/22 (A10001986) 216 | * - BTTFN dev type 217 | * 2023/07/21 (A10001986) 218 | * - Add LED translator skeleton, prepare display routines 219 | * 2023/07/09 (A10001986) 220 | * - BTTFN: Add night mode and fake power support (both signalled from TCD) 221 | * 2023/07/07 (A10001986) 222 | * - Add TCD notifications: New way of wirelessly connecting the props by WiFi (aptly named 223 | * "BTTFN"), without MQTT. Needs IP address of TCD entered in CP. Either MQTT or BTTFN is 224 | * used; if the TCD is configured to use MQTT, it will not send notifications via BTTFN. 225 | * - Add "screen saver": Deactivate all LEDs after a configurable number of minutes 226 | * of inactivity. TT button press, IR control key, time travel deactivates screen 227 | * saver. (If IR is locked, only '#' key will deactivate ss.) 228 | * 2023/07/03 (A10001986) 229 | * - Save ir lock state (*71OK), make it persitent over reboots 230 | * - IR input: Disable repeated-keys detection, this hinders proper game play 231 | * Remotes that send repeated keys (when holding the button) are not usable. 232 | * 2023/06/29 (A10001986) 233 | * - Add font and letter sequences 234 | * 2023/06/28 (A10001986) 235 | * - Initial version: unfinished, entirely untested 236 | * Missing: Display control; pin assignments; all sequences; etc. 237 | */ 238 | 239 | #include "sid_global.h" 240 | 241 | #include 242 | #include 243 | 244 | #include "sid_settings.h" 245 | #include "sid_wifi.h" 246 | #include "sid_main.h" 247 | 248 | void setup() 249 | { 250 | powerupMillis = millis(); 251 | 252 | Serial.begin(115200); 253 | Serial.println(); 254 | 255 | Wire.begin(-1, -1, 400000); 256 | 257 | main_boot(); 258 | settings_setup(); 259 | wifi_setup(); 260 | main_setup(); 261 | bttfn_loop(); 262 | } 263 | 264 | void loop() 265 | { 266 | main_loop(); 267 | wifi_loop(); 268 | bttfn_loop(); 269 | } 270 | -------------------------------------------------------------------------------- /src/sid_sa.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Spectrum Analyzer 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #include "sid_global.h" 34 | #include 35 | #include "src/arduinoFFT/arduinoFFT.h" 36 | #include 37 | #include 38 | #include 39 | #include "sid_main.h" 40 | 41 | #define NUMBANDS 11 // Number of bands ("bins" in FFT-speak) 42 | #define DISPLAYBANDS 10 // Displayed number of bands 43 | #define LEDS_PER_BAR 20 // Height of bar 44 | 45 | #define NUMSAMPLES 1024 // Size of sample block 46 | #define SAMPLERATE 32000 // Sampling frequency 47 | 48 | #define PEAK_HOLD 500 // ms - Peak hold time 49 | #define PEAK_FALL 100 // ms - Peak fall speed 50 | 51 | //#define SA_DBG_WRITEOUT // For debugging 52 | 53 | static const i2s_port_t I2S_PORT = I2S_NUM_0; 54 | 55 | static int32_t rawSamples[NUMSAMPLES]; 56 | static FTYPE vReal[NUMSAMPLES]; 57 | static FTYPE vImag[NUMSAMPLES]; 58 | 59 | static FTYPE freqBands[NUMBANDS] = { 0 }; 60 | 61 | // 32 = 32ms * 32 = 1 sec 62 | // 64 = 32ms * 64 = 2 secs 63 | // 128 = 32ms * 128 = 4 secs 64 | #define FQ_HIST 128 65 | static int histIdx = 0; 66 | static FTYPE freqBandsHistory[FQ_HIST][NUMBANDS] = { 0 }; 67 | 68 | // The frequency bands 69 | // First one is "garbage bin", not used for display 70 | static int freqSteps[NUMBANDS] = { 71 | //110, 200, 400, 600, 800, 1000, 2500, 3500, 5000, 7000, 9000 72 | // 60, 150, 250, 400, 650, 1000, 1600, 2500, 4000, 6250, 10000 73 | 80, 100, 150, 250, 430, 600, 1000, 2000, 4000, 7000, 10000 74 | }; 75 | 76 | // Noise threshold per band. Lower bands have more noise. 77 | static int minTreshold[NUMBANDS] = { 78 | 0, 5000, 5000, 5000, 3000, 1000, 1000, 1000, 1000, 1000, 1000 79 | }; 80 | 81 | static const uint8_t maxTTHeight[10] = { 82 | 20, 20, 13, 20, 20, 20, 20, 10, 20, 17 83 | }; 84 | 85 | static int oldHeight[DISPLAYBANDS] = { 0 }; 86 | 87 | static uint8_t peaks[DISPLAYBANDS] = { 0 }; 88 | static unsigned long newPeak[DISPLAYBANDS] = { 0 }; 89 | static unsigned long peakTimer[DISPLAYBANDS] = { 0 }; 90 | 91 | bool saActive = false; 92 | static bool sa_avail = false; 93 | bool doPeaks = false; 94 | static bool startFlag = false; 95 | static bool initFlag = false; 96 | static bool initDisplay = true; 97 | static unsigned long lastTime = 0; 98 | static unsigned long lastStart = 0; 99 | static unsigned long startDelay = 0; 100 | 101 | int ampFact = 100; 102 | 103 | #if defined(SID_DBG) && defined(SA_DBG_WRITEOUT) 104 | #include "sid_settings.h" 105 | #include 106 | #include 107 | static File outFile; 108 | static bool outFileOpen = false; 109 | #endif 110 | 111 | static const i2s_pin_config_t i2sPins = { 112 | .bck_io_num = I2S_BCLK_PIN, 113 | .ws_io_num = I2S_LRCLK_PIN, 114 | .data_out_num = I2S_PIN_NO_CHANGE, 115 | .data_in_num = I2S_DIN_PIN 116 | }; 117 | 118 | static const i2s_config_t i2s_config = { 119 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), 120 | .sample_rate = SAMPLERATE, 121 | .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, 122 | .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, 123 | .communication_format = I2S_COMM_FORMAT_STAND_MSB, 124 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 125 | .dma_buf_count = 4, 126 | .dma_buf_len = 1024, 127 | .use_apll = false, 128 | .tx_desc_auto_clear = false, 129 | .fixed_mclk = 0 130 | }; 131 | 132 | static bool sa_setup() 133 | { 134 | esp_err_t err; 135 | 136 | if(sa_avail) 137 | return true; 138 | 139 | err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); 140 | if(err != ESP_OK) { 141 | #ifdef SID_DBG 142 | Serial.printf("sa_setup: Failed to install i2s driver (%d)\n", err); 143 | #endif 144 | return false; 145 | } 146 | 147 | // For SPH0645 148 | REG_SET_BIT(I2S_TIMING_REG(I2S_PORT), BIT(9)); 149 | REG_SET_BIT(I2S_CONF_REG(I2S_PORT), I2S_RX_MSB_SHIFT); 150 | 151 | i2s_set_pin(I2S_PORT, &i2sPins); 152 | 153 | sa_avail = true; 154 | 155 | #if defined(SID_DBG) && defined(SA_DBG_WRITEOUT) 156 | if(haveSD) { 157 | outFile = SD.open("/sidsa.pcm", FILE_WRITE); 158 | outFileOpen = true; 159 | } 160 | #endif 161 | 162 | return true; 163 | } 164 | 165 | #if 0 // Unused 166 | void sa_remove() 167 | { 168 | if(!sa_avail) 169 | return; 170 | 171 | i2s_driver_uninstall(I2S_PORT); 172 | sa_avail = false; 173 | } 174 | #endif 175 | 176 | // internal resume/stop 177 | 178 | static void sa_resume(bool initDisp, unsigned long start_Delay) 179 | { 180 | if(!sa_avail) 181 | sa_setup(); 182 | else 183 | i2s_start(I2S_PORT); 184 | 185 | lastTime = lastStart = millis(); 186 | startFlag = true; 187 | startDelay = start_Delay; 188 | initFlag = false; 189 | initDisplay = initDisp; 190 | } 191 | 192 | static void sa_stop() 193 | { 194 | i2s_stop(I2S_PORT); 195 | } 196 | 197 | // Externally called activate/deactivate 198 | 199 | void sa_activate(bool init, unsigned long start_Delay) 200 | { 201 | sa_resume(init, start_Delay); 202 | 203 | if(sa_avail) 204 | saActive = true; 205 | } 206 | 207 | void sa_deactivate() 208 | { 209 | if(sa_avail) 210 | sa_stop(); 211 | 212 | saActive = false; 213 | 214 | #if defined(SID_DBG) && defined(SA_DBG_WRITEOUT) 215 | outFile.close(); 216 | outFileOpen = false; 217 | #endif 218 | } 219 | 220 | // Set amplification factor 221 | 222 | int sa_setAmpFact(int newAmpFact) 223 | { 224 | int old = ampFact; 225 | 226 | if(newAmpFact >= 0) { 227 | ampFact = newAmpFact; 228 | } 229 | 230 | return old; 231 | } 232 | 233 | // The loop 234 | 235 | void sa_loop() 236 | { 237 | size_t bytesRead = 0; 238 | unsigned long now = millis(); 239 | int mmaxi = 0, band = 0; 240 | FTYPE mmax = 1.0; 241 | 242 | if(!saActive || !sa_avail) 243 | return; 244 | 245 | if(lastTime && (now - lastTime < (NUMSAMPLES * 1000 / SAMPLERATE))) 246 | return; 247 | 248 | lastTime = now; 249 | 250 | // Read i2c data - do I need a timeout? FIXME 251 | i2s_read(I2S_PORT, (void *)rawSamples, sizeof(rawSamples), &bytesRead, portMAX_DELAY); 252 | 253 | if(bytesRead != sizeof(rawSamples)) { 254 | // what now? 255 | #ifdef SID_DBG 256 | Serial.println("bytesRead != sizeof(rawSamples)"); 257 | #endif 258 | } 259 | 260 | #if defined(SID_DBG) && defined(SA_DBG_WRITEOUT) 261 | 262 | if(outFileOpen) { 263 | outFile.write((uint8_t *)&rawSamples[0], NUMSAMPLES * 4); 264 | } 265 | 266 | #else 267 | 268 | // Convert; clear vImag 269 | for(int i = 0; i < NUMSAMPLES; i++) { 270 | vReal[i] = (FTYPE)(rawSamples[i] / 16384); // do NOT shift; result of shifting negative integer is undefined 271 | vImag[i] = 0.0; 272 | } 273 | 274 | // Do the FFT 275 | arduinoFFT FFT = arduinoFFT(vReal, vImag, NUMSAMPLES, SAMPLERATE); 276 | 277 | // Remove hum and dc offset 278 | FFT.DCRemoval(); 279 | 280 | // Windowing: "Rectangle" does fine for our purpose 281 | // and since this does effectively nothing, skip it. 282 | //FFT.Windowing(FFT_WIN_TYP_RECTANGLE, FFT_FORWARD); 283 | //FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD); 284 | 285 | FFT.Compute(FFT_FORWARD); 286 | 287 | //FFT.ComplexToMagnitude(); // Covers entire array, half would do 288 | FFT.ComplexToMagnitude(vReal, vImag, NUMSAMPLES/2); 289 | 290 | // Fill frequency bands 291 | // Max freq = Half of sampling rate => (SAMPLERATE / 2) 292 | // vReal only filled half because of this => (NUMSAMPLES / 2) 293 | band = 0; 294 | for(int i = 3; i < NUMSAMPLES / 2; i++) { 295 | int freq = (i - 2) * (SAMPLERATE / 2) / (NUMSAMPLES / 2); 296 | if(freq >= freqSteps[band]) { 297 | band++; 298 | if(band == NUMBANDS) break; 299 | else freqBands[band] = 0.0; 300 | } 301 | if(band && (vReal[i] > minTreshold[band])) { 302 | freqBands[band] += vReal[i]; 303 | } 304 | } 305 | 306 | // Store absolute band sums to our history table 307 | for(int i = 1; i < NUMBANDS; i++) { 308 | freqBandsHistory[histIdx][i] = freqBands[i]; 309 | } 310 | histIdx++; 311 | histIdx &= (FQ_HIST-1); 312 | 313 | // Find maximum in history table for scaling each bar 314 | for(int i = 1; i < NUMBANDS; i++) { 315 | mmax = 1.0; 316 | for(int j = 0; j < FQ_HIST; j++) { 317 | if(mmax < freqBandsHistory[j][i]) mmax = freqBandsHistory[j][i]; 318 | } 319 | freqBands[i] /= mmax; 320 | } 321 | 322 | now = millis(); 323 | 324 | if(startFlag) { 325 | 326 | if(now - lastStart < startDelay) { 327 | if(!initFlag) { 328 | for(int i = 0; i < DISPLAYBANDS; i++) { 329 | peaks[i] = 0; 330 | newPeak[i] = now; 331 | peakTimer[i] = PEAK_HOLD; 332 | oldHeight[i] = 1; 333 | if(initDisplay) { 334 | sid.drawBarWithHeight(i, 1); 335 | } 336 | } 337 | if(initDisplay) { 338 | sid.show(); 339 | } 340 | initFlag = true; 341 | } 342 | } else { 343 | startFlag = false; 344 | histIdx = 0; 345 | for(int i = 0; i < FQ_HIST; i++) { 346 | for(int j = 1; j < NUMBANDS; j++) { 347 | freqBandsHistory[i][j] = 0.0; 348 | } 349 | } 350 | } 351 | 352 | } else { 353 | 354 | // Calculate bar heights 355 | for(int i = 0; i < DISPLAYBANDS; i++) { 356 | int height = (int)(freqBands[i+1] * (FTYPE)(LEDS_PER_BAR - 1)); 357 | 358 | if(ampFact != 100) { 359 | if(!height) height = 1; 360 | height = height * ampFact / 100; 361 | if(height > maxTTHeight[i]) height = maxTTHeight[i]; 362 | } 363 | 364 | if(height > LEDS_PER_BAR) height = LEDS_PER_BAR; 365 | if(!height) height = 1; 366 | 367 | // Smoothen jumps in downward direction 368 | if(height < oldHeight[i]) { 369 | if(oldHeight[i] - height > 10) height = (oldHeight[i] + height) / 2; 370 | else height = oldHeight[i] - 1; 371 | } 372 | 373 | // Now do peak 374 | if(height - 1 > peaks[i]) { 375 | peaks[i] = min(LEDS_PER_BAR - 1, height - 1); 376 | newPeak[i] = now; 377 | peakTimer[i] = PEAK_HOLD; 378 | } 379 | 380 | oldHeight[i] = height; 381 | 382 | // Draw bars & peaks 383 | sid.drawBarWithHeight(i, oldHeight[i]); 384 | if(doPeaks && peaks[i] > oldHeight[i] - 1) { 385 | sid.drawDot(i, peaks[i]); 386 | } 387 | } 388 | 389 | // Put result on display 390 | sid.show(); 391 | } 392 | 393 | now = millis(); 394 | 395 | // Make peaks fall down 396 | for(int i = 0; i < DISPLAYBANDS; i++) { 397 | if(newPeak[i] && now - newPeak[i] > peakTimer[i]) { 398 | if(peaks[i] > 0) { 399 | peakTimer[i] = PEAK_FALL; 400 | newPeak[i] = now; 401 | peaks[i]--; 402 | } else { 403 | newPeak[i] = 0; 404 | } 405 | } 406 | } 407 | 408 | #endif 409 | } 410 | -------------------------------------------------------------------------------- /src/sid_siddly.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Siddly 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #include "sid_global.h" 34 | 35 | #include 36 | 37 | #include "sid_siddly.h" 38 | #include "sid_main.h" 39 | 40 | #define WIDTH 10 41 | #define HEIGHT 19 42 | 43 | #define NUM_LEVELS 9 44 | #define PIECES_PER_LEVEL 40 45 | 46 | bool siActive = false; 47 | 48 | static uint8_t board[HEIGHT][WIDTH] = { { 0 } }; 49 | 50 | #define NUM_PIECES 7 51 | static uint8_t p1[3][3] = { {0,0,1}, {1,1,1}, {0,0,0} }; 52 | static uint8_t p2[3][3] = { {1,0,0}, {1,1,1}, {0,0,0} }; 53 | static uint8_t p3[3][3] = { {0,1,1}, {1,1,0}, {0,0,0} }; 54 | static uint8_t p4[3][3] = { {1,1,0}, {0,1,1}, {0,0,0} }; 55 | static uint8_t p5[3][3] = { {0,1,0}, {1,1,1}, {0,0,0} }; 56 | static uint8_t p6[2][2] = { {1,1}, {1,1} }; 57 | static uint8_t p7[4][4] = { {0,0,0,0}, {1,1,1,1}, {0,0,0,0}, {0,0,0,0} }; 58 | 59 | static uint8_t *pd[NUM_PIECES] = { 60 | (uint8_t *)p1, (uint8_t *)p2, (uint8_t *)p3, (uint8_t *)p4, (uint8_t *)p5, (uint8_t *)p6, (uint8_t *)p7 61 | }; 62 | 63 | static uint8_t ps[NUM_PIECES] = { 3, 3, 3, 3, 3, 2, 4 }; 64 | 65 | static unsigned long ldelays[NUM_LEVELS] = { 66 | 1000, 900, 800, 700, 600, 500, 400, 300, 200 67 | }; 68 | 69 | static uint8_t cp = 0; // current piece index 70 | static uint8_t cps = 0; // current piece size 71 | static uint8_t cpd[4][4] = { { 0 } }; // current piece data (copy of pX above) 72 | static int cpx = 0; // current x position 73 | static int cpy = 0; // current y position 74 | 75 | static unsigned long cp_now = 0; 76 | 77 | static unsigned long siStartup = 0; // startup sequence running 78 | static bool havePiece = false; 79 | static bool gameOver = false; 80 | static bool gameOverShown = false; 81 | static bool removeCycle = false; 82 | static bool pauseGame = false; 83 | static bool pauseShown = false; 84 | static int level = 0; // current level (speed) 85 | static int pcnt = 0; // piece count in level 86 | 87 | static void clearBoard() 88 | { 89 | memset(board, 0, sizeof(board)); 90 | } 91 | 92 | static void setBoardAt(int x, int y) 93 | { 94 | if(y < 0 || y > HEIGHT - 1) return; 95 | if(x < 0 || x > WIDTH - 1) return; 96 | 97 | board[y][x] = 1; 98 | } 99 | 100 | static int boardAt(int x, int y) 101 | { 102 | if(y < 0 || y > HEIGHT - 1) return 0; 103 | if(x < 0 || x > WIDTH - 1) return 0; 104 | 105 | return board[y][x]; 106 | } 107 | 108 | static bool lineIsFull(int y) 109 | { 110 | for(int x = 0; x < WIDTH; x++) { 111 | if(!board[y][x]) 112 | return false; 113 | } 114 | return true; 115 | } 116 | 117 | static void copyLine(int sy, int dy) 118 | { 119 | for(int x = 0; x < WIDTH; x++) { 120 | board[dy][x] = board[sy][x]; 121 | } 122 | } 123 | 124 | static void clearLine(int y) 125 | { 126 | for(int x = 0; x < WIDTH; x++) { 127 | board[y][x] = 0; 128 | } 129 | } 130 | 131 | static void removeLine(int yy) 132 | { 133 | if(yy > 0) { 134 | for(int y = yy; y > 0; y--) { 135 | copyLine(y - 1, y); 136 | } 137 | } 138 | 139 | clearLine(0); 140 | } 141 | 142 | static void removeFullLines() 143 | { 144 | for(int y = HEIGHT - 1; y >= 0; y--) { 145 | if(lineIsFull(y)) { 146 | removeLine(y); 147 | y++; 148 | } 149 | } 150 | } 151 | 152 | static bool canPlace() 153 | { 154 | bool mat = false; 155 | int l = 0; 156 | 157 | for(int y = cps - 1; y >= 0; y--) { 158 | for(int x = 0; x < cps; x++) { 159 | if(cpd[y][x]) { 160 | mat = true; 161 | if(boardAt(cpx + x, cpy)) 162 | return false; 163 | } 164 | } 165 | if(mat) l++; 166 | if(cpy - l < 0) 167 | break; 168 | } 169 | return true; 170 | } 171 | 172 | static bool canRotate() 173 | { 174 | for(int y = 0; y < cps; y++) { 175 | for(int x = 0; x < cps; x++) { 176 | if(cpd[y][x]) { 177 | int ny = cpy + cps - 1 - x; 178 | int nx = cpx + y; 179 | if((ny >= HEIGHT) || 180 | (nx < 0 || nx >= WIDTH) || 181 | (boardAt(nx, ny))) 182 | return false; 183 | } 184 | } 185 | } 186 | return true; 187 | } 188 | 189 | static void rotate() 190 | { 191 | uint8_t cpdb[4][4] = { { 0 } }; 192 | 193 | for(int y = 0; y < cps; y++) { 194 | for(int x = 0; x < cps; x++) { 195 | cpdb[cps - 1 - x][y] = cpd[y][x]; 196 | } 197 | } 198 | memcpy(cpd, cpdb, sizeof(cpd)); 199 | } 200 | 201 | static bool canMoveDown() 202 | { 203 | for(int y = 0; y < cps; y++) { 204 | for(int x = 0; x < cps; x++) { 205 | if(cpd[y][x]) { 206 | if((cpy + y + 1 >= HEIGHT) || 207 | boardAt(cpx + x, cpy + y + 1)) 208 | return false; 209 | } 210 | } 211 | } 212 | return true; 213 | } 214 | 215 | static void moveDown() 216 | { 217 | cpy++; 218 | } 219 | 220 | static bool canMoveLeft() 221 | { 222 | for(int y = 0; y < cps; y++) { 223 | for(int x = 0; x < cps; x++) { 224 | if(cpd[y][x]) { 225 | if((cpx + x - 1 < 0) || 226 | boardAt(cpx + x - 1, cpy + y)) 227 | return false; 228 | } 229 | } 230 | } 231 | return true; 232 | } 233 | 234 | static void moveLeft() 235 | { 236 | cpx--; 237 | } 238 | 239 | static bool canMoveRight() 240 | { 241 | for(int y = 0; y < cps; y++) { 242 | for(int x = 0; x < cps; x++) { 243 | if(cpd[y][x]) { 244 | if((cpx + x + 1 >= WIDTH) || 245 | boardAt(cpx + x + 1, cpy + y)) 246 | return false; 247 | } 248 | } 249 | } 250 | return true; 251 | } 252 | 253 | static void moveRight() 254 | { 255 | cpx++; 256 | } 257 | 258 | static bool newPiece() 259 | { 260 | cp = esp_random() % NUM_PIECES; 261 | cps = ps[cp]; 262 | cpx = (WIDTH - cps) / 2; 263 | cpy = 0; 264 | 265 | uint8_t *npd = pd[cp]; 266 | for(int y = 0; y < cps; y++) { 267 | for(int x = 0; x < cps; x++) { 268 | cpd[y][x] = *(npd + (y * cps) + x); 269 | } 270 | } 271 | 272 | if(canPlace()) { 273 | cp_now = millis(); 274 | pcnt++; 275 | havePiece = true; 276 | } else { 277 | gameOver = true; 278 | havePiece = false; 279 | return false; 280 | } 281 | 282 | return true; 283 | } 284 | 285 | static void updateDisplay() 286 | { 287 | uint8_t myField[WIDTH * (HEIGHT + 1)]; 288 | 289 | memset((void *)myField, 0, WIDTH); 290 | 291 | for(int i = 0; i < min(10, ((PIECES_PER_LEVEL - pcnt) * 10 / PIECES_PER_LEVEL) + 1); i++) { 292 | myField[i] = 1; 293 | } 294 | 295 | memcpy((void *)(myField + WIDTH), (void *)board, WIDTH * HEIGHT); 296 | 297 | if(havePiece) { 298 | for(int y = 0; y < cps; y++) { 299 | for(int x = 0; x < cps; x++) { 300 | if(cpd[y][x]) { 301 | myField[((cpy + y + 1) * WIDTH) + cpx + x] = 1; 302 | } 303 | } 304 | } 305 | } 306 | sid.drawFieldAndShow((uint8_t *)myField); 307 | } 308 | 309 | static void resetGame() 310 | { 311 | pcnt = 0; 312 | level = 0; 313 | gameOver = gameOverShown = false; 314 | pauseGame = pauseShown = false; 315 | 316 | clearBoard(); 317 | } 318 | 319 | void si_init() // start game 320 | { 321 | resetGame(); 322 | 323 | showWordSequence("SIDDLY", 2); 324 | 325 | siStartup = millis(); 326 | siActive = true; 327 | } 328 | 329 | void si_loop() 330 | { 331 | unsigned long now = millis(); 332 | 333 | if(!siActive) 334 | return; 335 | 336 | if(gameOver) { 337 | if(!gameOverShown) { 338 | showWordSequence("GAME OVER ", 1); 339 | gameOverShown = true; 340 | return; 341 | } 342 | resetGame(); 343 | newPiece(); 344 | updateDisplay(); 345 | return; 346 | } 347 | 348 | if(siStartup) { 349 | if(now - siStartup < 1000) { 350 | return; 351 | } 352 | siStartup = 0; 353 | newPiece(); 354 | updateDisplay(); 355 | return; 356 | } 357 | 358 | if(pauseGame) { 359 | if(!pauseShown) { 360 | sid.drawLetterAndShow('P'); 361 | pauseShown = true; 362 | } 363 | return; 364 | } 365 | 366 | if(now - cp_now < ldelays[level]) 367 | return; 368 | 369 | if(removeCycle) { 370 | // Remove filled lines 371 | removeFullLines(); 372 | // Advance level if piece count is reached 373 | if(pcnt >= PIECES_PER_LEVEL) { 374 | pcnt = 0; 375 | if(level < NUM_LEVELS - 1) { 376 | char lvl[2]; 377 | clearBoard(); 378 | level++; 379 | lvl[0] = level + 1 + '0'; 380 | lvl[1] = 0; 381 | showWordSequence(lvl, 3); 382 | } else { 383 | // User won all levels; restart for now 384 | clearBoard(); 385 | level = 0; 386 | } 387 | } 388 | removeCycle = false; 389 | // Create new piece 390 | newPiece(); 391 | } else if(canMoveDown()) { 392 | moveDown(); 393 | cp_now = now; 394 | } else { 395 | // Put piece into board 396 | for(int y = 0; y < cps; y++) { 397 | for(int x = 0; x < cps; x++) { 398 | if(cpd[y][x]) { 399 | setBoardAt(cpx + x, cpy + y); 400 | } 401 | } 402 | } 403 | removeCycle = true; 404 | cp_now = now; 405 | } 406 | 407 | updateDisplay(); 408 | } 409 | 410 | void si_end() 411 | { 412 | if(!siActive) 413 | return; 414 | 415 | siActive = false; 416 | } 417 | 418 | void si_newGame() 419 | { 420 | if(!siActive || siStartup) 421 | return; 422 | 423 | resetGame(); 424 | 425 | newPiece(); 426 | updateDisplay(); 427 | } 428 | 429 | void si_pause() 430 | { 431 | if(!siActive || gameOver || siStartup) 432 | return; 433 | 434 | pauseGame = !pauseGame; 435 | pauseShown = false; 436 | } 437 | 438 | void si_moveRight() // move right 439 | { 440 | if(!siActive || gameOver || siStartup || !havePiece || pauseGame) 441 | return; 442 | 443 | if(canMoveRight()) { 444 | moveRight(); 445 | updateDisplay(); 446 | } 447 | } 448 | 449 | void si_moveLeft() // move left 450 | { 451 | if(!siActive || gameOver || siStartup || !havePiece || pauseGame) 452 | return; 453 | 454 | if(canMoveLeft()) { 455 | moveLeft(); 456 | updateDisplay(); 457 | } 458 | } 459 | 460 | void si_moveDown() // move down 461 | { 462 | if(!siActive || gameOver || siStartup || !havePiece || pauseGame) 463 | return; 464 | 465 | if(canMoveDown()) { 466 | moveDown(); 467 | updateDisplay(); 468 | } 469 | } 470 | 471 | 472 | void si_fallDown() // fall down 473 | { 474 | 475 | if(!siActive || gameOver || siStartup || !havePiece || pauseGame) 476 | return; 477 | 478 | while(canMoveDown()) { 479 | moveDown(); 480 | } 481 | updateDisplay(); 482 | } 483 | 484 | void si_rotate() // rotate (left) 485 | { 486 | if(!siActive || gameOver || siStartup || !havePiece || pauseGame) 487 | return; 488 | 489 | if(canRotate()) { 490 | rotate(); 491 | updateDisplay(); 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /src/siddisplay.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * SIDDisplay Classes: Handles the SID LEDs 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | #include "sid_global.h" 34 | 35 | #include 36 | #include 37 | 38 | #include "siddisplay.h" 39 | 40 | #include "sid_font.h" 41 | 42 | static const uint16_t translator[10][20][2] = 43 | { 44 | { 45 | { 8+2, 1<<3 }, // bar 0, top most LED { index in buffer [0-7 chip1, 8-15 chip2], bitmask } 46 | { 8+2, 1<<2 }, 47 | { 8+2, 1<<1 }, 48 | { 8+2, 1<<0 }, 49 | { 0, 1<<15 }, 50 | { 0, 1<<14 }, 51 | { 0, 1<<13 }, 52 | { 0, 1<<12 }, 53 | { 0, 1<<11 }, 54 | { 0, 1<<10 }, 55 | { 0, 1<<9 }, 56 | { 0, 1<<8 }, 57 | { 0, 1<<7 }, 58 | { 0, 1<<6 }, 59 | { 0, 1<<5 }, 60 | { 0, 1<<4 }, 61 | { 0, 1<<3 }, 62 | { 0, 1<<2 }, 63 | { 0, 1<<1 }, 64 | { 0, 1<<0 } // bar 0, bottom LED 65 | }, 66 | { 67 | { 8+3, 1<<3 }, // bar 1, top most LED 68 | { 8+3, 1<<2 }, 69 | { 8+3, 1<<1 }, 70 | { 8+3, 1<<0 }, 71 | { 1, 1<<15 }, 72 | { 1, 1<<14 }, 73 | { 1, 1<<13 }, 74 | { 1, 1<<12 }, 75 | { 1, 1<<11 }, 76 | { 1, 1<<10 }, 77 | { 1, 1<<9 }, 78 | { 1, 1<<8 }, 79 | { 1, 1<<7 }, 80 | { 1, 1<<6 }, 81 | { 1, 1<<5 }, 82 | { 1, 1<<4 }, 83 | { 1, 1<<3 }, 84 | { 1, 1<<2 }, 85 | { 1, 1<<1 }, 86 | { 1, 1<<0 } 87 | }, 88 | { 89 | { 8+4, 1<<3 }, // bar 2, top most LED 90 | { 8+4, 1<<2 }, 91 | { 8+4, 1<<1 }, 92 | { 8+4, 1<<0 }, 93 | { 2, 1<<15 }, 94 | { 2, 1<<14 }, 95 | { 2, 1<<13 }, 96 | { 2, 1<<12 }, 97 | { 2, 1<<11 }, 98 | { 2, 1<<10 }, 99 | { 2, 1<<9 }, 100 | { 2, 1<<8 }, 101 | { 2, 1<<7 }, 102 | { 2, 1<<6 }, 103 | { 2, 1<<5 }, 104 | { 2, 1<<4 }, 105 | { 2, 1<<3 }, 106 | { 2, 1<<2 }, 107 | { 2, 1<<1 }, 108 | { 2, 1<<0 } 109 | }, 110 | { 111 | { 8+5, 1<<3 }, // bar 3, top most LED 112 | { 8+5, 1<<2 }, 113 | { 8+5, 1<<1 }, 114 | { 8+5, 1<<0 }, 115 | { 3, 1<<15 }, 116 | { 3, 1<<14 }, 117 | { 3, 1<<13 }, 118 | { 3, 1<<12 }, 119 | { 3, 1<<11 }, 120 | { 3, 1<<10 }, 121 | { 3, 1<<9 }, 122 | { 3, 1<<8 }, 123 | { 3, 1<<7 }, 124 | { 3, 1<<6 }, 125 | { 3, 1<<5 }, 126 | { 3, 1<<4 }, 127 | { 3, 1<<3 }, 128 | { 3, 1<<2 }, 129 | { 3, 1<<1 }, 130 | { 3, 1<<0 } 131 | }, 132 | { 133 | { 8+6, 1<<3 }, // bar 4, top most LED 134 | { 8+6, 1<<2 }, 135 | { 8+6, 1<<1 }, 136 | { 8+6, 1<<0 }, 137 | { 4, 1<<15 }, 138 | { 4, 1<<14 }, 139 | { 4, 1<<13 }, 140 | { 4, 1<<12 }, 141 | { 4, 1<<11 }, 142 | { 4, 1<<10 }, 143 | { 4, 1<<9 }, 144 | { 4, 1<<8 }, 145 | { 4, 1<<7 }, 146 | { 4, 1<<6 }, 147 | { 4, 1<<5 }, 148 | { 4, 1<<4 }, 149 | { 4, 1<<3 }, 150 | { 4, 1<<2 }, 151 | { 4, 1<<1 }, 152 | { 4, 1<<0 } 153 | }, 154 | { 155 | { 8+7, 1<<3 }, // bar 5, top most LED 156 | { 8+7, 1<<2 }, 157 | { 8+7, 1<<1 }, 158 | { 8+7, 1<<0 }, 159 | { 5, 1<<15 }, 160 | { 5, 1<<14 }, 161 | { 5, 1<<13 }, 162 | { 5, 1<<12 }, 163 | { 5, 1<<11 }, 164 | { 5, 1<<10 }, 165 | { 5, 1<<9 }, 166 | { 5, 1<<8 }, 167 | { 5, 1<<7 }, 168 | { 5, 1<<6 }, 169 | { 5, 1<<5 }, 170 | { 5, 1<<4 }, 171 | { 5, 1<<3 }, 172 | { 5, 1<<2 }, 173 | { 5, 1<<1 }, 174 | { 5, 1<<0 } 175 | }, 176 | { 177 | { 8+2, 1<<7 }, // bar 6, top most LED 178 | { 8+2, 1<<6 }, 179 | { 8+2, 1<<5 }, 180 | { 8+2, 1<<4 }, 181 | { 6, 1<<15 }, 182 | { 6, 1<<14 }, 183 | { 6, 1<<13 }, 184 | { 6, 1<<12 }, 185 | { 6, 1<<11 }, 186 | { 6, 1<<10 }, 187 | { 6, 1<<9 }, 188 | { 6, 1<<8 }, 189 | { 6, 1<<7 }, 190 | { 6, 1<<6 }, 191 | { 6, 1<<5 }, 192 | { 6, 1<<4 }, 193 | { 6, 1<<3 }, 194 | { 6, 1<<2 }, 195 | { 6, 1<<1 }, 196 | { 6, 1<<0 } 197 | }, 198 | { 199 | { 8+3, 1<<7 }, // bar 7, top most LED 200 | { 8+3, 1<<6 }, 201 | { 8+3, 1<<5 }, 202 | { 8+3, 1<<4 }, 203 | { 7, 1<<15 }, 204 | { 7, 1<<14 }, 205 | { 7, 1<<13 }, 206 | { 7, 1<<12 }, 207 | { 7, 1<<11 }, 208 | { 7, 1<<10 }, 209 | { 7, 1<<9 }, 210 | { 7, 1<<8 }, 211 | { 7, 1<<7 }, 212 | { 7, 1<<6 }, 213 | { 7, 1<<5 }, 214 | { 7, 1<<4 }, 215 | { 7, 1<<3 }, 216 | { 7, 1<<2 }, 217 | { 7, 1<<1 }, 218 | { 7, 1<<0 } 219 | }, 220 | { 221 | { 8+4, 1<<7 }, // bar 8, top most LED 222 | { 8+4, 1<<6 }, 223 | { 8+4, 1<<5 }, 224 | { 8+4, 1<<4 }, 225 | { 8+0, 1<<15 }, 226 | { 8+0, 1<<14 }, 227 | { 8+0, 1<<13 }, 228 | { 8+0, 1<<12 }, 229 | { 8+0, 1<<11 }, 230 | { 8+0, 1<<10 }, 231 | { 8+0, 1<<9 }, 232 | { 8+0, 1<<8 }, 233 | { 8+0, 1<<7 }, 234 | { 8+0, 1<<6 }, 235 | { 8+0, 1<<5 }, 236 | { 8+0, 1<<4 }, 237 | { 8+0, 1<<3 }, 238 | { 8+0, 1<<2 }, 239 | { 8+0, 1<<1 }, 240 | { 8+0, 1<<0 } 241 | }, 242 | { 243 | { 8+5, 1<<7 }, // bar 9, top most LED 244 | { 8+5, 1<<6 }, 245 | { 8+5, 1<<5 }, 246 | { 8+5, 1<<4 }, 247 | { 8+1, 1<<15 }, 248 | { 8+1, 1<<14 }, 249 | { 8+1, 1<<13 }, 250 | { 8+1, 1<<12 }, 251 | { 8+1, 1<<11 }, 252 | { 8+1, 1<<10 }, 253 | { 8+1, 1<<9 }, 254 | { 8+1, 1<<8 }, 255 | { 8+1, 1<<7 }, 256 | { 8+1, 1<<6 }, 257 | { 8+1, 1<<5 }, 258 | { 8+1, 1<<4 }, 259 | { 8+1, 1<<3 }, 260 | { 8+1, 1<<2 }, 261 | { 8+1, 1<<1 }, 262 | { 8+1, 1<<0 } 263 | } 264 | }; 265 | 266 | // Store i2c address and display ID 267 | sidDisplay::sidDisplay(uint8_t address1, uint8_t address2) 268 | { 269 | _address[0] = address1; 270 | _address[1] = address2; 271 | } 272 | 273 | // Start the display 274 | void sidDisplay::begin() 275 | { 276 | directCmd(0x20 | 1); // turn on oscillator 277 | 278 | clearBuf(); // clear buffer 279 | setBrightness(15); // setup initial brightness 280 | clearDisplayDirect(); // clear display RAM 281 | on(); // turn it on 282 | } 283 | 284 | // Turn on the display 285 | void sidDisplay::on() 286 | { 287 | directCmd(0x80 | 1); 288 | } 289 | 290 | // Turn off the display 291 | void sidDisplay::off() 292 | { 293 | directCmd(0x80); 294 | } 295 | 296 | void sidDisplay::lampTest() 297 | { 298 | for(int j = 0; j < 2; j++) { 299 | Wire.beginTransmission(_address[j]); 300 | Wire.write(0x00); // start address 301 | for(int i = 0; i < SD_BUF_SIZE / 2; i++) { 302 | Wire.write(0xff); 303 | Wire.write(0xff); 304 | } 305 | Wire.endTransmission(); 306 | } 307 | } 308 | 309 | 310 | // Clear the buffer 311 | void sidDisplay::clearBuf() 312 | { 313 | for(int i = 0; i < SD_BUF_SIZE; i++) { 314 | _displayBuffer[i] = 0; 315 | } 316 | } 317 | 318 | // Set display brightness 319 | // Valid brightness levels are 0 to 15. 320 | // 255 sets it to previous level 321 | uint8_t sidDisplay::setBrightness(uint8_t level, bool setInitial) 322 | { 323 | if(level == 255) 324 | level = _brightness; // restore to old val 325 | 326 | _brightness = setBrightnessDirect(level); 327 | 328 | if(setInitial) _origBrightness = _brightness; 329 | 330 | return _brightness; 331 | } 332 | 333 | void sidDisplay::resetBrightness() 334 | { 335 | _brightness = setBrightnessDirect(_origBrightness); 336 | } 337 | 338 | uint8_t sidDisplay::setBrightnessDirect(uint8_t level) 339 | { 340 | if(level > 15) 341 | level = 15; 342 | 343 | directCmd(0xe0 | level); 344 | 345 | return level; 346 | } 347 | 348 | uint8_t sidDisplay::getBrightness() 349 | { 350 | return _brightness; 351 | } 352 | 353 | // Draw bar into buffer, do NOT call show 354 | void sidDisplay::drawBarWithHeight(uint8_t bar, uint8_t height) 355 | { 356 | // Clear bar 357 | // Draw bar with given height 358 | 359 | if(height > 127) height = 0; 360 | if(height > 20) height = 20; 361 | 362 | if(height < 20) { 363 | for(int i = 0; i < 20 - height; i++) { 364 | _displayBuffer[translator[bar][i][0]] &= ~(translator[bar][i][1]); 365 | } 366 | } 367 | if(height > 0) { 368 | for(int i = 20 - height; i < 20; i++) { 369 | _displayBuffer[translator[bar][i][0]] |= translator[bar][i][1]; 370 | } 371 | } 372 | } 373 | 374 | // Draw bar into buffer, do NOT call show 375 | void sidDisplay::drawBar(uint8_t bar, uint8_t bottom, uint8_t top) 376 | { 377 | // Clear bar 378 | // Draw bar from top to bottom (0-19, 0=bottom) 379 | 380 | if(top > 19) top = 19; 381 | if(bottom > 19) bottom = 19; 382 | if(bottom > top) bottom = top; 383 | 384 | if(top < 19) { 385 | for(int i = 0; i <= 19-top; i++) { 386 | _displayBuffer[translator[bar][i][0]] &= ~(translator[bar][i][1]); 387 | } 388 | } 389 | if(bottom > 0) { 390 | for(int i = 19; i <= 19-bottom; i--) { 391 | _displayBuffer[translator[bar][i][0]] &= ~(translator[bar][i][1]); 392 | } 393 | } 394 | for(int i = 19-top; i <= 19-bottom; i++) { 395 | _displayBuffer[translator[bar][i][0]] |= translator[bar][i][1]; 396 | } 397 | } 398 | 399 | void sidDisplay::clearBar(uint8_t bar) 400 | { 401 | for(int i = 0; i <= 19; i++) { 402 | _displayBuffer[translator[bar][i][0]] &= ~(translator[bar][i][1]); 403 | } 404 | } 405 | 406 | // Draw dot into buffer, do NOT call show 407 | void sidDisplay::drawDot(uint8_t bar, uint8_t dot_y) 408 | { 409 | // Do not clear bar 410 | // Draw dot at dot_y (0 = bottom) 411 | if(dot_y > 19) dot_y = 19; 412 | 413 | _displayBuffer[translator[bar][19-dot_y][0]] |= translator[bar][19-dot_y][1]; 414 | } 415 | 416 | void sidDisplay::drawFieldAndShow(uint8_t *fieldData) 417 | { 418 | // Draw entire field. Data is 0 or 1, organized in lines 419 | for(int i = 0, k = 0; i < 20; i++, k += 10) { 420 | for(int j = 0; j < 10; j++) { 421 | if(fieldData[k+j]) { 422 | _displayBuffer[translator[j][i][0]] |= translator[j][i][1]; 423 | } else { 424 | _displayBuffer[translator[j][i][0]] &= ~(translator[j][i][1]); 425 | } 426 | } 427 | } 428 | show(); 429 | } 430 | 431 | void sidDisplay::drawLetterAndShow(char alpha, int x, int y) 432 | { 433 | uint8_t field[20*10] = { 0 }; 434 | int w = 10, h = 10, fx = 0, fy = 0, a = 0x200, s; 435 | 436 | if(x < -9 || x > 9 || y < -9 || y > 19) { 437 | clearDisplayDirect(); 438 | return; 439 | } 440 | 441 | if(alpha >= '0' && alpha <= '9') { 442 | alpha -= '0'; 443 | } else if(alpha >= 'A' && alpha <= 'Z') { 444 | alpha -= ('A' - 10); 445 | } else if(alpha >= 'a' && alpha <= 'z') { 446 | alpha -= ('a' - 10); 447 | } else if(alpha == '.') { 448 | alpha = 36; 449 | } else if(alpha == '&') { 450 | alpha = 37; 451 | } else if(alpha == '*') { 452 | alpha = 38; 453 | } else if(alpha == '#') { 454 | alpha = 39; 455 | } else if(alpha == '^') { 456 | alpha = 40; 457 | } else if(alpha == '$') { 458 | alpha = 41; 459 | } else if(alpha == '<') { 460 | alpha = 42; 461 | } else if(alpha == '>') { 462 | alpha = 43; 463 | } else if(alpha == '~') { 464 | alpha = 44; 465 | } else { 466 | clearDisplayDirect(); 467 | return; 468 | } 469 | 470 | if(x < 0) { 471 | fx = -x; 472 | a >>= fx; 473 | w = 10 - fx; 474 | x = 0; 475 | } else if(x > 0) { 476 | w = 10 - x; 477 | } 478 | if(y < 0) { 479 | fy = -y; 480 | h = 10 - fy; 481 | y = 0; 482 | } else if(y > (20-10)) { 483 | h = 20 - y; 484 | } 485 | 486 | for(int yy = fy; yy < h; yy++, y++) { 487 | uint16_t font = alphaChars[alpha][yy]; 488 | int xxx = x; 489 | for(int xx = fx, s = a; xx < w; xx++, s >>= 1, xxx++) { 490 | if(font & s) { 491 | field[(y*10) + xxx] = 1; 492 | } 493 | } 494 | } 495 | drawFieldAndShow(field); 496 | } 497 | 498 | void sidDisplay::drawLetterMask(char alpha, int x, int y) 499 | { 500 | uint8_t field[20*10] = { 0 }; 501 | int w = 8, h = 8, fx = 0, fy = 0, a = 0x80, s; 502 | 503 | if(x < -7 || x > 9 || y < -7 || y > 19) { 504 | return; 505 | } 506 | 507 | if(alpha >= '0' && alpha <= '9') { 508 | alpha -= '0'; 509 | } else if(alpha >= 'A' && alpha <= 'Z') { 510 | alpha -= ('A' - 10); 511 | } else if(alpha >= 'a' && alpha <= 'z') { 512 | alpha -= ('a' - 10); 513 | } else if(alpha == '.') { 514 | alpha = 36; 515 | } else if(alpha == '#') { 516 | alpha = 37; 517 | } else if(alpha >= '$' && alpha <= '\'') { 518 | alpha -= '$'; 519 | alpha += 38; 520 | } else { 521 | return; 522 | } 523 | 524 | if(x < 0) { 525 | fx = -x; 526 | a >>= fx; 527 | w = 8 - fx; 528 | x = 0; 529 | } else if(x > 2) { 530 | w = 8 - x; 531 | } 532 | if(y < 0) { 533 | fy = -y; 534 | h = 8 - fy; 535 | y = 0; 536 | } else if(y > (20-8)) { 537 | h = 20 - y; 538 | } 539 | 540 | for(int yy = fy; yy < h; yy++, y++) { 541 | uint8_t font = alphaChars8[alpha][yy]; 542 | int xxx = x; 543 | for(int xx = fx, s = a; xx < w; xx++, s >>= 1, xxx++) { 544 | if(font & s) { 545 | _displayBuffer[translator[xxx][y][0]] &= ~(translator[xxx][y][1]); 546 | } 547 | } 548 | } 549 | } 550 | 551 | // Show the buffer 552 | void sidDisplay::show() 553 | { 554 | uint16_t *tp = &_displayBuffer[0]; 555 | 556 | for(int j = 0; j < 2; j++) { 557 | Wire.beginTransmission(_address[j]); 558 | Wire.write(0x00); 559 | for(int i = 0; i < SD_BUF_SIZE / 2; i++) { 560 | uint16_t t = *tp++; 561 | Wire.write(t & 0xff); 562 | Wire.write(t >> 8); 563 | } 564 | Wire.endTransmission(); 565 | } 566 | } 567 | 568 | void sidDisplay::clearDisplayDirect() 569 | { 570 | for(int j = 0; j < 2; j++) { 571 | Wire.beginTransmission(_address[j]); 572 | Wire.write(0x00); 573 | for(int i = 0; i < SD_BUF_SIZE / 2; i++) { 574 | Wire.write(0x00); 575 | Wire.write(0x00); 576 | } 577 | Wire.endTransmission(); 578 | } 579 | } 580 | 581 | void sidDisplay::directCmd(uint8_t val) 582 | { 583 | for(int j = 0; j < 2; j++) { 584 | Wire.beginTransmission(_address[j]); 585 | Wire.write(val); 586 | Wire.endTransmission(); 587 | } 588 | } 589 | -------------------------------------------------------------------------------- /src/src/arduinoFFT/arduinoFFT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | FFT library 4 | Copyright (C) 2010 Didier Longueville 5 | Copyright (C) 2014 Enrique Condes 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program 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 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | #include "arduinoFFT.h" 23 | 24 | arduinoFFT::arduinoFFT(void) { // Constructor 25 | //#warning("This method is deprecated and may be removed on future revisions.") 26 | } 27 | 28 | arduinoFFT::arduinoFFT(FTYPE *vReal, FTYPE *vImag, uint16_t samples, 29 | FTYPE samplingFrequency) { // Constructor 30 | this->_vReal = vReal; 31 | this->_vImag = vImag; 32 | this->_samples = samples; 33 | this->_samplingFrequency = samplingFrequency; 34 | this->_power = Exponent(samples); 35 | } 36 | 37 | arduinoFFT::~arduinoFFT(void) { 38 | // Destructor 39 | } 40 | 41 | uint8_t arduinoFFT::Revision(void) { return (FFT_LIB_REV); } 42 | 43 | void arduinoFFT::Compute(FTYPE *vReal, FTYPE *vImag, uint16_t samples, 44 | FFTDirection dir) { 45 | //#warning("This method is deprecated and may be removed on future revisions.") 46 | Compute(vReal, vImag, samples, Exponent(samples), dir); 47 | } 48 | 49 | void arduinoFFT::Compute(FFTDirection dir) { 50 | // Computes in-place complex-to-complex FFT / 51 | // Reverse bits / 52 | uint16_t j = 0; 53 | for (uint16_t i = 0; i < (this->_samples - 1); i++) { 54 | if (i < j) { 55 | Swap(&this->_vReal[i], &this->_vReal[j]); 56 | if (dir == FFT_REVERSE) 57 | Swap(&this->_vImag[i], &this->_vImag[j]); 58 | } 59 | uint16_t k = (this->_samples >> 1); 60 | while (k <= j) { 61 | j -= k; 62 | k >>= 1; 63 | } 64 | j += k; 65 | } 66 | // Compute the FFT 67 | #ifdef __AVR__ 68 | uint8_t index = 0; 69 | #endif 70 | FTYPE c1 = -1.0; 71 | FTYPE c2 = 0.0; 72 | uint16_t l2 = 1; 73 | for (uint8_t l = 0; (l < this->_power); l++) { 74 | uint16_t l1 = l2; 75 | l2 <<= 1; 76 | FTYPE u1 = 1.0; 77 | FTYPE u2 = 0.0; 78 | for (j = 0; j < l1; j++) { 79 | for (uint16_t i = j; i < this->_samples; i += l2) { 80 | uint16_t i1 = i + l1; 81 | FTYPE t1 = u1 * this->_vReal[i1] - u2 * this->_vImag[i1]; 82 | FTYPE t2 = u1 * this->_vImag[i1] + u2 * this->_vReal[i1]; 83 | this->_vReal[i1] = this->_vReal[i] - t1; 84 | this->_vImag[i1] = this->_vImag[i] - t2; 85 | this->_vReal[i] += t1; 86 | this->_vImag[i] += t2; 87 | } 88 | FTYPE z = ((u1 * c1) - (u2 * c2)); 89 | u2 = ((u1 * c2) + (u2 * c1)); 90 | u1 = z; 91 | } 92 | #ifdef __AVR__ 93 | c2 = pgm_read_float_near(&(_c2[index])); 94 | c1 = pgm_read_float_near(&(_c1[index])); 95 | index++; 96 | #else 97 | c2 = FFT_SQRT((1.0 - c1) / 2.0); 98 | c1 = FFT_SQRT((1.0 + c1) / 2.0); 99 | #endif 100 | if (dir == FFT_FORWARD) { 101 | c2 = -c2; 102 | } 103 | } 104 | // Scaling for reverse transform / 105 | if (dir != FFT_FORWARD) { 106 | for (uint16_t i = 0; i < this->_samples; i++) { 107 | this->_vReal[i] /= this->_samples; 108 | this->_vImag[i] /= this->_samples; 109 | } 110 | } 111 | } 112 | 113 | void arduinoFFT::Compute(FTYPE *vReal, FTYPE *vImag, uint16_t samples, 114 | uint8_t power, FFTDirection dir) { 115 | // Computes in-place complex-to-complex FFT 116 | // Reverse bits 117 | //#warning("This method is deprecated and may be removed on future revisions.") 118 | uint16_t j = 0; 119 | for (uint16_t i = 0; i < (samples - 1); i++) { 120 | if (i < j) { 121 | Swap(&vReal[i], &vReal[j]); 122 | if (dir == FFT_REVERSE) 123 | Swap(&vImag[i], &vImag[j]); 124 | } 125 | uint16_t k = (samples >> 1); 126 | while (k <= j) { 127 | j -= k; 128 | k >>= 1; 129 | } 130 | j += k; 131 | } 132 | // Compute the FFT 133 | #ifdef __AVR__ 134 | uint8_t index = 0; 135 | #endif 136 | FTYPE c1 = -1.0; 137 | FTYPE c2 = 0.0; 138 | uint16_t l2 = 1; 139 | for (uint8_t l = 0; (l < power); l++) { 140 | uint16_t l1 = l2; 141 | l2 <<= 1; 142 | FTYPE u1 = 1.0; 143 | FTYPE u2 = 0.0; 144 | for (j = 0; j < l1; j++) { 145 | for (uint16_t i = j; i < samples; i += l2) { 146 | uint16_t i1 = i + l1; 147 | FTYPE t1 = u1 * vReal[i1] - u2 * vImag[i1]; 148 | FTYPE t2 = u1 * vImag[i1] + u2 * vReal[i1]; 149 | vReal[i1] = vReal[i] - t1; 150 | vImag[i1] = vImag[i] - t2; 151 | vReal[i] += t1; 152 | vImag[i] += t2; 153 | } 154 | FTYPE z = ((u1 * c1) - (u2 * c2)); 155 | u2 = ((u1 * c2) + (u2 * c1)); 156 | u1 = z; 157 | } 158 | #ifdef __AVR__ 159 | c2 = pgm_read_float_near(&(_c2[index])); 160 | c1 = pgm_read_float_near(&(_c1[index])); 161 | index++; 162 | #else 163 | c2 = FFT_SQRT((1.0 - c1) / 2.0); 164 | c1 = FFT_SQRT((1.0 + c1) / 2.0); 165 | #endif 166 | if (dir == FFT_FORWARD) { 167 | c2 = -c2; 168 | } 169 | } 170 | // Scaling for reverse transform 171 | if (dir != FFT_FORWARD) { 172 | for (uint16_t i = 0; i < samples; i++) { 173 | vReal[i] /= samples; 174 | vImag[i] /= samples; 175 | } 176 | } 177 | } 178 | 179 | void arduinoFFT::ComplexToMagnitude() { 180 | // vM is half the size of vReal and vImag 181 | for (uint16_t i = 0; i < this->_samples; i++) { 182 | this->_vReal[i] = FFT_SQRT(sq(this->_vReal[i]) + sq(this->_vImag[i])); 183 | } 184 | } 185 | 186 | void arduinoFFT::ComplexToMagnitude(FTYPE *vReal, FTYPE *vImag, 187 | uint16_t samples) { 188 | // vM is half the size of vReal and vImag 189 | //#warning("This method is deprecated and may be removed on future revisions.") 190 | for (uint16_t i = 0; i < samples; i++) { 191 | vReal[i] = FFT_SQRT(sq(vReal[i]) + sq(vImag[i])); 192 | } 193 | } 194 | 195 | void arduinoFFT::DCRemoval() { 196 | // calculate the mean of vData 197 | FTYPE mean = 0; 198 | for (uint16_t i = 0; i < this->_samples; i++) { 199 | mean += this->_vReal[i]; 200 | } 201 | mean /= this->_samples; 202 | // Subtract the mean from vData 203 | for (uint16_t i = 0; i < this->_samples; i++) { 204 | this->_vReal[i] -= mean; 205 | } 206 | } 207 | 208 | void arduinoFFT::DCRemoval(FTYPE *vData, uint16_t samples) { 209 | // calculate the mean of vData 210 | //#warning("This method is deprecated and may be removed on future revisions.") 211 | FTYPE mean = 0; 212 | for (uint16_t i = 0; i < samples; i++) { 213 | mean += vData[i]; 214 | } 215 | mean /= samples; 216 | // Subtract the mean from vData 217 | for (uint16_t i = 0; i < samples; i++) { 218 | vData[i] -= mean; 219 | } 220 | } 221 | 222 | void arduinoFFT::Windowing(FFTWindow windowType, FFTDirection dir) { 223 | // Weighing factors are computed once before multiple use of FFT 224 | // The weighing function is symmetric; half the weighs are recorded 225 | FTYPE samplesMinusOne = (FTYPE(this->_samples) - 1.0); 226 | for (uint16_t i = 0; i < (this->_samples >> 1); i++) { 227 | FTYPE indexMinusOne = FTYPE(i); 228 | FTYPE ratio = (indexMinusOne / samplesMinusOne); 229 | FTYPE weighingFactor = 1.0; 230 | // Compute and record weighting factor 231 | switch (windowType) { 232 | case FFT_WIN_TYP_RECTANGLE: // rectangle (box car) 233 | weighingFactor = 1.0; 234 | break; 235 | case FFT_WIN_TYP_HAMMING: // hamming 236 | weighingFactor = 0.54 - (0.46 * FFT_COS(twoPi * ratio)); 237 | break; 238 | case FFT_WIN_TYP_HANN: // hann 239 | weighingFactor = 0.54 * (1.0 - FFT_COS(twoPi * ratio)); 240 | break; 241 | case FFT_WIN_TYP_TRIANGLE: // triangle (Bartlett) 242 | #if defined(ESP8266) || defined(ESP32) 243 | weighingFactor = 244 | 1.0 - ((2.0 * fabs(indexMinusOne - (samplesMinusOne / 2.0))) / 245 | samplesMinusOne); 246 | #else 247 | weighingFactor = 248 | 1.0 - ((2.0 * abs(indexMinusOne - (samplesMinusOne / 2.0))) / 249 | samplesMinusOne); 250 | #endif 251 | break; 252 | case FFT_WIN_TYP_NUTTALL: // nuttall 253 | weighingFactor = 0.355768 - (0.487396 * (FFT_COS(twoPi * ratio))) + 254 | (0.144232 * (FFT_COS(fourPi * ratio))) - 255 | (0.012604 * (FFT_COS(sixPi * ratio))); 256 | break; 257 | case FFT_WIN_TYP_BLACKMAN: // blackman 258 | weighingFactor = 0.42323 - (0.49755 * (FFT_COS(twoPi * ratio))) + 259 | (0.07922 * (FFT_COS(fourPi * ratio))); 260 | break; 261 | case FFT_WIN_TYP_BLACKMAN_NUTTALL: // blackman nuttall 262 | weighingFactor = 0.3635819 - (0.4891775 * (FFT_COS(twoPi * ratio))) + 263 | (0.1365995 * (FFT_COS(fourPi * ratio))) - 264 | (0.0106411 * (FFT_COS(sixPi * ratio))); 265 | break; 266 | case FFT_WIN_TYP_BLACKMAN_HARRIS: // blackman harris 267 | weighingFactor = 0.35875 - (0.48829 * (FFT_COS(twoPi * ratio))) + 268 | (0.14128 * (FFT_COS(fourPi * ratio))) - 269 | (0.01168 * (FFT_COS(sixPi * ratio))); 270 | break; 271 | case FFT_WIN_TYP_FLT_TOP: // flat top 272 | weighingFactor = 0.2810639 - (0.5208972 * FFT_COS(twoPi * ratio)) + 273 | (0.1980399 * FFT_COS(fourPi * ratio)); 274 | break; 275 | case FFT_WIN_TYP_WELCH: // welch 276 | weighingFactor = 1.0 - sq((indexMinusOne - samplesMinusOne / 2.0) / 277 | (samplesMinusOne / 2.0)); 278 | break; 279 | } 280 | if (dir == FFT_FORWARD) { 281 | this->_vReal[i] *= weighingFactor; 282 | this->_vReal[this->_samples - (i + 1)] *= weighingFactor; 283 | } else { 284 | this->_vReal[i] /= weighingFactor; 285 | this->_vReal[this->_samples - (i + 1)] /= weighingFactor; 286 | } 287 | } 288 | } 289 | 290 | void arduinoFFT::Windowing(FTYPE *vData, uint16_t samples, 291 | FFTWindow windowType, FFTDirection dir) { 292 | // Weighing factors are computed once before multiple use of FFT 293 | // The weighing function is symetric; half the weighs are recorded 294 | //#warning("This method is deprecated and may be removed on future revisions.") 295 | FTYPE samplesMinusOne = (FTYPE(samples) - 1.0); 296 | for (uint16_t i = 0; i < (samples >> 1); i++) { 297 | FTYPE indexMinusOne = FTYPE(i); 298 | FTYPE ratio = (indexMinusOne / samplesMinusOne); 299 | FTYPE weighingFactor = 1.0; 300 | // Compute and record weighting factor 301 | switch (windowType) { 302 | case FFT_WIN_TYP_RECTANGLE: // rectangle (box car) 303 | weighingFactor = 1.0; 304 | break; 305 | case FFT_WIN_TYP_HAMMING: // hamming 306 | weighingFactor = 0.54 - (0.46 * FFT_COS(twoPi * ratio)); 307 | break; 308 | case FFT_WIN_TYP_HANN: // hann 309 | weighingFactor = 0.54 * (1.0 - FFT_COS(twoPi * ratio)); 310 | break; 311 | case FFT_WIN_TYP_TRIANGLE: // triangle (Bartlett) 312 | #if defined(ESP8266) || defined(ESP32) 313 | weighingFactor = 314 | 1.0 - ((2.0 * fabs(indexMinusOne - (samplesMinusOne / 2.0))) / 315 | samplesMinusOne); 316 | #else 317 | weighingFactor = 318 | 1.0 - ((2.0 * abs(indexMinusOne - (samplesMinusOne / 2.0))) / 319 | samplesMinusOne); 320 | #endif 321 | break; 322 | case FFT_WIN_TYP_NUTTALL: // nuttall 323 | weighingFactor = 0.355768 - (0.487396 * (FFT_COS(twoPi * ratio))) + 324 | (0.144232 * (FFT_COS(fourPi * ratio))) - 325 | (0.012604 * (FFT_COS(sixPi * ratio))); 326 | break; 327 | case FFT_WIN_TYP_BLACKMAN: // blackman 328 | weighingFactor = 0.42323 - (0.49755 * (FFT_COS(twoPi * ratio))) + 329 | (0.07922 * (FFT_COS(fourPi * ratio))); 330 | break; 331 | case FFT_WIN_TYP_BLACKMAN_NUTTALL: // blackman nuttall 332 | weighingFactor = 0.3635819 - (0.4891775 * (FFT_COS(twoPi * ratio))) + 333 | (0.1365995 * (FFT_COS(fourPi * ratio))) - 334 | (0.0106411 * (FFT_COS(sixPi * ratio))); 335 | break; 336 | case FFT_WIN_TYP_BLACKMAN_HARRIS: // blackman harris 337 | weighingFactor = 0.35875 - (0.48829 * (FFT_COS(twoPi * ratio))) + 338 | (0.14128 * (FFT_COS(fourPi * ratio))) - 339 | (0.01168 * (FFT_COS(sixPi * ratio))); 340 | break; 341 | case FFT_WIN_TYP_FLT_TOP: // flat top 342 | weighingFactor = 0.2810639 - (0.5208972 * FFT_COS(twoPi * ratio)) + 343 | (0.1980399 * FFT_COS(fourPi * ratio)); 344 | break; 345 | case FFT_WIN_TYP_WELCH: // welch 346 | weighingFactor = 1.0 - sq((indexMinusOne - samplesMinusOne / 2.0) / 347 | (samplesMinusOne / 2.0)); 348 | break; 349 | } 350 | if (dir == FFT_FORWARD) { 351 | vData[i] *= weighingFactor; 352 | vData[samples - (i + 1)] *= weighingFactor; 353 | } else { 354 | vData[i] /= weighingFactor; 355 | vData[samples - (i + 1)] /= weighingFactor; 356 | } 357 | } 358 | } 359 | 360 | FTYPE arduinoFFT::MajorPeak() { 361 | FTYPE maxY = 0; 362 | uint16_t IndexOfMaxY = 0; 363 | // If sampling_frequency = 2 * max_frequency in signal, 364 | // value would be stored at position samples/2 365 | for (uint16_t i = 1; i < ((this->_samples >> 1) + 1); i++) { 366 | if ((this->_vReal[i - 1] < this->_vReal[i]) && 367 | (this->_vReal[i] > this->_vReal[i + 1])) { 368 | if (this->_vReal[i] > maxY) { 369 | maxY = this->_vReal[i]; 370 | IndexOfMaxY = i; 371 | } 372 | } 373 | } 374 | FTYPE delta = 375 | 0.5 * 376 | ((this->_vReal[IndexOfMaxY - 1] - this->_vReal[IndexOfMaxY + 1]) / 377 | (this->_vReal[IndexOfMaxY - 1] - (2.0 * this->_vReal[IndexOfMaxY]) + 378 | this->_vReal[IndexOfMaxY + 1])); 379 | FTYPE interpolatedX = 380 | ((IndexOfMaxY + delta) * this->_samplingFrequency) / (this->_samples - 1); 381 | if (IndexOfMaxY == 382 | (this->_samples >> 1)) // To improve calculation on edge values 383 | interpolatedX = 384 | ((IndexOfMaxY + delta) * this->_samplingFrequency) / (this->_samples); 385 | // returned value: interpolated frequency peak apex 386 | return (interpolatedX); 387 | } 388 | 389 | void arduinoFFT::MajorPeak(FTYPE *f, FTYPE *v) { 390 | FTYPE maxY = 0; 391 | uint16_t IndexOfMaxY = 0; 392 | // If sampling_frequency = 2 * max_frequency in signal, 393 | // value would be stored at position samples/2 394 | for (uint16_t i = 1; i < ((this->_samples >> 1) + 1); i++) { 395 | if ((this->_vReal[i - 1] < this->_vReal[i]) && 396 | (this->_vReal[i] > this->_vReal[i + 1])) { 397 | if (this->_vReal[i] > maxY) { 398 | maxY = this->_vReal[i]; 399 | IndexOfMaxY = i; 400 | } 401 | } 402 | } 403 | FTYPE delta = 404 | 0.5 * 405 | ((this->_vReal[IndexOfMaxY - 1] - this->_vReal[IndexOfMaxY + 1]) / 406 | (this->_vReal[IndexOfMaxY - 1] - (2.0 * this->_vReal[IndexOfMaxY]) + 407 | this->_vReal[IndexOfMaxY + 1])); 408 | FTYPE interpolatedX = 409 | ((IndexOfMaxY + delta) * this->_samplingFrequency) / (this->_samples - 1); 410 | if (IndexOfMaxY == 411 | (this->_samples >> 1)) // To improve calculation on edge values 412 | interpolatedX = 413 | ((IndexOfMaxY + delta) * this->_samplingFrequency) / (this->_samples); 414 | // returned value: interpolated frequency peak apex 415 | *f = interpolatedX; 416 | #if defined(ESP8266) || defined(ESP32) 417 | *v = fabs(this->_vReal[IndexOfMaxY - 1] - (2.0 * this->_vReal[IndexOfMaxY]) + 418 | this->_vReal[IndexOfMaxY + 1]); 419 | #else 420 | *v = abs(this->_vReal[IndexOfMaxY - 1] - (2.0 * this->_vReal[IndexOfMaxY]) + 421 | this->_vReal[IndexOfMaxY + 1]); 422 | #endif 423 | } 424 | 425 | FTYPE arduinoFFT::MajorPeak(FTYPE *vD, uint16_t samples, 426 | FTYPE samplingFrequency) { 427 | //#warning("This method is deprecated and may be removed on future revisions.") 428 | FTYPE maxY = 0; 429 | uint16_t IndexOfMaxY = 0; 430 | // If sampling_frequency = 2 * max_frequency in signal, 431 | // value would be stored at position samples/2 432 | for (uint16_t i = 1; i < ((samples >> 1) + 1); i++) { 433 | if ((vD[i - 1] < vD[i]) && (vD[i] > vD[i + 1])) { 434 | if (vD[i] > maxY) { 435 | maxY = vD[i]; 436 | IndexOfMaxY = i; 437 | } 438 | } 439 | } 440 | FTYPE delta = 441 | 0.5 * 442 | ((vD[IndexOfMaxY - 1] - vD[IndexOfMaxY + 1]) / 443 | (vD[IndexOfMaxY - 1] - (2.0 * vD[IndexOfMaxY]) + vD[IndexOfMaxY + 1])); 444 | FTYPE interpolatedX = 445 | ((IndexOfMaxY + delta) * samplingFrequency) / (samples - 1); 446 | if (IndexOfMaxY == (samples >> 1)) // To improve calculation on edge values 447 | interpolatedX = ((IndexOfMaxY + delta) * samplingFrequency) / (samples); 448 | // returned value: interpolated frequency peak apex 449 | return (interpolatedX); 450 | } 451 | 452 | void arduinoFFT::MajorPeak(FTYPE *vD, uint16_t samples, 453 | FTYPE samplingFrequency, FTYPE *f, FTYPE *v) { 454 | //#warning("This method is deprecated and may be removed on future revisions.") 455 | FTYPE maxY = 0; 456 | uint16_t IndexOfMaxY = 0; 457 | // If sampling_frequency = 2 * max_frequency in signal, 458 | // value would be stored at position samples/2 459 | for (uint16_t i = 1; i < ((samples >> 1) + 1); i++) { 460 | if ((vD[i - 1] < vD[i]) && (vD[i] > vD[i + 1])) { 461 | if (vD[i] > maxY) { 462 | maxY = vD[i]; 463 | IndexOfMaxY = i; 464 | } 465 | } 466 | } 467 | FTYPE delta = 468 | 0.5 * 469 | ((vD[IndexOfMaxY - 1] - vD[IndexOfMaxY + 1]) / 470 | (vD[IndexOfMaxY - 1] - (2.0 * vD[IndexOfMaxY]) + vD[IndexOfMaxY + 1])); 471 | FTYPE interpolatedX = 472 | ((IndexOfMaxY + delta) * samplingFrequency) / (samples - 1); 473 | // FTYPE popo = 474 | if (IndexOfMaxY == (samples >> 1)) // To improve calculation on edge values 475 | interpolatedX = ((IndexOfMaxY + delta) * samplingFrequency) / (samples); 476 | // returned value: interpolated frequency peak apex 477 | *f = interpolatedX; 478 | #if defined(ESP8266) || defined(ESP32) 479 | *v = 480 | fabs(vD[IndexOfMaxY - 1] - (2.0 * vD[IndexOfMaxY]) + vD[IndexOfMaxY + 1]); 481 | #else 482 | *v = abs(vD[IndexOfMaxY - 1] - (2.0 * vD[IndexOfMaxY]) + vD[IndexOfMaxY + 1]); 483 | #endif 484 | } 485 | 486 | FTYPE arduinoFFT::MajorPeakParabola() { 487 | FTYPE maxY = 0; 488 | uint16_t IndexOfMaxY = 0; 489 | // If sampling_frequency = 2 * max_frequency in signal, 490 | // value would be stored at position samples/2 491 | for (uint16_t i = 1; i < ((this->_samples >> 1) + 1); i++) { 492 | if ((this->_vReal[i - 1] < this->_vReal[i]) && 493 | (this->_vReal[i] > this->_vReal[i + 1])) { 494 | if (this->_vReal[i] > maxY) { 495 | maxY = this->_vReal[i]; 496 | IndexOfMaxY = i; 497 | } 498 | } 499 | } 500 | 501 | FTYPE freq = 0; 502 | if (IndexOfMaxY > 0) { 503 | // Assume the three points to be on a parabola 504 | FTYPE a, b, c; 505 | Parabola(IndexOfMaxY - 1, this->_vReal[IndexOfMaxY - 1], IndexOfMaxY, 506 | this->_vReal[IndexOfMaxY], IndexOfMaxY + 1, 507 | this->_vReal[IndexOfMaxY + 1], &a, &b, &c); 508 | 509 | // Peak is at the middle of the parabola 510 | FTYPE x = -b / (2 * a); 511 | 512 | // And magnitude is at the extrema of the parabola if you want It... 513 | // FTYPE y = a*x*x+b*x+c; 514 | 515 | // Convert to frequency 516 | freq = (x * this->_samplingFrequency) / (this->_samples); 517 | } 518 | 519 | return freq; 520 | } 521 | 522 | void arduinoFFT::Parabola(FTYPE x1, FTYPE y1, FTYPE x2, FTYPE y2, FTYPE x3, 523 | FTYPE y3, FTYPE *a, FTYPE *b, FTYPE *c) { 524 | FTYPE reversed_denom = 1 / ((x1 - x2) * (x1 - x3) * (x2 - x3)); 525 | 526 | *a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) * reversed_denom; 527 | *b = (x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1) + x1 * x1 * (y2 - y3)) * 528 | reversed_denom; 529 | *c = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + 530 | x1 * x2 * (x1 - x2) * y3) * 531 | reversed_denom; 532 | } 533 | 534 | uint8_t arduinoFFT::Exponent(uint16_t value) { 535 | //#warning("This method may not be accessible on future revisions.") 536 | // Calculates the base 2 logarithm of a value 537 | uint8_t result = 0; 538 | while (((value >> result) & 1) != 1) 539 | result++; 540 | return (result); 541 | } 542 | 543 | // Private functions 544 | 545 | void arduinoFFT::Swap(FTYPE *x, FTYPE *y) { 546 | FTYPE temp = *x; 547 | *x = *y; 548 | *y = temp; 549 | } 550 | 551 | -------------------------------------------------------------------------------- /src/sid_font.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * CircuitSetup.us Status Indicator Display 4 | * (C) 2023 Thomas Winischhofer (A10001986) 5 | * https://github.com/realA10001986/SID 6 | * https://sid.backtothefutu.re 7 | * 8 | * Fonts 9 | * 10 | * ------------------------------------------------------------------- 11 | * License: MIT 12 | * 13 | * Permission is hereby granted, free of charge, to any person 14 | * obtaining a copy of this software and associated documentation 15 | * files (the "Software"), to deal in the Software without restriction, 16 | * including without limitation the rights to use, copy, modify, 17 | * merge, publish, distribute, sublicense, and/or sell copies of the 18 | * Software, and to permit persons to whom the Software is furnished to 19 | * do so, subject to the following conditions: 20 | * 21 | * The above copyright notice and this permission notice shall be 22 | * included in all copies or substantial portions of the Software. 23 | * 24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | */ 32 | 33 | 34 | #ifndef _SID_FONT_H 35 | #define _SID_FONT_H 36 | 37 | static const uint16_t alphaChars[36+1+1+7][10] = 38 | { 39 | { 40 | 0b0011111100, 41 | 0b0111111110, 42 | 0b1110000111, 43 | 0b1100000011, 44 | 0b1100000011, 45 | 0b1100000011, 46 | 0b1100000011, 47 | 0b1110000111, 48 | 0b0111111110, 49 | 0b0011111100 50 | }, 51 | { 52 | 0b0000110000, 53 | 0b0001110000, 54 | 0b0011110000, 55 | 0b0000110000, 56 | 0b0000110000, 57 | 0b0000110000, 58 | 0b0000110000, 59 | 0b0000110000, 60 | 0b0011111100, 61 | 0b0011111100 62 | }, 63 | { 64 | 0b1111111100, 65 | 0b1111111110, 66 | 0b0000000111, 67 | 0b0000000111, 68 | 0b0011111110, 69 | 0b0111111100, 70 | 0b1110000000, 71 | 0b1100000000, 72 | 0b1111111111, 73 | 0b1111111111 74 | }, 75 | { 76 | 0b1111111100, 77 | 0b1111111110, 78 | 0b0000000111, 79 | 0b0000000111, 80 | 0b0001111110, 81 | 0b0001111110, 82 | 0b0000000111, 83 | 0b0000000111, 84 | 0b1111111110, 85 | 0b1111111100 86 | }, 87 | { 88 | 0b0000011111, 89 | 0b0000111111, 90 | 0b0001110011, 91 | 0b0011100011, 92 | 0b0111000011, 93 | 0b1111111111, 94 | 0b1111111111, 95 | 0b0000000011, 96 | 0b0000000011, 97 | 0b0000000011 98 | }, 99 | { 100 | 0b1111111110, 101 | 0b1111111110, 102 | 0b1100000000, 103 | 0b1100000000, 104 | 0b1111111100, 105 | 0b1111111110, 106 | 0b0000000111, 107 | 0b0000000111, 108 | 0b1111111110, 109 | 0b1111111100 110 | }, 111 | { 112 | 0b0011111110, 113 | 0b0111111110, 114 | 0b1110000000, 115 | 0b1100000000, 116 | 0b1111111100, 117 | 0b1111111110, 118 | 0b1100000111, 119 | 0b1110000111, 120 | 0b0111111110, 121 | 0b0011111100 122 | }, 123 | { 124 | 0b1111111111, 125 | 0b1111111111, 126 | 0b0000000011, 127 | 0b0000000111, 128 | 0b0000001110, 129 | 0b0000011100, 130 | 0b0000111000, 131 | 0b0000110000, 132 | 0b0000110000, 133 | 0b0000110000 134 | }, 135 | { 136 | 0b0011111100, 137 | 0b0111111110, 138 | 0b1100000011, 139 | 0b1100000011, 140 | 0b0111111110, 141 | 0b0111111110, 142 | 0b1100000011, 143 | 0b1100000011, 144 | 0b0111111110, 145 | 0b0011111100 146 | }, 147 | { 148 | 0b0011111100, 149 | 0b0111111110, 150 | 0b1100000011, 151 | 0b1100000011, 152 | 0b0111111111, 153 | 0b0111111111, 154 | 0b0000000011, 155 | 0b0000000011, 156 | 0b0111111110, 157 | 0b0011111100 158 | }, 159 | { 160 | 0b0011111100, 161 | 0b0111111110, 162 | 0b1110000111, 163 | 0b1100000011, 164 | 0b1100000011, 165 | 0b1111111111, 166 | 0b1111111111, 167 | 0b1100000011, 168 | 0b1100000011, 169 | 0b1100000011 170 | }, 171 | { 172 | 0b1111111100, 173 | 0b1111111110, 174 | 0b1100000111, 175 | 0b1100000111, 176 | 0b1111111100, 177 | 0b1111111100, 178 | 0b1100000111, 179 | 0b1100000111, 180 | 0b1111111110, 181 | 0b1111111100 182 | }, 183 | { 184 | 0b0011111100, 185 | 0b0111111110, 186 | 0b1110000111, 187 | 0b1100000000, 188 | 0b1100000000, 189 | 0b1100000000, 190 | 0b1100000000, 191 | 0b1110000111, 192 | 0b0111111110, 193 | 0b0011111100 194 | }, 195 | { 196 | 0b1111111100, 197 | 0b1111111110, 198 | 0b1100000111, 199 | 0b1100000011, 200 | 0b1100000011, 201 | 0b1100000011, 202 | 0b1100000011, 203 | 0b1100000111, 204 | 0b1111111110, 205 | 0b1111111100 206 | }, 207 | { 208 | 0b1111111111, 209 | 0b1111111111, 210 | 0b1100000000, 211 | 0b1100000000, 212 | 0b1111110000, 213 | 0b1111110000, 214 | 0b1100000000, 215 | 0b1100000000, 216 | 0b1111111111, 217 | 0b1111111111, 218 | }, 219 | { 220 | 0b1111111111, 221 | 0b1111111111, 222 | 0b1100000000, 223 | 0b1100000000, 224 | 0b1111110000, 225 | 0b1111110000, 226 | 0b1100000000, 227 | 0b1100000000, 228 | 0b1100000000, 229 | 0b1100000000 230 | }, 231 | { 232 | 0b0011111110, 233 | 0b0111111111, 234 | 0b1110000011, 235 | 0b1100000000, 236 | 0b1100111111, 237 | 0b1100111111, 238 | 0b1100000011, 239 | 0b1110000011, 240 | 0b0111111111, 241 | 0b0011111111 242 | }, 243 | { 244 | 0b1100000011, 245 | 0b1100000011, 246 | 0b1100000011, 247 | 0b1100000011, 248 | 0b1111111111, 249 | 0b1111111111, 250 | 0b1100000011, 251 | 0b1100000011, 252 | 0b1100000011, 253 | 0b1100000011 254 | }, 255 | { 256 | 0b0111111110, 257 | 0b0111111110, 258 | 0b0000110000, 259 | 0b0000110000, 260 | 0b0000110000, 261 | 0b0000110000, 262 | 0b0000110000, 263 | 0b0000110000, 264 | 0b0111111110, 265 | 0b0111111110 266 | }, 267 | { 268 | 0b0011111111, 269 | 0b0011111111, 270 | 0b0000000011, 271 | 0b0000000011, 272 | 0b0000000011, 273 | 0b0000000011, 274 | 0b1100000011, 275 | 0b1110000111, 276 | 0b0111111110, 277 | 0b0011111100 278 | }, 279 | { 280 | 0b1100000111, 281 | 0b1100001110, 282 | 0b1100011100, 283 | 0b1100111000, 284 | 0b1111110000, 285 | 0b1111110000, 286 | 0b1100111000, 287 | 0b1100011100, 288 | 0b1100001110, 289 | 0b1100000111 290 | }, 291 | { 292 | 0b1100000000, 293 | 0b1100000000, 294 | 0b1100000000, 295 | 0b1100000000, 296 | 0b1100000000, 297 | 0b1100000000, 298 | 0b1100000000, 299 | 0b1100000000, 300 | 0b1111111111, 301 | 0b1111111111 302 | }, 303 | { 304 | 0b1100000011, 305 | 0b1110000111, 306 | 0b1111001111, 307 | 0b1111111111, 308 | 0b1101111011, 309 | 0b1100110011, 310 | 0b1100000011, 311 | 0b1100000011, 312 | 0b1100000011, 313 | 0b1100000011 314 | }, 315 | { 316 | 0b1100000011, 317 | 0b1110000011, 318 | 0b1111000011, 319 | 0b1111100011, 320 | 0b1101110011, 321 | 0b1100111011, 322 | 0b1100011111, 323 | 0b1100001111, 324 | 0b1100000111, 325 | 0b1100000011 326 | }, 327 | { 328 | 0b0011111100, 329 | 0b0111111110, 330 | 0b1110000111, 331 | 0b1100000011, 332 | 0b1100000011, 333 | 0b1100000011, 334 | 0b1100000011, 335 | 0b1110000111, 336 | 0b0111111110, 337 | 0b0011111100 338 | }, 339 | { 340 | 0b1111111100, 341 | 0b1111111110, 342 | 0b1100000111, 343 | 0b1100000111, 344 | 0b1111111110, 345 | 0b1111111100, 346 | 0b1100000000, 347 | 0b1100000000, 348 | 0b1100000000, 349 | 0b1100000000 350 | }, 351 | { 352 | 0b0011111100, 353 | 0b0111111110, 354 | 0b1110000111, 355 | 0b1100000011, 356 | 0b1100000011, 357 | 0b1100000111, 358 | 0b1100001110, 359 | 0b1110011100, 360 | 0b0111111111, 361 | 0b0011111111 362 | }, 363 | { 364 | 0b1111111100, 365 | 0b1111111110, 366 | 0b1100000111, 367 | 0b1100000111, 368 | 0b1111111110, 369 | 0b1111111110, 370 | 0b1100001110, 371 | 0b1100000111, 372 | 0b1100000011, 373 | 0b1100000011 374 | }, 375 | { 376 | 0b0011111111, 377 | 0b0111111111, 378 | 0b1110000000, 379 | 0b1110000000, 380 | 0b0111111100, 381 | 0b0011111110, 382 | 0b0000000111, 383 | 0b0000000111, 384 | 0b1111111110, 385 | 0b1111111100 386 | }, 387 | { 388 | 0b1111111111, 389 | 0b1111111111, 390 | 0b0000110000, 391 | 0b0000110000, 392 | 0b0000110000, 393 | 0b0000110000, 394 | 0b0000110000, 395 | 0b0000110000, 396 | 0b0000110000, 397 | 0b0000110000 398 | }, 399 | { 400 | 0b1100000011, 401 | 0b1100000011, 402 | 0b1100000011, 403 | 0b1100000011, 404 | 0b1100000011, 405 | 0b1100000011, 406 | 0b1100000011, 407 | 0b1110000111, 408 | 0b0111001110, 409 | 0b0011111100 410 | }, 411 | { 412 | 0b1100000011, 413 | 0b1100000011, 414 | 0b1100000011, 415 | 0b1100000011, 416 | 0b1100000011, 417 | 0b1110000111, 418 | 0b0111001110, 419 | 0b0011111100, 420 | 0b0001111000, 421 | 0b0000110000 422 | }, 423 | { 424 | 0b1100000011, 425 | 0b1100000011, 426 | 0b1100000011, 427 | 0b1100000011, 428 | 0b1100110011, 429 | 0b1101111011, 430 | 0b1111111111, 431 | 0b1111001111, 432 | 0b1110000111, 433 | 0b1100000011 434 | }, 435 | { 436 | 0b1100000011, 437 | 0b1110000111, 438 | 0b0111001110, 439 | 0b0011111100, 440 | 0b0001111000, 441 | 0b0001111000, 442 | 0b0011111100, 443 | 0b0111001110, 444 | 0b1110000111, 445 | 0b1100000011 446 | }, 447 | { 448 | 0b1100000011, 449 | 0b1110000111, 450 | 0b0111001110, 451 | 0b0011111100, 452 | 0b0001111000, 453 | 0b0000110000, 454 | 0b0000110000, 455 | 0b0000110000, 456 | 0b0000110000, 457 | 0b0000110000 458 | }, 459 | { 460 | 0b1111111111, 461 | 0b1111111111, 462 | 0b0000001110, 463 | 0b0000011100, 464 | 0b0000111000, 465 | 0b0001110000, 466 | 0b0011100000, 467 | 0b0111000000, 468 | 0b1111111111, 469 | 0b1111111111 470 | }, 471 | { 472 | 0b0000000000, 473 | 0b0000000000, 474 | 0b0000000000, 475 | 0b0000000000, 476 | 0b0000000000, 477 | 0b0000000000, 478 | 0b0000000000, 479 | 0b0000000000, 480 | 0b0000110000, 481 | 0b0000110000 482 | }, 483 | { // 37 484 | 0b1111111111, 485 | 0b0110000110, 486 | 0b0011001100, 487 | 0b0001111000, 488 | 0b0000110000, 489 | 0b0001111000, 490 | 0b0011001100, 491 | 0b0110000110, 492 | 0b1111111111, 493 | 0b0000000000 494 | }, 495 | { 496 | 0b0000000000, 497 | 0b0110000110, 498 | 0b0011001100, 499 | 0b0001111000, 500 | 0b1111111111, 501 | 0b0001111000, 502 | 0b0011001100, 503 | 0b0110000110, 504 | 0b0000000000, 505 | 0b0000000000 506 | }, 507 | { 508 | 0b0011001100, 509 | 0b0011001100, 510 | 0b0011001100, 511 | 0b1111111111, 512 | 0b0011001100, 513 | 0b0011001100, 514 | 0b1111111111, 515 | 0b0011001100, 516 | 0b0011001100, 517 | 0b0011001100 518 | }, 519 | { 520 | 0b0000110000, 521 | 0b0001111000, 522 | 0b0011111100, 523 | 0b0111111110, 524 | 0b0000110000, 525 | 0b0000110000, 526 | 0b0000110000, 527 | 0b0000110000, 528 | 0b0000110000, 529 | 0b0000110000 530 | }, 531 | { 532 | 0b0000110000, 533 | 0b0000110000, 534 | 0b0000110000, 535 | 0b0000110000, 536 | 0b0000110000, 537 | 0b0000110000, 538 | 0b0111111110, 539 | 0b0011111100, 540 | 0b0001111000, 541 | 0b0000110000 542 | }, 543 | { 544 | 0b0000100000, 545 | 0b0001100000, 546 | 0b0011100000, 547 | 0b0111100000, 548 | 0b1111111111, 549 | 0b1111111111, 550 | 0b0111100000, 551 | 0b0011100000, 552 | 0b0001100000, 553 | 0b0000100000 554 | }, 555 | { 556 | 0b0000010000, 557 | 0b0000011000, 558 | 0b0000011100, 559 | 0b0000011110, 560 | 0b1111111111, 561 | 0b1111111111, 562 | 0b0000011110, 563 | 0b0000011100, 564 | 0b0000011000, 565 | 0b0000010000 566 | }, 567 | { 568 | 0b0000000000, 569 | 0b0000000000, 570 | 0b0110010001, 571 | 0b1001010010, 572 | 0b1001011100, 573 | 0b1001010010, 574 | 0b0110010001, 575 | 0b0000000000, 576 | 0b0000000000, 577 | 0b0000000000 578 | } 579 | }; 580 | 581 | static const uint8_t alphaChars8[36+1+1+4][8] = 582 | { 583 | { 584 | 0b01111110, 585 | 0b11111111, 586 | 0b11000011, 587 | 0b11000011, 588 | 0b11000011, 589 | 0b11000011, 590 | 0b11111111, 591 | 0b01111110 592 | }, 593 | { 594 | 0b00011000, 595 | 0b01111000, 596 | 0b01111000, 597 | 0b00011000, 598 | 0b00011000, 599 | 0b00011000, 600 | 0b00011000, 601 | 0b00011000 602 | }, 603 | { 604 | 0b11111110, 605 | 0b11111111, 606 | 0b00000011, 607 | 0b00111111, 608 | 0b01111110, 609 | 0b11000000, 610 | 0b11111111, 611 | 0b11111111 612 | }, 613 | { 614 | 0b11111110, 615 | 0b11111111, 616 | 0b00000011, 617 | 0b00111111, 618 | 0b00111111, 619 | 0b00000011, 620 | 0b11111111, 621 | 0b11111110, 622 | }, 623 | { 624 | 0b00000111, 625 | 0b00001111, 626 | 0b00011011, 627 | 0b00110011, 628 | 0b01100011, 629 | 0b11111111, 630 | 0b00000011, 631 | 0b00000011 632 | }, 633 | { 634 | 0b11111111, 635 | 0b11111111, 636 | 0b11000000, 637 | 0b11111110, 638 | 0b11111111, 639 | 0b00000011, 640 | 0b11111111, 641 | 0b11111110, 642 | }, 643 | { 644 | 0b01111111, 645 | 0b11111111, 646 | 0b11000000, 647 | 0b11111110, 648 | 0b11111111, 649 | 0b11000011, 650 | 0b11111111, 651 | 0b01111110, 652 | }, 653 | { 654 | 0b11111111, 655 | 0b11111111, 656 | 0b00000011, 657 | 0b00000110, 658 | 0b00001100, 659 | 0b00011000, 660 | 0b00011000, 661 | 0b00011000 662 | }, 663 | { 664 | 0b01111110, 665 | 0b11111111, 666 | 0b11000011, 667 | 0b11111111, 668 | 0b01111110, 669 | 0b11000011, 670 | 0b11111111, 671 | 0b01111110 672 | }, 673 | { 674 | 0b01111110, 675 | 0b11111111, 676 | 0b11000011, 677 | 0b11111111, 678 | 0b01111111, 679 | 0b00000011, 680 | 0b11111111, 681 | 0b01111110 682 | }, 683 | { 684 | 0b01111110, 685 | 0b01111110, 686 | 0b11100111, 687 | 0b11000011, 688 | 0b11111111, 689 | 0b11111111, 690 | 0b11000011, 691 | 0b11000011 692 | }, 693 | { 694 | 0b11111110, 695 | 0b11111111, 696 | 0b11000011, 697 | 0b11111110, 698 | 0b11111110, 699 | 0b11000011, 700 | 0b11111111, 701 | 0b11111110 702 | }, 703 | { 704 | 0b01111110, 705 | 0b11111111, 706 | 0b11000011, 707 | 0b11000000, 708 | 0b11000000, 709 | 0b11000011, 710 | 0b11111111, 711 | 0b01111110 712 | }, 713 | { 714 | 0b11111110, 715 | 0b11111111, 716 | 0b11000011, 717 | 0b11000011, 718 | 0b11000011, 719 | 0b11000011, 720 | 0b11111111, 721 | 0b11111110 722 | }, 723 | { 724 | 0b11111111, 725 | 0b11111111, 726 | 0b11000000, 727 | 0b11111100, 728 | 0b11111100, 729 | 0b11000000, 730 | 0b11111111, 731 | 0b11111111, 732 | }, 733 | { 734 | 0b11111111, 735 | 0b11111111, 736 | 0b11000000, 737 | 0b11111100, 738 | 0b11111100, 739 | 0b11000000, 740 | 0b11000000, 741 | 0b11000000 742 | }, 743 | { 744 | 0b01111110, 745 | 0b11111111, 746 | 0b11000011, 747 | 0b11000000, 748 | 0b11001111, 749 | 0b11000011, 750 | 0b11111111, 751 | 0b01111110 752 | }, 753 | { 754 | 0b11000011, 755 | 0b11000011, 756 | 0b11000011, 757 | 0b11111111, 758 | 0b11111111, 759 | 0b11000011, 760 | 0b11000011, 761 | 0b11000011 762 | }, 763 | { 764 | 0b00111100, 765 | 0b00011000, 766 | 0b00011000, 767 | 0b00011000, 768 | 0b00011000, 769 | 0b00011000, 770 | 0b00011000, 771 | 0b00111100 772 | }, 773 | { 774 | 0b00000011, 775 | 0b00000011, 776 | 0b00000011, 777 | 0b00000011, 778 | 0b00000011, 779 | 0b11000011, 780 | 0b11111111, 781 | 0b01111110 782 | }, 783 | { 784 | 0b11000011, 785 | 0b11000110, 786 | 0b11001100, 787 | 0b11111000, 788 | 0b11111000, 789 | 0b11001100, 790 | 0b11000110, 791 | 0b11000011 792 | }, 793 | { 794 | 0b11000000, 795 | 0b11000000, 796 | 0b11000000, 797 | 0b11000000, 798 | 0b11000000, 799 | 0b11000000, 800 | 0b11111111, 801 | 0b11111111 802 | }, 803 | { 804 | 0b11000011, 805 | 0b11100111, 806 | 0b11111111, 807 | 0b11011011, 808 | 0b11000011, 809 | 0b11000011, 810 | 0b11000011, 811 | 0b11000011 812 | }, 813 | { 814 | 0b11000011, 815 | 0b11100011, 816 | 0b11110011, 817 | 0b11011011, 818 | 0b11001111, 819 | 0b11000111, 820 | 0b11000011, 821 | 0b11000011 822 | }, 823 | { 824 | 0b01111110, 825 | 0b11111111, 826 | 0b11000011, 827 | 0b11000011, 828 | 0b11000011, 829 | 0b11000011, 830 | 0b11111111, 831 | 0b01111110 832 | }, 833 | { 834 | 0b11111110, 835 | 0b11111111, 836 | 0b11000011, 837 | 0b11111111, 838 | 0b11111110, 839 | 0b11000000, 840 | 0b11000000, 841 | 0b11000000 842 | }, 843 | { 844 | 0b01111110, 845 | 0b11111111, 846 | 0b11000011, 847 | 0b11000011, 848 | 0b11000011, 849 | 0b11001101, 850 | 0b11110110, 851 | 0b01111011 852 | }, 853 | { 854 | 0b11111110, 855 | 0b11111111, 856 | 0b11000011, 857 | 0b11111110, 858 | 0b11111100, 859 | 0b11000110, 860 | 0b11000011, 861 | 0b11000011 862 | }, 863 | { 864 | 0b01111111, 865 | 0b11111111, 866 | 0b11000000, 867 | 0b01111110, 868 | 0b01111110, 869 | 0b00000011, 870 | 0b11111111, 871 | 0b11111110 872 | }, 873 | { 874 | 0b11111111, 875 | 0b11111111, 876 | 0b00011000, 877 | 0b00011000, 878 | 0b00011000, 879 | 0b00011000, 880 | 0b00011000, 881 | 0b00011000 882 | }, 883 | { 884 | 0b11000011, 885 | 0b11000011, 886 | 0b11000011, 887 | 0b11000011, 888 | 0b11000011, 889 | 0b11000011, 890 | 0b11111111, 891 | 0b01111110 892 | }, 893 | { 894 | 0b11000011, 895 | 0b11000011, 896 | 0b11000011, 897 | 0b11000011, 898 | 0b11000011, 899 | 0b01100110, 900 | 0b00111100, 901 | 0b00011000 902 | }, 903 | { 904 | 0b11000011, 905 | 0b11000011, 906 | 0b11000011, 907 | 0b11000011, 908 | 0b11011011, 909 | 0b11111111, 910 | 0b11100111, 911 | 0b11000011 912 | 913 | }, 914 | { 915 | 0b11000011, 916 | 0b11000011, 917 | 0b01100110, 918 | 0b00111100, 919 | 0b00111100, 920 | 0b01100110, 921 | 0b11000011, 922 | 0b11000011 923 | }, 924 | { 925 | 0b11000011, 926 | 0b11000011, 927 | 0b11000011, 928 | 0b01100110, 929 | 0b00111100, 930 | 0b00011000, 931 | 0b00011000, 932 | 0b00011000 933 | }, 934 | { 935 | 0b11111111, 936 | 0b11111111, 937 | 0b00000110, 938 | 0b00001100, 939 | 0b00011000, 940 | 0b00110000, 941 | 0b11111111, 942 | 0b11111111 943 | }, 944 | { 945 | 0b00000000, 946 | 0b00000000, 947 | 0b00000000, 948 | 0b00000000, 949 | 0b00000000, 950 | 0b00000000, 951 | 0b00011000, 952 | 0b00011000 953 | }, 954 | { 955 | 0 956 | }, 957 | { 958 | 0b01111110, 959 | 0b11111111, 960 | 0b11111111, 961 | 0b11111111, 962 | 0b11111111, 963 | 0b11111111, 964 | 0b11111111, 965 | 0b11100111 966 | }, 967 | { 968 | 0b11111111, 969 | 0b10111101, 970 | 0b11111111, 971 | 0b11111111, 972 | 0b11111111, 973 | 0b11111111, 974 | 0b11100111, 975 | 0b11111111 976 | }, 977 | { 978 | 0b11111111, 979 | 0b11111111, 980 | 0b11011011, 981 | 0b11111111, 982 | 0b11111111, 983 | 0b11100111, 984 | 0b11111111, 985 | 0b11111111 986 | }, 987 | { 988 | 0b11111111, 989 | 0b11111111, 990 | 0b11111111, 991 | 0b11100111, 992 | 0b11100111, 993 | 0b11111111, 994 | 0b11111111, 995 | 0b11111111, 996 | }, 997 | }; 998 | 999 | #endif 1000 | -------------------------------------------------------------------------------- /src/mqtt.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * PubSubClient.cpp - A simple client for MQTT. 4 | * Nick O'Leary 5 | * http://knolleary.net 6 | * Minimized & adapted by Thomas Winischhofer (A10001986) in 2023 7 | * 8 | * Copyright (c) 2008-2020 Nicholas O'Leary 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining 11 | * a copy of this software and associated documentation files (the 12 | * "Software"), to deal in the Software without restriction, including 13 | * without limitation the rights to use, copy, modify, merge, publish, 14 | * distribute, sublicense, and/or sell copies of the Software, and to 15 | * permit persons to whom the Software is furnished to do so, subject to 16 | * the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be 19 | * included in all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | */ 30 | 31 | #include "sid_global.h" 32 | 33 | #ifdef SID_HAVEMQTT 34 | 35 | #include "mqtt.h" 36 | 37 | #include "lwip/inet_chksum.h" 38 | #include "lwip/ip.h" 39 | #include "lwip/ip4.h" 40 | #include "lwip/err.h" 41 | #include "lwip/icmp.h" 42 | #include "lwip/sockets.h" 43 | #include "lwip/sys.h" 44 | #include "lwip/netdb.h" 45 | #include "lwip/dns.h" 46 | 47 | static void defLooper() 48 | { 49 | } 50 | 51 | PubSubClient::PubSubClient(WiFiClient& client) 52 | { 53 | this->_state = MQTT_DISCONNECTED; 54 | this->_client = &client; 55 | this->bufferSize = 0; 56 | setBufferSize(MQTT_MAX_PACKET_SIZE); 57 | this->keepAlive = MQTT_KEEPALIVE; 58 | this->socketTimeout = MQTT_SOCKET_TIMEOUT * 1000; 59 | setLooper(defLooper); 60 | } 61 | 62 | PubSubClient::~PubSubClient() 63 | { 64 | free(this->buffer); 65 | } 66 | 67 | bool PubSubClient::connect(const char *id) 68 | { 69 | return connect(id, NULL, NULL, true); 70 | } 71 | 72 | bool PubSubClient::connect(const char *id, const char *user, const char *pass) 73 | { 74 | return connect(id, user, pass, true); 75 | } 76 | 77 | bool PubSubClient::connect(const char *id, const char *user, const char *pass, bool cleanSession) 78 | { 79 | if(!connected()) { 80 | 81 | int result = 0; 82 | 83 | if(_client->connected()) { 84 | result = 1; 85 | } else { 86 | if(domain) { 87 | result = _client->connect(this->domain, this->port, (int)5000); 88 | } else { 89 | result = _client->connect(this->ip, this->port, (int)5000); 90 | } 91 | } 92 | 93 | if(result == 1) { 94 | 95 | nextMsgId = 1; 96 | 97 | // Leave room in the buffer for header and variable length field 98 | uint16_t length = MQTT_MAX_HEADER_SIZE; 99 | unsigned int j; 100 | 101 | #if MQTT_VERSION == MQTT_VERSION_3_1 102 | uint8_t d[9] = { 0x00, 0x06, 'M', 'Q', 'I', 's', 'd', 'p', MQTT_VERSION }; 103 | #define MQTT_HEADER_VERSION_LENGTH 9 104 | #elif MQTT_VERSION == MQTT_VERSION_3_1_1 105 | uint8_t d[7] = { 0x00, 0x04, 'M', 'Q', 'T', 'T', MQTT_VERSION }; 106 | #define MQTT_HEADER_VERSION_LENGTH 7 107 | #endif 108 | for(j = 0; j < MQTT_HEADER_VERSION_LENGTH; j++) { 109 | this->buffer[length++] = d[j]; 110 | } 111 | 112 | uint8_t v = 0; 113 | 114 | if(cleanSession) v |= 0x02; 115 | 116 | if(user) { 117 | v |= 0x80; 118 | if(pass) { 119 | v |= 0x40; 120 | } 121 | } 122 | this->buffer[length++] = v; 123 | 124 | this->buffer[length++] = (this->keepAlive >> 8); 125 | this->buffer[length++] = (this->keepAlive & 0xff); 126 | 127 | CHECK_STRING_LENGTH(length, id) 128 | length = writeString(id, this->buffer, length); 129 | 130 | if(user) { 131 | CHECK_STRING_LENGTH(length, user) 132 | length = writeString(user, this->buffer, length); 133 | if(pass) { 134 | CHECK_STRING_LENGTH(length, pass) 135 | length = writeString(pass, this->buffer, length); 136 | } 137 | } 138 | 139 | write(MQTTCONNECT, this->buffer, length - MQTT_MAX_HEADER_SIZE); 140 | 141 | lastInActivity = lastOutActivity = millis(); 142 | 143 | _state = MQTT_CONNECTING; 144 | 145 | return true; 146 | 147 | } else { 148 | 149 | _state = MQTT_CONNECT_FAILED; 150 | 151 | } 152 | 153 | return false; 154 | 155 | } 156 | 157 | return true; 158 | } 159 | 160 | // reads a byte into result 161 | bool PubSubClient::readByte(uint8_t *result) 162 | { 163 | unsigned long mnow, currentMillis; 164 | unsigned long previousMillis = millis(); 165 | 166 | mnow = previousMillis; 167 | 168 | while(!_client->available()) { 169 | 170 | currentMillis = millis(); 171 | 172 | if(currentMillis - previousMillis >= this->socketTimeout) 173 | return false; 174 | 175 | if(currentMillis - mnow > 20) { 176 | looper(); 177 | mnow = millis(); 178 | } 179 | 180 | delay(2); 181 | 182 | } 183 | 184 | *result = _client->read(); 185 | 186 | return true; 187 | } 188 | 189 | // reads a byte into result[*index] and increments index 190 | bool PubSubClient::readByte(uint8_t *result, uint16_t *index) 191 | { 192 | uint16_t current_index = *index; 193 | uint8_t *write_address = &(result[current_index]); 194 | 195 | if(readByte(write_address)) { 196 | *index = current_index + 1; 197 | return true; 198 | } 199 | 200 | return false; 201 | } 202 | 203 | uint32_t PubSubClient::readPacket(uint8_t *lengthLength) 204 | { 205 | uint16_t len = 0; 206 | 207 | if(!readByte(this->buffer, &len)) 208 | return 0; 209 | 210 | bool isPublish = ((this->buffer[0] & 0xf0) == MQTTPUBLISH); 211 | 212 | uint32_t multiplier = 1; 213 | uint32_t length = 0; 214 | uint8_t digit = 0; 215 | uint32_t start = 0; 216 | 217 | do { 218 | 219 | if(len == 5) { 220 | // Invalid remaining length encoding - kill the connection 221 | _state = MQTT_DISCONNECTED; 222 | _client->stop(); 223 | return 0; 224 | } 225 | 226 | if(!readByte(&digit)) return 0; 227 | 228 | this->buffer[len++] = digit; 229 | length += (digit & 0x7f) * multiplier; 230 | multiplier <<= 7; 231 | 232 | } while((digit & 0x80) != 0); 233 | 234 | *lengthLength = len - 1; 235 | 236 | if(isPublish) { 237 | 238 | // Read in topic length to calculate bytes to skip over for Stream writing 239 | if(!readByte(this->buffer, &len)) return 0; 240 | if(!readByte(this->buffer, &len)) return 0; 241 | start = 2; 242 | 243 | } 244 | 245 | uint32_t idx = len; 246 | 247 | for(uint32_t i = start; i < length; i++) { 248 | 249 | if(!readByte(&digit)) 250 | return 0; 251 | 252 | if(len < this->bufferSize) { 253 | this->buffer[len] = digit; 254 | len++; 255 | } 256 | idx++; 257 | } 258 | 259 | if(idx > this->bufferSize) 260 | len = 0; // This will cause the packet to be ignored. 261 | 262 | return len; 263 | } 264 | 265 | bool PubSubClient::loop() 266 | { 267 | if(_state == MQTT_CONNECTING) { 268 | 269 | if(!_client->available()) { 270 | 271 | if(millis() - lastInActivity >= this->socketTimeout) { 272 | _state = MQTT_CONNECTION_TIMEOUT; 273 | _client->stop(); 274 | #ifdef SID_DBG 275 | Serial.println("MQTT: CONNACK timed-out"); 276 | #endif 277 | return false; 278 | } 279 | 280 | return true; 281 | 282 | } else { 283 | 284 | uint8_t llen; 285 | uint32_t len = readPacket(&llen); 286 | 287 | if(len == 4) { 288 | if(buffer[3] == 0) { 289 | lastInActivity = millis(); 290 | pingOutstanding = false; 291 | _state = MQTT_CONNECTED; 292 | #ifdef SID_DBG 293 | Serial.println("MQTT: CONNACK received"); 294 | #endif 295 | return true; 296 | } else { 297 | _state = buffer[3]; 298 | } 299 | } else { 300 | _state = MQTT_CONNECT_BAD_PROTOCOL; 301 | } 302 | _client->stop(); 303 | 304 | #ifdef SID_DBG 305 | Serial.printf("MQTT: CONNACK failed, state %d\n", _state); 306 | #endif 307 | 308 | return false; 309 | } 310 | 311 | } else if(connected()) { 312 | 313 | unsigned long t = millis(); 314 | unsigned long ka = this->keepAlive * 1000UL; 315 | 316 | if((t - lastInActivity > ka) || (t - lastOutActivity > ka)) { 317 | 318 | if(pingOutstanding) { 319 | this->_state = MQTT_CONNECTION_TIMEOUT; 320 | _client->stop(); 321 | return false; 322 | } else { 323 | this->buffer[0] = MQTTPINGREQ; 324 | this->buffer[1] = 0; 325 | _client->write(this->buffer, 2); 326 | lastOutActivity = t; 327 | lastInActivity = t; 328 | pingOutstanding = true; 329 | } 330 | 331 | } 332 | 333 | if(_client->available()) { 334 | 335 | uint8_t llen; 336 | uint16_t len = readPacket(&llen); 337 | uint8_t msgId1 = 0, msgId2 = 0; 338 | uint8_t *payload; 339 | 340 | if(len > 0) { 341 | 342 | lastInActivity = t; 343 | uint8_t type = this->buffer[0] & 0xf0; 344 | 345 | if(type == MQTTPUBLISH) { 346 | 347 | if(callback) { 348 | // topic length in bytes 349 | uint16_t tl = (this->buffer[llen+1] << 8) + this->buffer[llen+2]; 350 | 351 | // move topic inside buffer 1 byte to front to make room for 0-terminator 352 | memmove(this->buffer + llen + 2, this->buffer + llen + 3, tl); 353 | this->buffer[llen + 2 + tl] = 0; 354 | 355 | char *topic = (char *)this->buffer + llen + 2; 356 | 357 | if((this->buffer[0] & 0x06) == MQTTQOS1) { 358 | 359 | // msgId only present for QOS>0 360 | msgId1 = this->buffer[llen + 3 + tl]; 361 | msgId2 = this->buffer[llen + 3 + tl + 1]; 362 | 363 | payload = this->buffer + llen + 3 + tl + 2; 364 | callback(topic, payload, len - llen - 3 - tl - 2); 365 | 366 | this->buffer[0] = MQTTPUBACK; 367 | this->buffer[1] = 2; 368 | this->buffer[2] = msgId1; 369 | this->buffer[3] = msgId2; 370 | _client->write(this->buffer, 4); 371 | lastOutActivity = t; 372 | 373 | } else { 374 | 375 | payload = this->buffer + llen + 3 + tl; 376 | callback(topic, payload, len - llen - 3 - tl); 377 | 378 | } 379 | } 380 | 381 | } else if(type == MQTTPINGREQ) { 382 | 383 | this->buffer[0] = MQTTPINGRESP; 384 | this->buffer[1] = 0; 385 | _client->write(this->buffer, 2); 386 | 387 | } else if(type == MQTTPINGRESP) { 388 | 389 | pingOutstanding = false; 390 | 391 | } 392 | 393 | } else if(!connected()) { 394 | 395 | // readPacket has closed the connection 396 | return false; 397 | 398 | } 399 | } 400 | 401 | return true; 402 | } 403 | 404 | return false; 405 | } 406 | 407 | bool PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, bool retained) 408 | { 409 | if(connected()) { 410 | if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) { 411 | // Too long 412 | return false; 413 | } 414 | 415 | // Leave room in the buffer for header and variable length field 416 | uint16_t length = MQTT_MAX_HEADER_SIZE; 417 | length = writeString(topic, this->buffer, length); 418 | 419 | // Add payload 420 | uint16_t i; 421 | for(i = 0; i < plength; i++) { 422 | this->buffer[length++] = payload[i]; 423 | } 424 | 425 | // Write the header 426 | uint8_t header = MQTTPUBLISH; 427 | 428 | if(retained) header |= 1; 429 | 430 | return write(header, this->buffer, length - MQTT_MAX_HEADER_SIZE); 431 | } 432 | 433 | return false; 434 | } 435 | 436 | size_t PubSubClient::buildHeader(uint8_t header, uint8_t *buf, uint16_t length) 437 | { 438 | uint8_t lenBuf[4]; 439 | uint8_t llen = 0; 440 | uint8_t digit; 441 | uint8_t pos = 0; 442 | uint16_t len = length; 443 | 444 | do { 445 | 446 | digit = len & 0x7f; 447 | len >>= 7; 448 | if(len > 0) digit |= 0x80; 449 | lenBuf[pos++] = digit; 450 | llen++; 451 | 452 | } while(len > 0); 453 | 454 | buf[4 - llen] = header; 455 | 456 | for(int i = 0; i < llen; i++) { 457 | buf[MQTT_MAX_HEADER_SIZE - llen + i] = lenBuf[i]; 458 | } 459 | 460 | return llen + 1; // Full header size is variable length bit plus the 1-byte fixed header 461 | } 462 | 463 | bool PubSubClient::write(uint8_t header, uint8_t *buf, uint16_t length) 464 | { 465 | uint16_t rc; 466 | uint8_t hlen = buildHeader(header, buf, length); 467 | 468 | #ifdef MQTT_MAX_TRANSFER_SIZE 469 | 470 | uint8_t* writeBuf = buf + (MQTT_MAX_HEADER_SIZE - hlen); 471 | uint16_t bytesRemaining = length + hlen; // Match the length type 472 | uint8_t bytesToWrite; 473 | bool result = true; 474 | 475 | while((bytesRemaining > 0) && result) { 476 | bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE) ? MQTT_MAX_TRANSFER_SIZE : bytesRemaining; 477 | rc = _client->write(writeBuf, bytesToWrite); 478 | result = (rc == bytesToWrite); 479 | bytesRemaining -= rc; 480 | writeBuf += rc; 481 | } 482 | 483 | return result; 484 | 485 | #else 486 | 487 | rc = _client->write(buf + (MQTT_MAX_HEADER_SIZE - hlen), length + hlen); 488 | lastOutActivity = millis(); 489 | return (rc == hlen + length); 490 | 491 | #endif 492 | } 493 | 494 | bool PubSubClient::subscribe(const char *topic, const char *topic2, uint8_t qos) 495 | { 496 | return subscribe_int(false, topic, topic2, qos); 497 | } 498 | 499 | bool PubSubClient::unsubscribe(const char* topic) 500 | { 501 | return subscribe_int(true, topic, NULL, 0); 502 | } 503 | 504 | bool PubSubClient::subscribe_int(bool unsubscribe, const char *topic, const char *topic2, uint8_t qos) 505 | { 506 | size_t topicLength = strnlen(topic, this->bufferSize); 507 | size_t topicLength2 = topic2 ? strnlen(topic2, this->bufferSize) : 0; 508 | uint8_t header = unsubscribe ? MQTTUNSUBSCRIBE : MQTTSUBSCRIBE; 509 | 510 | if(!topic) 511 | return false; 512 | 513 | if(!unsubscribe) { 514 | if(qos > 1) 515 | return false; 516 | if(this->bufferSize < MQTT_MAX_HEADER_SIZE+2 + 2+topicLength+1 + (topicLength2 ? 2+topicLength+1 : 0)) 517 | return false; 518 | } else { 519 | if(this->bufferSize < MQTT_MAX_HEADER_SIZE+2 + 2+topicLength) 520 | return false; 521 | } 522 | 523 | if(connected()) { 524 | // Leave room in the buffer for header and variable length field 525 | uint16_t length = MQTT_MAX_HEADER_SIZE; 526 | nextMsgId++; 527 | if(!nextMsgId) nextMsgId++; 528 | this->buffer[length++] = (nextMsgId >> 8); 529 | this->buffer[length++] = (nextMsgId & 0xff); 530 | 531 | length = writeString((char*)topic, this->buffer, length); 532 | if(!unsubscribe) this->buffer[length++] = qos; 533 | 534 | if(topic2 && topicLength2) { 535 | length = writeString((char*)topic2, this->buffer, length); 536 | this->buffer[length++] = qos; 537 | } 538 | 539 | return write(header | MQTTQOS1, this->buffer, length - MQTT_MAX_HEADER_SIZE); 540 | } 541 | 542 | return false; 543 | } 544 | 545 | void PubSubClient::disconnect() 546 | { 547 | this->buffer[0] = MQTTDISCONNECT; 548 | this->buffer[1] = 0; 549 | 550 | _client->write(this->buffer, 2); 551 | 552 | _state = MQTT_DISCONNECTED; 553 | 554 | _client->flush(); 555 | _client->stop(); 556 | 557 | lastInActivity = lastOutActivity = millis(); 558 | } 559 | 560 | uint16_t PubSubClient::writeString(const char *string, uint8_t *buf, uint16_t pos) 561 | { 562 | const char *idp = string; 563 | uint16_t i = 0, oldPos = pos; 564 | 565 | pos += 2; 566 | while(*idp) { 567 | buf[pos++] = *idp++; 568 | i++; 569 | } 570 | buf[oldPos++] = (i >> 8); 571 | buf[oldPos] = (i & 0xff); 572 | 573 | return pos; 574 | } 575 | 576 | bool PubSubClient::connected() 577 | { 578 | bool rc = false; 579 | 580 | if(_client) { 581 | 582 | rc = (int)_client->connected(); 583 | 584 | if(!rc) { 585 | if(this->_state == MQTT_CONNECTED) { 586 | this->_state = MQTT_CONNECTION_LOST; 587 | _client->flush(); 588 | _client->stop(); 589 | } 590 | } else { 591 | return (this->_state == MQTT_CONNECTED); 592 | } 593 | } 594 | return rc; 595 | } 596 | 597 | void PubSubClient::setServer(IPAddress ip, uint16_t port) 598 | { 599 | this->ip = ip; 600 | this->port = port; 601 | this->domain = NULL; 602 | } 603 | 604 | void PubSubClient::setServer(const char *domain, uint16_t port) 605 | { 606 | this->domain = domain; 607 | this->port = port; 608 | } 609 | 610 | void PubSubClient::setCallback(void (*callback)(char*, uint8_t*, unsigned int)) 611 | { 612 | this->callback = callback; 613 | } 614 | 615 | void PubSubClient::setLooper(void (*looper)()) 616 | { 617 | this->looper = looper; 618 | } 619 | 620 | int PubSubClient::state() 621 | { 622 | return this->_state; 623 | } 624 | 625 | bool PubSubClient::setBufferSize(uint16_t size) 626 | { 627 | if(size == 0) 628 | return false; 629 | 630 | if(this->bufferSize == 0) { 631 | this->buffer = (uint8_t*)malloc(size); 632 | } else { 633 | uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size); 634 | if(newBuffer) { 635 | this->buffer = newBuffer; 636 | } else { 637 | return false; 638 | } 639 | } 640 | 641 | this->bufferSize = size; 642 | 643 | return (this->buffer != NULL); 644 | } 645 | 646 | /* 647 | * Async PING 648 | * We PING the broker before connecting in order 649 | * to avoid a blocking connect(). 650 | */ 651 | 652 | #define PING_ID 0xAFAF 653 | 654 | bool PubSubClient::sendPing() 655 | { 656 | struct sockaddr_in address; 657 | ip4_addr_t ping_target; 658 | struct icmp_echo_hdr *iecho; 659 | struct sockaddr_in to; 660 | struct timeval tout; 661 | int size = 32; 662 | size_t ping_size = sizeof(struct icmp_echo_hdr) + size; 663 | size_t data_len = ping_size - sizeof(struct icmp_echo_hdr); 664 | int err; 665 | 666 | #ifdef SID_DBG 667 | Serial.printf("MQTT: Sending ping\n"); 668 | #endif 669 | 670 | // We only PING if we have an IP address. 671 | // No point in avoiding a blocking connect 672 | // if a DNS call is required beforehand 673 | if(this->domain) 674 | return false; 675 | 676 | if((_s = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) 677 | return false; 678 | 679 | address.sin_addr.s_addr = ip; 680 | ping_target.addr = ip; 681 | 682 | tout.tv_sec = 0; 683 | tout.tv_usec = 2000; // 2ms 684 | 685 | if(setsockopt(_s, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(tout)) < 0) { 686 | closesocket(_s); 687 | return false; 688 | } 689 | 690 | iecho = (struct icmp_echo_hdr *)mem_malloc((mem_size_t)ping_size); 691 | if(!iecho) { 692 | closesocket(_s); 693 | return false; 694 | } 695 | 696 | ICMPH_TYPE_SET(iecho, ICMP_ECHO); 697 | ICMPH_CODE_SET(iecho, 0); 698 | iecho->chksum = 0; 699 | iecho->id = PING_ID; 700 | iecho->seqno = htons(++_pseq_num); 701 | 702 | iecho->chksum = inet_chksum(iecho, (uint16_t)ping_size); 703 | 704 | to.sin_len = sizeof(to); 705 | to.sin_family = AF_INET; 706 | inet_addr_from_ip4addr(&to.sin_addr, &ping_target); 707 | 708 | err = sendto(_s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to)); 709 | 710 | mem_free(iecho); 711 | 712 | if(err) { 713 | _pstate = PING_PINGING; 714 | } else { 715 | closesocket(_s); 716 | _pstate = PING_IDLE; 717 | } 718 | 719 | return (err ? true : false); 720 | } 721 | 722 | bool PubSubClient::pollPing() 723 | { 724 | char buf[64]; 725 | int len, fromlen; 726 | struct sockaddr_in from; 727 | struct ip_hdr *iphdr; 728 | struct icmp_echo_hdr *iecho = NULL; 729 | bool success = false; 730 | 731 | if(_pstate != PING_PINGING) 732 | return false; 733 | 734 | while((len = recvfrom(_s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) { 735 | if(len >= (int)(sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr))) { 736 | iphdr = (struct ip_hdr *)buf; 737 | iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4)); 738 | success = ((iecho->id == PING_ID) && (iecho->seqno == htons(_pseq_num))); 739 | } 740 | } 741 | 742 | if(success) { 743 | closesocket(_s); 744 | _pstate = PING_IDLE; 745 | } 746 | 747 | return success; 748 | } 749 | 750 | void PubSubClient::cancelPing() 751 | { 752 | if(_pstate != PING_PINGING) 753 | return; 754 | 755 | closesocket(_s); 756 | _pstate = PING_IDLE; 757 | } 758 | 759 | int PubSubClient::pstate() 760 | { 761 | return this->_pstate; 762 | } 763 | 764 | #endif // TC_HAVEMQTT 765 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firmware for Status Indicator Display (SID) 2 | 3 | This repository holds the most current firmware for CircuitSetup's magnificent [SID](https://circuitsetup.us/product/delorean-time-machine-status-indicator-display-sid/). The SID, also known as "Field Containment System Display", is an important part of Doc Brown's time machine in the "Back to the Future" movies. 4 | 5 | The hardware is available [here](https://circuitsetup.us/product/delorean-time-machine-status-indicator-display-sid/). 6 | 7 | ![mysid](https://github.com/realA10001986/SID/assets/76924199/cdd8f609-1248-41f2-92cc-0489fe0397bf) 8 | 9 | Features include 10 | - various idle patterns 11 | - [Time Travel](#time-travel) function, triggered by button, [Time Circuits Display](https://tcd.backtothefutu.re) or via [MQTT](#home-assistant--mqtt) 12 | - [IR remote controlled](#ir-remote-control); can learn keys from third-party remote 13 | - Spectrum Analyzer mode via built-in microphone 14 | - Advanced network-accessible [Config Portal](#the-config-portal) for setup with mDNS support for easy access (http://sid.local, hostname configurable) 15 | - Wireless communication with [Time Circuits Display](https://tcd.backtothefutu.re) ("[BTTF-Network](#bttf-network-bttfn)"); used for synchonized time travels, GPS-speed adapted patterns, alarm, night mode, fake power and remote control through TCD keypad 16 | - [Home Assistant](#home-assistant--mqtt) (MQTT 3.1.1) support 17 | - [*Siddly*](#siddly) and [*Snake*](#snake) games 18 | - [SD card](#sd-card) support 19 | 20 | ## Installation 21 | 22 | There are different alternative ways to install this firmware: 23 | 24 | 1) If a previous version of the SID firmware was installed on your device, you can upload the provided pre-compiled binary to update to the current version: Enter the [Config Portal](#the-config-portal), click on "Update" and select the pre-compiled binary file provided in this repository ("install/sid-A10001986.ino.nodemcu-32s.bin"). 25 | 26 | 2) Using the Arduino IDE or PlatformIO: Download the sketch source code, all required libraries, compile and upload it. This method is the one for fresh ESP32 boards and/or folks familiar with the programming tool chain. Detailed build information is in [sid-A10001986.ino](https://github.com/realA10001986/SID/blob/main/sid-A10001986/sid-A10001986.ino). 27 | 28 | *Important: After a firmware update, a "wait" symbol (hourglass) might be shown for up to a minute after reboot. Do NOT unplug the device during this time.* 29 | 30 | ## Short summary of first steps 31 | 32 | A good first step would be to establish access to the Config Portal in order to configure your SID. 33 | 34 | As long as the device is unconfigured, as is the case with a brand new SID, or later if it for some reason fails to connect to a configured WiFi network, it starts in "access point" mode, i.e. it creates a WiFi network of its own named "SID-AP". This is called "Access Point mode", or "AP-mode". 35 | 36 | - Power up the device and wait until the startup sequence has completed. 37 | - Connect your computer or handheld device to the WiFi network "SID-AP". 38 | - Navigate your browser to http://sid.local or http://192.168.4.1 to enter the Config Portal. 39 | 40 | If you want your SID to connect to your WiFi network, click on "Configure WiFi". The bare minimum is to select an SSID (WiFi network name) and a WiFi password. 41 | 42 | Note that the device requests an IP address via DHCP, unless you entered valid data in the fields for static IP addresses (IP, gateway, netmask, DNS). 43 | 44 | After saving the WiFi network settings, the device reboots and tries to connect to your configured WiFi network. If that fails, it will again start in access point mode. 45 | 46 | If the device is inaccessible as a result of wrong static IPs, wait until the SID has completed its startup sequence, then type \*123456OK on the IR remote; static IP data will be deleted and the device will return to DHCP after a reboot. 47 | 48 | If you have your SID, along with a Time Circuits Display, mounted in a car, see also [here](#car-setup). 49 | 50 | ### The Config Portal 51 | 52 | The Config Portal is accessible exclusively through WiFi. As outlined above, if the device is not connected to a WiFi network, it creates its own WiFi network (named "SID-AP"), to which your WiFi-enabled hand held device or computer first needs to connect in order to access the Config Portal. 53 | 54 | If the operating system on your handheld or computer supports Bonjour (a.k.a. "mDNS"), you can enter the Config Portal by directing your browser to http://sid.local. (mDNS is supported on Windows 10 version TH2 (1511) [other sources say 1703] and later, Android 13 and later, MacOS, iOS) 55 | 56 | If that fails, the way to enter the Config Portal depends on whether the device is in access point mode or not. 57 | - If it is in access point mode (and your handheld/computer is connected to the WiFi network "SID-AP"), navigate your browser to http://192.168.4.1 58 | - Otherwise type *90 followed by OK on the remote control; the IP address will be shown on the display. 59 | 60 | In the main menu, click on "Setup" to configure your SID. 61 | 62 | | ![The Config Portal](https://github.com/realA10001986/SID/assets/76924199/8c60dc7a-9d3c-4b65-be3a-411640874c48) | 63 | |:--:| 64 | | *The Config Portal's Setup page* | 65 | 66 | A full reference of the Config Portal is [here](#appendix-a-the-config-portal). 67 | 68 | ## Basic Operation 69 | 70 | When the SID is idle, it shows an idle pattern. There are alternative idle patterns to choose from, selected by *10OK through *14OK on the remote, or via MQTT. If an SD card is inserted, the setting will be persistent accross reboots. 71 | 72 | If the option **_Adhere strictly to movie patterns_** is set (which is the default), the idle patterns #0 through #3 will only use patterns extracted from the movies (plus some interpolations); the same goes for when [GPS speed](#bttf-network-bttfn) is used. If this option is unset, random variations are shown, which is less boring, but also less accurate. 73 | 74 | For ways to trigger a time travel, see [here](#time-travel). 75 | 76 | The main control device is the supplied IR remote control. If a TCD is connected through [BTTF-Network](#bttf-network-bttfn), the SID can also be controlled through the TCD's keypad. 77 | 78 | ### IR remote control 79 | 80 | Your SID has an IR remote control included. This remote works out-of-the-box and needs no setup. 81 | 82 | | ![Supplied IR remote control](https://github.com/realA10001986/SID/assets/76924199/2a3bfd40-2a44-4cc0-8209-13468115ae17) | 83 | |:--:| 84 | | *The default IR remote control* | 85 | 86 | Each time you press a (recognized) key on the remote, an IR feedback LED will briefly light up. This LED is located at the bottom of the board. 87 | 88 | ### IR learning 89 | 90 | Your SID can learn the codes of another IR remote control. Most remotes with a carrier signal of 38kHz (which most IR remotes use) will work. However, some remote controls, expecially ones for TVs, send keys repeatedly and/or send different codes alternately. If you had the SID learn a remote and the keys are not (always) recognized afterwards or appear to the pressed repeatedly while held, that remote is of that type and cannot be used. 91 | 92 | First, go to the Config Portal, uncheck **_TCD connected by wire_** on the Setup page and save. The SID reboots. Afterwards, to start the learning process, hold the Time Travel button for a few seconds, until the displays shows "GO" followed by "0". Then press "0" on your remote, which the SID will visually acknowledge by displaying the next key to press. Then press "1", wait for the acknowledgement, and so on. Enter your keys in the following order: 93 | 94 | ```0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - * - # - Arrow up - Arrow down - Arrow left - Arrow right - OK``` 95 | 96 | If your remote control lacks the \* (starts command sequence) and \# (aborts command sequence) keys, you can use any other key, of course. \* could be eg. "menu" or "setup", \# could be "exit" or "return". 97 | 98 | If no key is pressed for 10 seconds, the learning process aborts, as does briefly pressing the Time Travel button. In thoses cases, the keys already learned are forgotten and nothing is saved. 99 | 100 | To make the SID forget a learned IR remote control, type *654321 followed by OK. 101 | 102 | ### Locking IR control 103 | 104 | You can have your SID ignore IR commands from any IR remote control (be it the default supplied one, be it one you had the SID learn) by entering *71 followed by OK. After this sequence the SID will ignore all IR commands until *71OK is entered again. The purpose of this function is to enable you to use the same IR control for your SID and other props (such as Flux Capacitor). 105 | 106 | Note that the status of the IR lock is saved 10 seconds after its last change, and is persistent accross reboots. 107 | 108 | In order to only disable the supplied IR remote control, check the option **_Disable supplied IR remote control_** in the [Config Portal](#-disable-supplied-ir-remote-control). In that case, any learned remote will still work. 109 | 110 | ### Remote control reference 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 |
Single key actions
1
Games: Quit
2
-
3
Games: New game
4
-
5
-
6
-
7
-
8
-
9
Games: Pause
*
Start command sequence
0
Time Travel
Siddly: Fall down
#
Abort command sequence

Increase Brightness
Siddly: Rotate
Snake: Up

Games: Left
OK
Execute command

Games: Right

Decrease Brightness
Games: Down
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 |
Special sequences
(⏎ = OK key)
FunctionCode on IRCode on TCD
Default idle pattern*10⏎6010
Idle pattern 1*11⏎6011
Idle pattern 2*12⏎6012
Idle pattern 3*13⏎6013
Idle pattern 4*14⏎6014
Switch to idle mode*20⏎6020
Start Spectrum Analyzer*21⏎6021
Start Siddly game*22⏎6022
Start Snake game*23⏎6023
Enable/disable "strictly movie patterns"*50⏎6050
Enable/disable peaks in Spectrum Analyzer*51⏎6051
Disable/Enable IR remote commands*71⏎6071
Display current IP address*90⏎6090
Reboot the device*64738⏎6064738
Delete static IP address
and WiFi-AP password
*123456⏎6123456
Delete learned IR remote control*654321⏎6654321
223 | 224 | [Here](https://github.com/realA10001986/SID/blob/main/CheatSheet.pdf) is a cheat sheet for printing or screen-use. (Note that MacOS' preview application has a bug that scrambles the links in the document. Acrobat Reader does it correctly.) 225 | 226 | ## Time travel 227 | 228 | To travel through time, type "0" on the remote control. The SID will play its time travel sequence. 229 | 230 | You can also connect a physical button to your SID; the button must shorten "TT" and "3.3V" on the "Time Travel" connector. Pressing this button briefly will trigger a time travel. 231 | 232 | Other ways of triggering a time travel are available if a [Time Circuits Display](#connecting-a-time-circuits-display) is connected. 233 | 234 | ## Spectrum Analyzer 235 | 236 | The spectrum analyzer (or rather: frequency-separated vu meter) works through a built-in microphone. This microphone is located behind the right hand side center hole of the enclosure. 237 | 238 | Sticky peaks are optional, they can be switched on/off in the Config Portal and by typing *51 followed by OK on the remote. 239 | 240 | ## Games 241 | 242 | ### Siddly 243 | 244 | Siddly is a simple game where puzzle pieces of various shapes fall down from the top. You can slide them left and right, as well as rotate them while they are falling. When the piece lands at the bottom, a new piece will appear at the top and start falling down. If a line at the bottom is completely filled with fallen pieces or parts thereof, that line will be cleared, and everything piled on top of that line will move down. The target is to keep the pile at the bottom as low as possible; the game ends when the pile is as high as the screen and no new piece has room to appear. I think you get the idea. Note that the red LEDs at the top are not part of the playfield (but show a level-progress bar instead), the field only covers the yellow and green LEDs, and that simularities of Siddly with computer games, especially older ones, exist only in your imagination. 245 | 246 | ### Snake 247 | 248 | Snakes like apples (at least so I have heard). You control a snake that feels a profound urge to eat apples. After each eaten apple, the snake grows, and a new apple appears. Unfortunately, snakes don't like to hit their heads, so you need to watch out that the snake's head doesn't collide with its body. 249 | 250 | ## SD card 251 | 252 | Preface note on SD cards: For unknown reasons, some SD cards simply do not work with this device. For instance, I had no luck with Sandisk Ultra 32GB and "Intenso" cards. If your SD card is not recognized, check if it is formatted in FAT32 format (not exFAT!). Also, the size must not exceed 32GB (as larger cards cannot be formatted with FAT32). I am currently using Transcend SDHC 4GB cards and those work fine. 253 | 254 | The SD card is used for saving secondary settings, in order to avoid flash wear on the SID's ESP32. The chosen idle pattern (*1x) is only stored on SD, so for it to be persistent accross reboots, an SD card is required. 255 | 256 | Note that the SD card must be inserted before powering up the device. It is not recognized if inserted while the SID is running. Furthermore, do not remove the SD card while the device is powered. 257 | 258 | ## Connecting a Time Circuits Display 259 | 260 | ### Connecting a TCD by wire 261 | 262 | Connect GND and GPIO on the SID's "Time Travel" connector to the TCD like in the table below: 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 |
SID:
"Time Travel" connector
TCD control board 1.2TCD control board 1.3
GNDGND of "IO13" connectorGND on "Time Travel" connector
TTIO13 of "IO13" connectorTT OUT on "Time Travel" connector
281 | 282 | Next, head to the Config Portal and set the ooption **_TCD connected by wire_**. On the TCD, the option "Control props connected by wire" must be set. 283 | 284 | Note that a wired connection only allows for synchronized time travel sequences, no other communication takes place. Therefore I strongly recommend a wireless BTTFN connection, see immediately below. 285 | 286 | ### BTTF-Network ("BTTFN") 287 | 288 | The TCD can communicate with the SID wirelessly, via WiFi. It can send out information about a time travel and an alarm, and the SID queries the TCD for speed and some other data. Furthermore, the TCD's keypad can be used to remote-control the SID. 289 | 290 | ![BTTFN connection](https://github.com/realA10001986/SID/assets/76924199/404e4dd5-ac31-4ca4-b6a1-c8084d3a82e0) 291 | 292 | In order to connect your SID to the TCD using BTTFN, just enter the TCD's IP address or hostname in the **_IP address or hostname of TCD_** field in the SID's Config Portal. On the TCD, no special configuration is required. Note that you need TCD firmware 2.9.1 or later for using a hostname; previous versions only work with an IP address. 293 | 294 | Afterwards, the SID and the TCD can communicate wirelessly and 295 | - play time travel sequences in sync, 296 | - both play an alarm-sequence when the TCD's alarm occurs, 297 | - the SID can be remote controlled through the TCD's keypad (command codes 6xxx), 298 | - the SID queries the TCD for GPS speed if desired to adapt its idle pattern to GPS speed, 299 | - the SID queries the TCD for fake power and night mode, in order to react accordingly if so configured. 300 | 301 | You can use BTTF-Network and MQTT at the same time, see immediately below. 302 | 303 | ## Home Assistant / MQTT 304 | 305 | The SID supports the MQTT protocol version 3.1.1 for the following features: 306 | 307 | ### Control the SID via MQTT 308 | 309 | The SID can - to a some extent - be controlled through messages sent to topic **bttf/sid/cmd**. Support commands are 310 | - TIMETRAVEL: Start a [time travel](#time-travel) 311 | - IDLE: Switch to idle mode 312 | - SA: Start spectrum analyzer 313 | - IDLE_0, IDLE_1, IDLE_2, IDLE_3, IDLE_4: Select idle pattern 314 | 315 | ### Receive commands from Time Circuits Display 316 | 317 | If both TCD and SID are connected to the same broker, and the option **_Send event notifications_** is checked on the TCD's side, the SID will receive information on time travel and alarm and play their sequences in sync with the TCD. Unlike BTTFN, however, no other communication takes place. 318 | 319 | ![MQTT connection](https://github.com/realA10001986/SID/assets/76924199/4a7f3c63-91cf-4206-af36-ab39c28dfb3e) 320 | 321 | MQTT and BTTFN can co-exist. However, the TCD only sends out time travel and alarm notifications through either MQTT or BTTFN, never both. If you have other MQTT-aware devices listening to the TCD's public topic (bttf/tcd/pub) in order to react to time travel or alarm messages, use MQTT (ie check **_Send event notifications_**). If only BTTFN-aware devices are to be used, uncheck this option to use BTTFN as it has less latency. 322 | 323 | ### Setup 324 | 325 | In order to connect to a MQTT network, a "broker" (such as [mosquitto](https://mosquitto.org/), [EMQ X](https://www.emqx.io/), [Cassandana](https://github.com/mtsoleimani/cassandana), [RabbitMQ](https://www.rabbitmq.com/), [Ejjaberd](https://www.ejabberd.im/), [HiveMQ](https://www.hivemq.com/) to name a few) must be present in your network, and its address needs to be configured in the Config Portal. The broker can be specified either by domain or IP (IP preferred, spares us a DNS call). The default port is 1883. If a different port is to be used, append a ":" followed by the port number to the domain/IP, such as "192.168.1.5:1884". 326 | 327 | If your broker does not allow anonymous logins, a username and password can be specified. 328 | 329 | Limitations: MQTT Protocol version 3.1.1; TLS/SSL not supported; ".local" domains (MDNS) not supported; server/broker must respond to PING (ICMP) echo requests. For proper operation with low latency, it is recommended that the broker is on your local network. 330 | 331 | ## Car setup 332 | 333 | If your SID, along with a [Time Circuits Display](https://tcd.backtothefutu.re), is mounted in a car, the following network configuration is recommended: 334 | 335 | #### TCD 336 | 337 | - Run your TCD in [*car mode*](https://tcd.backtothefutu.re/#car-mode); 338 | - disable WiFi power-saving on the TCD by setting **_WiFi power save timer for AP-mode_** to 0 (zero). 339 | 340 | #### SID 341 | 342 | Enter the Config Portal on the SID (as described above), click on *Setup* and 343 | - enter *192.168.4.1* into the field **_IP address or hostname of TCD_** 344 | - check the option **_Follow TCD fake power_** if you have a fake power switch for the TCD (like eg a TFC switch) 345 | - click on *Save*. 346 | 347 | After the SID has restarted, re-enter the SID's Config Portal (while the TCD is powered and in *car mode*) and 348 | - click on *Configure WiFi*, 349 | - select the TCD's access point name in the list at the top or enter *TCD-AP* into the *SSID* field; if you password-protected your TCD's AP, enter this password in the *password* field. Leave all other fields empty, 350 | - click on *Save*. 351 | 352 | Using this setup enables the SID to receive notifications about time travel and alarm wirelessly, and to query the TCD for data. Also, the TCD keypad can be used to remote-control the SID. 353 | 354 | In order to access the SID's Config Portal in your car, connect your hand held or computer to the TCD's WiFi access point ("TCD-AP"), and direct your browser to http://sid.local ; if that does not work, go to the TCD's keypad menu, press ENTER until "BTTFN CLIENTS" is shown, hold ENTER, and look for the SID's IP address there; then direct your browser to that IP by using the URL http://a.b.c.d (a-d being the IP address displayed on the TCD display). 355 | 356 | ## Flash Wear 357 | 358 | Flash memory has a somewhat limited life-time. It can be written to only between 10.000 and 100.000 times before becoming unreliable. The firmware writes to the internal flash memory when saving settings and other data. Every time you change settings, data is written to flash memory. 359 | 360 | In order to reduce the number of write operations and thereby prolong the life of your SID, it is recommended to use a good-quality SD card and to check **_["Save secondary settings on SD"](#-save-secondary-settings-on-sd)_** in the Config Portal; some settings as well as learned IR codes are then stored on the SD card (which also suffers from wear but is easy to replace). If you want to swap the SD card but preserve your settings, go to the Config Portal while the old SD card is still in place, uncheck the **_Save secondary settings on SD_** option, click on Save and wait until the device has rebooted. You can then power down, swap the SD card and power-up again. Then go to the Config Portal, change the option back on and click on Save. Your settings are now on the new SD card. 361 | 362 | ## Appendix A: The Config Portal 363 | 364 | ### Main page 365 | 366 | ##### ▶ Configure WiFi 367 | 368 | Clicking this leads to the WiFi configuration page. On that page, you can connect your SID to your WiFi network by selecting/entering the SSID (WiFi network name) as well as a password (WPA2). By default, the SID requests an IP address via DHCP. However, you can also configure a static IP for the SID by entering the IP, netmask, gateway and DNS server. All four fields must be filled for a valid static IP configuration. If you want to stick to DHCP, leave those four fields empty. 369 | 370 | Note that this page has nothing to do with Access Point mode; it is strictly for connecting your SID to an existing WiFi network as a client. 371 | 372 | ##### ▶ Setup 373 | 374 | This leads to the [Setup page](#setup-page). 375 | 376 | ##### ▶ Restart 377 | 378 | This reboots the SID. No confirmation dialog is displayed. 379 | 380 | ##### ▶ Update 381 | 382 | This leads to the firmware update page. You can select a locally stored firmware image file to upload (such as the ones published here in the install/ folder). 383 | 384 | ##### ▶ Erase WiFi Config 385 | 386 | Clicking this (and saying "yes" in the confirmation dialog) erases the WiFi configuration (WiFi network and password) and reboots the device; it will restart in "access point" mode. See [here](#short-summary-of-first-steps). 387 | 388 | --- 389 | 390 | ### Setup page 391 | 392 | #### Basic settings 393 | 394 | ##### ▶ Screen saver timer 395 | 396 | Enter the number of minutes until the Screen Saver should become active when the SID is idle. 397 | 398 | The Screen Saver, when active, disables all LEDs, until 399 | - a key on the IR remote control is pressed; if IR is [locked](#locking-ir-control), only the # key deactivates the Screen Saver; 400 | - the time travel button is briefly pressed (the first press when the screen saver is active will not trigger a time travel), 401 | - on a connected TCD, a destination date is entered (only if TCD is wirelessly connected) or a time travel event is triggered (also when wired). 402 | 403 | #### Hardware configuration settings 404 | 405 | ##### ▶ Disable supplied IR remote control 406 | 407 | Check this to disable the supplied remote control; the SID will only accept commands from a learned IR remote (if applicable). 408 | 409 | Note that this only disables the supplied remote, unlike [IR locking](#locking-ir-control), where IR commands from any known remote are ignored. 410 | 411 | #### Network settings 412 | 413 | ##### ▶ Hostname 414 | 415 | The device's hostname in the WiFi network. Defaults to 'sid'. This also is the domain name at which the Config Portal is accessible from a browser in the same local network. The URL of the Config Portal then is http://hostname.local (the default is http://sid.local) 416 | 417 | If you have more than one SID in your local network, please give them unique hostnames. 418 | 419 | ##### ▶ AP Mode: Network name appendix 420 | 421 | By default, if the SID creates a WiFi network of its own ("AP-mode"), this network is named "SID-AP". In case you have multiple SIDs in your vicinity, you can have a string appended to create a unique network name. If you, for instance, enter "-ABC" here, the WiFi network name will be "SID-AP-ABC". Characters A-Z, a-z, 0-9 and - are allowed. 422 | 423 | ##### ▶ AP Mode: WiFi password 424 | 425 | By default, and if this field is empty, the SID's own WiFi network ("AP-mode") will be unprotected. If you want to protect your SID access point, enter your password here. It needs to be 8 characters in length and only characters A-Z, a-z, 0-9 and - are allowed. 426 | 427 | If you forget this password and are thereby locked out of your SID, enter *123456 followed by OK on the IR remote control; this deletes the WiFi password. Then power-down and power-up your SID and the access point will start unprotected. 428 | 429 | ##### ▶ WiFi connection attempts 430 | 431 | Number of times the firmware tries to reconnect to a WiFi network, before falling back to AP-mode. See [here](#short-summary-of-first-steps) 432 | 433 | ##### ▶ WiFi connection timeout 434 | 435 | Number of seconds before a timeout occurs when connecting to a WiFi network. When a timeout happens, another attempt is made (see immediately above), and if all attempts fail, the device falls back to AP-mode. See [here](#short-summary-of-first-steps) 436 | 437 | #### Settings for prop communication/synchronization 438 | 439 | ##### ▶ TCD connected by wire 440 | 441 | Check this if you have a Time Circuits Display connected by wire. You can only connect *either* a button *or* the TCD to the "time travel" connector on the SID, but not both. 442 | 443 | Note that a wired connection only allows for synchronized time travel sequences, no other communication takes place. 444 | 445 | Also note that the process of [learning keys from an IR remote control](#ir-remote-control) requires this option to be unchecked. After learning keys is done, you can, of course, check this option again. 446 | 447 | Do NOT check this option if your TCD is connected wirelessly (BTTFN, MQTT). 448 | 449 | ##### ▶ TCD signals Time Travel without 5s lead 450 | 451 | Usually, the TCD signals a time travel with a 5 seconds lead, in order to give a prop a chance to play an acceletation sequence before the actual time travel takes place. Since this 5 second lead is unique to CircuitSetup props, and people sometimes want to connect third party props to the TCD, the TCD has the option of skipping this 5 seconds lead. That that is the case, and your SID is connected by wire, you need to set this option. 452 | 453 | If your SID is connected wirelessly, this option has no effect. 454 | 455 | ##### ▶ IP address or hostname of TCD 456 | 457 | If you want to have your SID to communicate with a Time Circuits Display wirelessly ("BTTF-Network"), enter the IP address of the TCD here. Do NOT enter a host name here. If your TCD is running firmware version 2.9.1 or later, you can also enter the TCD's hostname here instead (eg. 'timecircuits'). 458 | 459 | If you connect your SID to the TCD's access point ("TCD-AP"), the TCD's IP address is 192.168.4.1. 460 | 461 | ##### ▶ Adapt to GPS speed 462 | 463 | If your TCD is equipped with a GPS sensor, the SID can adapt its display to current GPS speed. This option selects if GPS speed should be used. 464 | 465 | ##### ▶ Follow TCD night-mode 466 | 467 | If this option is checked, and your TCD goes into night mode, the SID will activate the Screen Saver with a very short timeout. 468 | 469 | ##### ▶ Follow TCD fake power 470 | 471 | If this option is checked, and your TCD is equipped with a fake power switch, the SID will also fake-power up/down. If fake power is off, no LED is active and the SID will ignore all input from buttons, knobs and the IR control. 472 | 473 | #### Visual options 474 | 475 | ##### ▶ Adhere strictly to movie patterns 476 | 477 | If this is set, in idle modes 0-3 as well as when using GPS speed, only patterns which were extracted from the movies (plus some interpolations) are shown. If this option is unset, random variations will be shown, which is less accurate, but also less monotonous. Purists will want this option to be set, which is also the default. This option can also be changed by typing *50 followed by OK on the IR remote control. 478 | 479 | ##### ▶ Skip time tunnel animation 480 | 481 | When set, the time travel sequence will not be animated (no flicker, no "moving bar"). Purists will want this option to be set; the default is unset. 482 | 483 | ##### ▶ Show peaks in Spectrum Analyzer 484 | 485 | This selects the boot-up setting for showing or not showing the peaks in the Spectrum Analyzer. Can be changed anytime by typing *51 followed by OK on the IR remote control. 486 | 487 | #### Home Assistant / MQTT settings 488 | 489 | ##### ▶ Use Home Assistant (MQTT 3.1.1) 490 | 491 | If checked, the SID will connect to the broker (if configured) and send and receive messages via [MQTT](#home-assistant--mqtt) 492 | 493 | ##### ▶ Broker IP[:port] or domain[:port] 494 | 495 | The broker server address. Can be a domain (eg. "myhome.me") or an IP address (eg "192.168.1.5"). The default port is 1883. If different port is to be used, it can be specified after the domain/IP and a colon ":", for example: "192.168.1.5:1884". Specifiying the IP address is preferred over a domain since the DNS call adds to the network overhead. Note that ".local" (MDNS) domains are not supported. 496 | 497 | ##### ▶ User[:Password] 498 | 499 | The username (and optionally the password) to be used when connecting to the broker. Can be left empty if the broker accepts anonymous logins. 500 | 501 | #### Other settings 502 | 503 | ##### ▶ Save secondary settings on SD 504 | 505 | If this is checked, secondary settings (brightness, IR lock status, learned IR keys) are stored on the SD card (if one is present). This helps to minimize write operations to the internal flash memory and to prolong the lifetime of your SID. See [Flash Wear](#flash-wear). 506 | 507 | 508 | --------------------------------------------------------------------------------