├── .gitignore ├── todo.md ├── images └── circuitboard.jpg ├── include ├── Example.Secret.h ├── LogitechIRCodes.h └── DebugHelpers.hpp ├── logitech_ir.md ├── platformio.ini ├── lib └── readme.txt ├── .travis.yml ├── README.md └── src └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | **/*.vscode 3 | include/Secret.h 4 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | 1. Add blink when recieving requests -------------------------------------------------------------------------------- /images/circuitboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albznw/Logitech-Z906-WiFi/HEAD/images/circuitboard.jpg -------------------------------------------------------------------------------- /include/Example.Secret.h: -------------------------------------------------------------------------------- 1 | const char* ssid = ".........."; 2 | const char* password = ".........."; 3 | 4 | /****************************** MQTT - Settings *******************************/ 5 | #define Broker "192.168.1.16" 6 | #define Port 1883 7 | 8 | #define MQTTClientId "logitech_z906" 9 | #define MQTTUsername "mqtt_user" 10 | #define MQTTPassword "mqtt_password" 11 | -------------------------------------------------------------------------------- /include/LogitechIRCodes.h: -------------------------------------------------------------------------------- 1 | #define POWER_IR 0x400501FE 2 | #define INPUT_IR 0x400510EF 3 | #define MUTE_IR 0x400557A8 4 | #define LEVEL_IR 0x400550AF 5 | #define PLUS_IR 0x400555AA 6 | #define EFFECT_IR 0x4005708F 7 | #define MINUS_IR 0x400556A9 8 | #define INPUT1_IR 0x400520DF 9 | #define INPUT2_IR 0x400541BE 10 | #define INPUT3_IR 0x400530CF 11 | #define INPUT4_IR 0x400531CE 12 | #define INPUT5_IR 0x400540BF 13 | #define AUX_IR 0x400542BD 14 | #define TEST_IR 0x4005807F -------------------------------------------------------------------------------- /logitech_ir.md: -------------------------------------------------------------------------------- 1 | Protocol: NEC1 2 | Device: 2 3 | SubDevice: 160 4 | 5 | Func. OBC HEX EFC 6 | Power: 128 FE 189 7 | Input: 8 EF 053 8 | Mute: 234 A8 079 9 | Level: 10 AF 055 10 | Plus: 170 AA 095 11 | Effect:14 8F 054 12 | Minus: 106 A9 071 13 | 14 | Input1 4 DF 180 15 | Input2 130 BE 191 16 | Input3 12 CF 052 17 | Input4 140 CE 060 18 | Input5 2 BF 183 19 | AUX 66 BD 167 20 | TEST 1 7F 185 21 | 22 | 23 | 24 | 25 | 26 | Upgrade Code 0 = 34 57 (Amp/1111) Logitech Speaker System Z906 (RM v2.02 Beta) 27 | 5A 00 F5 20 BF FA 7F DF BE CF CE BF BD 00 00 00 28 | AA A9 A8 AF 8F FE EF 29 | End -------------------------------------------------------------------------------- /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 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | ; [env:d1_mini] 12 | ; platform = espressif8266 13 | ; board = d1_mini 14 | ; framework = arduino 15 | ; upload_port = 192.168.1.36 16 | ; build_flags = -DMQTT_MAX_PACKET_SIZE=192 17 | 18 | [env:esp12e] 19 | platform = espressif8266 20 | board = esp12e 21 | framework = arduino 22 | build_flags = -Wl,-Teagle.flash.2m.ld -DMQTT_MAX_PACKET_SIZE=192 23 | upload_port = 192.168.1.73 24 | upload_protocol = espota 25 | 26 | -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) http://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- readme.txt --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | Then in `src/main.c` you should use: 31 | 32 | #include 33 | #include 34 | 35 | // rest H/C/CPP code 36 | 37 | PlatformIO will find your libraries automatically, configure preprocessor's 38 | include paths and build them. 39 | 40 | More information about PlatformIO Library Dependency Finder 41 | - http://docs.platformio.org/page/librarymanager/ldf.html 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to by used as a library with examples 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /include/DebugHelpers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H_ 2 | #define DEBUG_H_ 3 | 4 | #include 5 | 6 | // Define debug and log port 7 | #define DEBUG_PORT 8 | 9 | // Define wanted debug levels 10 | #define LOG_ENABLED 11 | #define ERRORS_ENABLED 12 | // #define DEBUG_ENABLED 13 | 14 | #ifdef DEBUG_PORT 15 | #define DebugInit(baud) Serial.begin(baud) 16 | #define LogInit(baud) Serial.begin(baud) 17 | #else 18 | #define DebugInit(baud) 19 | #define LogInit(baud) 20 | #endif 21 | 22 | /******************** Debug levels ********************/ 23 | #ifdef LOG_ENABLED 24 | #define Log(...) Serial.printf( __VA_ARGS__ ) 25 | #define LogFunc(...) do { Serial.print("["); Serial.print(__PRETTY_FUNCTION__); Serial.print("] "); Serial.printf(__VA_ARGS__); } while (0) 26 | #define Logln(...) Serial.println( __VA_ARGS__ ) 27 | #else 28 | #define Log(...) 29 | #define LogFunc(...) 30 | #define Logln(...) 31 | #endif 32 | 33 | #ifdef ERRORS_ENABLED 34 | #define Err(...) Serial.printf( __VA_ARGS__ ) 35 | #define ErrFunc(...) do { Serial.print("["); Serial.print(__PRETTY_FUNCTION__); Serial.print("] "); Serial.printf(__VA_ARGS__); } while (0) 36 | #define Errln(...) Serial.println( __VA_ARGS__ ) 37 | #else 38 | #define Err(...) 39 | #define ErrFunc(...) 40 | #define Errln(...) 41 | #endif 42 | 43 | #ifdef DEBUG_ENABLED 44 | #define Debugf(...) Serial.printf( __VA_ARGS__ ) 45 | #else 46 | #define Debugf(...) 47 | #endif 48 | 49 | void chipInformation() { 50 | Serial.println(); 51 | Serial.print( F("Heap: ") ); Serial.println(system_get_free_heap_size()); 52 | Serial.print( F("Boot Vers: ") ); Serial.println(system_get_boot_version()); 53 | Serial.print( F("CPU: ") ); Serial.println(system_get_cpu_freq()); 54 | Serial.print( F("SDK: ") ); Serial.println(system_get_sdk_version()); 55 | Serial.print( F("Chip ID: ") ); Serial.println(system_get_chip_id()); 56 | Serial.print( F("Flash ID: ") ); Serial.println(spi_flash_get_id()); 57 | Serial.print( F("Flash Size: ") ); Serial.println(ESP.getFlashChipRealSize()); 58 | Serial.print( F("Vcc: ") ); Serial.println(ESP.getVcc()); 59 | Serial.println(); 60 | } 61 | 62 | #endif // DEBUG_H_ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logitech Zmart906 2 | Breathe new life into your Logitech Z906 Surround system with this ESP8266-hack 3 | . 4 | 5 | ![Picture of the ESP installed on the controller](./images/circuitboard.jpg) 6 | 7 | ### Version 2.0 8 | I've worked on version 2.0 of this project which intercepts the UART traffic between the controller and the subwoofer (that actually controls the sound and input settings). This makes the hack much more reliable as the ESP does not have to track the state of the sounds system. 9 | 10 | As soon as I have some spare time I'll upload the next version complete with instructions on how to install, set it up, and use it. Since version 2.0 is coming, I'm not going to write an in-depth guide on how to install, set up, and use this version. However, the source code is here if you would like to look at it and set it up yourself. 11 | 12 | # Installation 13 | **DISCLAIMER: You are doing this at your own risk. I will not take responsibility if you break your system!** 14 | As stated above, since version 2.0 is on its way I will not write a more thorough installation guide than this. However, feel free to contact me if you need any help. 15 | 16 | Note: You don't actually have to open up the controller to use this hack. You could build the ESP8266 system and point the IR LED towards the IR receiver on the outside of the Logitech controller. 17 | 18 | ## Prerequisites 19 | 20 | * Knowledge about Ardunio, the ESP8266 chip, and platformio 21 | * Hardware: soldering iron 22 | * The guts to pry open the case and solder extra components onto the circuit board 23 | 24 | ## Partlist 25 | - ESP8266 26 | - IR LED 27 | - An appropriate voltage source (5v -> 3.3v) 28 | 29 | The easiest way to flash the ESP8266 with the firmware is with platformio. 30 | The controller board does not have enough oomph to power both the controller board and the ESP8266. Thus you will have to route an extra cable to the controller to deliver power to the ESP8266. 31 | 32 | The IR LED should be pointed to the IR receiver inside the Logitech controller box. 33 | 34 | # Usage 35 | The ESP8266 can control the sound system through a REST API or through MQTT. In both cases, the payload is a json document. Have a look at the source code to see what the json document should look like. 36 | 37 | Depending on the json payload you can: 38 | - Turn on/off the system 39 | - Change sound levels (main levels, bass level, and tweeter levels) 40 | - Change the sound input 41 | - Change sound mode (Surround, Stereo, and Music) 42 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | _ _ _ _ ________ ___ __ 3 | | | ___ __ _(_) |_ ___ ___| |__ |__ / _ \ / _ \ / /_ 4 | | | / _ \ / _` | | __/ _ \/ __| '_ \ / / (_) | | | | '_ \ 5 | | |__| (_) | (_| | | || __/ (__| | | | / /_\__, | |_| | (_) | 6 | |_____\___/ \__, |_|\__\___|\___|_| |_| /____| /_/ \___/ \___/ 7 | __ __|___/__ _ _ ____ ___ 8 | \ \ / (_) ___(_) / \ | _ \_ _| 9 | \ \ /\ / /| | |_ | | / _ \ | |_) | | 10 | \ V V / | | _| | | / ___ \| __/| | 11 | \_/\_/ |_|_| |_| /_/ \_\_| |___| 12 | 13 | A Wifi API for the Logitech Z906 Speakers 14 | by Albin Winkelmann 15 | 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "DebugHelpers.hpp" 36 | #include "Secret.h" 37 | 38 | #define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) 39 | 40 | /**************************** General - Settings ******************************/ 41 | #define HOSTNAME "Logitech-Z906" // Comment out empty for esp8266-[ChipID] 42 | 43 | #define STATUS_LED D7 // Status led pin 44 | #define ON_LED D0 // The pin that is connected to the on-led on speaker system 45 | #define IR_LED D2 // The IR LED pin 46 | #define RECV_IR D1 // The ir reciever pin 47 | #define MS_BETWEEN_SENDING_IR 20 // Amount of ms to leap between sending commands in a row 48 | #include "LogitechIRCodes.h" 49 | 50 | bool OTA_ON = true; // Turn on OTA 51 | 52 | /****************************** MQTT - Settings *******************************/ 53 | // Connection things is found in Secret.h 54 | #define MQTTClientId "logitech_z906" 55 | #define MQTTCategory "speaker" 56 | 57 | #define ClientRoot MQTTCategory "/" MQTTClientId 58 | 59 | // Some examples on how the routes should be 60 | #define CommandTopic ClientRoot "/cmnd/json" 61 | #define StateTopic ClientRoot "/state/json" 62 | #define DebugTopic ClientRoot "/debug" 63 | #define WillTopic ClientRoot "/will" 64 | #define WillQoS 0 65 | #define WillRetain false 66 | const char* willMessage = MQTTClientId " has disconnected..."; 67 | 68 | #define FirstMessage "I communicate via JSON!" 69 | #define MQTT_MAX_PACKET_SIZE 192 //Remember to set this in platformio.ini 70 | 71 | WiFiClient wificlient; // is needed for the mqtt client 72 | PubSubClient mqttclient; 73 | 74 | ESP8266WebServer server(80); 75 | 76 | #define CAPTURE_BUFFER_SIZE 64 77 | #define MIN_UNKNOWN_SIZE 12 78 | IRrecv irrecv(RECV_IR, CAPTURE_BUFFER_SIZE); 79 | IRsend irsend(IR_LED); 80 | decode_results results; // Somewhere to store the results 81 | 82 | /********************************* Variables **********************************/ 83 | 84 | int8_t soundLevel[4]; // [Volume, Bass, Rear, Center] 85 | 86 | bool mute; 87 | bool isOn = false; 88 | 89 | enum Input : byte { AUX, Input1, Input2, Input3, Input4, Input5 }; 90 | const char* inputs[] { "AUX", "Input 1", "Input 2", "Input 3", "Input 4", "Input 5" }; 91 | Input currentInput; 92 | 93 | enum Effect : byte { Surround, Music, Stereo }; 94 | const char* effects[] { "Surround", "Music", "Stereo" }; 95 | Effect currentEffectOnInput[5]; 96 | 97 | // In mode On we'll change the soundlevel (defult) 98 | enum Mode : byte { Off, On, BassLevel, RearLevel, CenterLevel}; 99 | const char* modes[] = { "Off", "On", "Bass level", "Rear level", "Center level" }; 100 | Mode currentMode = Off; 101 | Mode lastMode = On; 102 | 103 | #define LEVEL_TIMEOUT 5000 104 | unsigned long levelTimeout; 105 | 106 | /* EEPROM Addresses */ 107 | #define SOUND_LEVEL_ADDR 1 108 | #define BASS_LEVEL_ADDR 2 109 | #define REAR_LEVEL_ADDR 3 110 | #define CENTER_LEVEL_ADDR 4 111 | #define CURRENT_INPUT_ADDR 5 112 | #define EFFECT_ON_AUX 6 113 | #define EFFECT_ON_INPUT1 7 114 | #define EFFECT_ON_INPUT2 8 115 | #define EFFECT_ON_INPUT3 9 116 | #define EFFECT_ON_INPUT4 10 117 | #define EFFECT_ON_INPUT5 11 118 | #define MUTE_ADDR 12 119 | 120 | /*********************************** Tasks ************************************/ 121 | // Declare task methods 122 | void checkIfStillOn(); 123 | void checkWifiStatusCallback(); 124 | void checkMQTTStatusCallback(); 125 | void blinkStatusLedCallback(); 126 | void blinkStatusLedDisabledCallback(); 127 | void sendStatesMQTT(); 128 | 129 | Scheduler taskManager; 130 | Task tCheckIfStillOn(TASK_SECOND, TASK_FOREVER, &checkIfStillOn, &taskManager); 131 | Task tCheckMQTTStatus(TASK_MINUTE, TASK_FOREVER, &checkMQTTStatusCallback, &taskManager); 132 | Task tWifiStatus(TASK_SECOND, TASK_FOREVER, &checkWifiStatusCallback, &taskManager); 133 | Task tBlink(200, 3, &blinkStatusLedCallback, &taskManager, false, NULL, &blinkStatusLedDisabledCallback); 134 | Task tSendStatesMQTT(TASK_MINUTE, TASK_FOREVER, &sendStatesMQTT, &taskManager); 135 | 136 | /** Returns the soundlevel that the receiver is currently on */ 137 | uint8_t currentLevel() { 138 | return currentMode - 1; 139 | } 140 | 141 | /** 142 | * Returns the current effect for the current input 143 | * (Each input has it's on effect independent of the other inputs) 144 | */ 145 | Effect currentEffect() { 146 | return currentEffectOnInput[currentInput]; 147 | } 148 | 149 | void setCurrentEffect(Effect effect) { 150 | currentEffectOnInput[currentInput] = effect; 151 | } 152 | 153 | void loadSettings() { 154 | for(int8_t i = 0; i < 4; i++) { 155 | soundLevel[i] = EEPROM.read(SOUND_LEVEL_ADDR + i); 156 | if(soundLevel[i] > 128 || soundLevel[i] < 0) { 157 | soundLevel[i] = 0; 158 | } 159 | } 160 | 161 | currentInput = (Input)EEPROM.read(CURRENT_INPUT_ADDR); 162 | currentInput = currentInput < 6 ? currentInput : AUX; 163 | 164 | for(uint8_t i= 0; i < 6; i++) { 165 | currentEffectOnInput[i] = (Effect)EEPROM.read(EFFECT_ON_AUX + i); 166 | currentEffectOnInput[i] = currentEffectOnInput[i] < 3 ? currentEffectOnInput[i] : Surround; 167 | } 168 | 169 | mute = (bool)EEPROM.read(MUTE_ADDR); 170 | } 171 | 172 | void saveSettings() { 173 | for(uint8_t i = 0; i < 4; i++) { 174 | EEPROM.write(SOUND_LEVEL_ADDR + i, soundLevel[i]); 175 | } 176 | EEPROM.write(CURRENT_INPUT_ADDR, currentInput); 177 | for(uint8_t i = 0; i < 6; i++) { 178 | EEPROM.write(EFFECT_ON_AUX + i, currentEffectOnInput[i]); 179 | } 180 | EEPROM.write(MUTE_ADDR, mute); 181 | if(EEPROM.commit()) 182 | Logln("Successfully saved to EEPROM"); 183 | else 184 | Logln("Failed to save to EEPROM"); 185 | } 186 | 187 | void getSettings(JsonObject json) { 188 | JsonObject settings = json.createNestedObject("settings"); 189 | settings["mode"] = modes[currentMode]; 190 | if(mute) 191 | settings["soundlevel"] = "mute"; 192 | else { 193 | settings["soundlevel"] = soundLevel[0]; 194 | settings["basslevel"] = soundLevel[1]; 195 | settings["rearlevel"] = soundLevel[2]; 196 | settings["centerlevel"] = soundLevel[3]; 197 | } 198 | settings["input"] = inputs[currentInput]; 199 | settings["effect"] = effects[currentEffect()]; 200 | } 201 | 202 | void sendIR(uint64_t data, uint16_t repeat) { 203 | irrecv.disableIRIn(); 204 | irsend.sendNEC(data, 32, repeat); 205 | irrecv.enableIRIn(); 206 | irrecv.resume(); 207 | } 208 | 209 | void sendIR(uint64_t data) { 210 | irrecv.disableIRIn(); 211 | irsend.sendNEC(data, 32); 212 | irrecv.enableIRIn(); 213 | irrecv.resume(); 214 | } 215 | 216 | void turnOn() { 217 | if(currentMode == Off) { 218 | sendIR(POWER_IR); 219 | currentMode = On; 220 | saveSettings(); 221 | } 222 | checkIfStillOn(); 223 | } 224 | 225 | void turnOff() { 226 | if(currentMode != Off) { 227 | sendIR(POWER_IR); 228 | currentMode = Off; 229 | saveSettings(); 230 | } 231 | checkIfStillOn(); 232 | } 233 | 234 | void togglePower() { 235 | sendIR(POWER_IR); 236 | currentMode = currentMode == On ? Off : On; 237 | saveSettings(); 238 | } 239 | 240 | /** Sets the current input to the next input, but does not save or send 241 | * anything */ 242 | void setNextInput() { 243 | currentInput = currentInput >= 5 ? (Input)0 : (Input)(currentInput + 1); 244 | } 245 | 246 | /** Sets the current input to the next input, but does not save or send anything 247 | * anything */ 248 | void setNextEffect() { 249 | Effect effect = currentEffect() >= 2 ? (Effect)0 : (Effect)(currentEffect() + 1); 250 | setCurrentEffect(effect); 251 | } 252 | 253 | void toggleInput() { 254 | sendIR(INPUT_IR); 255 | setNextInput(); 256 | saveSettings(); 257 | } 258 | 259 | void toggleMute() { 260 | sendIR(MUTE_IR); 261 | mute = !mute; 262 | saveSettings(); 263 | } 264 | 265 | /** Sends ir code and saves settings to change to wanted input */ 266 | void changeInput(Input input) { 267 | Log("[changeInput] Changing input to: %s\n", inputs[input]); 268 | switch(input) { 269 | case AUX: 270 | sendIR(AUX_IR); 271 | break; 272 | case Input1: 273 | sendIR(INPUT1_IR); 274 | break; 275 | case Input2: 276 | sendIR(INPUT2_IR); 277 | break; 278 | case Input3: 279 | sendIR(INPUT3_IR); 280 | break; 281 | case Input4: 282 | sendIR(INPUT4_IR); 283 | break; 284 | case Input5: 285 | sendIR(INPUT5_IR); 286 | break; 287 | default: 288 | Logln("\tNo such input!"); 289 | } 290 | Logln(""); 291 | currentInput = input; 292 | saveSettings(); 293 | } 294 | 295 | /** Sends ir code and saves settings to change to wanted effect */ 296 | void changeEffect(Effect effect) { 297 | Log("[changeEffect] Changing effect from: %s to: %s\n", effects[currentEffect()], effects[effect]); 298 | int8_t diff = effect - currentEffectOnInput[currentInput]; 299 | int8_t ir_send_times = (diff >= 0) ? diff : (abs(diff) + 1) % 3; 300 | Log("[changeEffect] Diff: %d\t\tBlasting ir %d times\n", diff, ir_send_times); 301 | 302 | for(uint8_t i = 0; i < ir_send_times; i++) { 303 | sendIR(EFFECT_IR); 304 | delay(MS_BETWEEN_SENDING_IR); 305 | } 306 | 307 | currentEffectOnInput[currentInput] = effect; 308 | saveSettings(); 309 | } 310 | 311 | /** Sends ir code and saves settings to change to wanted sound level */ 312 | void changeSoundLevel(int8_t level) { 313 | if(level < 0) 314 | level = 0; 315 | int8_t diff = level - soundLevel[currentLevel()]; 316 | Log("[changeSoundLevel] Setting sound level %d -> %d\tDiff: %d\n", soundLevel[currentLevel()], level, diff); 317 | if(diff > 1) { 318 | sendIR(PLUS_IR, diff); 319 | } else if(diff < 0) { 320 | sendIR(MINUS_IR, -diff); 321 | } 322 | soundLevel[currentLevel()] = level; 323 | saveSettings(); 324 | } 325 | 326 | /** Sends ir code and saves settings to change to wanted mode (Level) */ 327 | void changeMode(Mode mode) { 328 | Log("[changeMode] Changing mode from: %s to: %s\n", modes[currentMode], modes[mode]); 329 | int8_t diff = (mode - 1) - (currentMode - 1); 330 | int8_t ir_send_times = (diff >= 0) ? diff : (abs(diff) + 1) % 4; 331 | Log("[changeMode] Diff: %d\t\tBlasting ir %d times\n", diff, ir_send_times); 332 | 333 | for(uint8_t i = 0; i < ir_send_times; i++) { 334 | sendIR(LEVEL_IR); 335 | delay(MS_BETWEEN_SENDING_IR); 336 | } 337 | 338 | currentMode = mode; 339 | saveSettings(); 340 | } 341 | 342 | /** Checks if speaker system is still on */ 343 | void checkIfStillOn() { 344 | bool lastBool = isOn; 345 | isOn = digitalRead(ON_LED); 346 | if(isOn) { 347 | currentMode = currentMode == Off ? On : currentMode; 348 | } else { 349 | currentMode = Off; 350 | } 351 | 352 | if(lastBool != isOn) { 353 | sendStatesMQTT(); 354 | } 355 | Debugf("[checkIfStillON] %s\n", isOn ? "On" : "Off"); 356 | } 357 | 358 | /** Returns the strings index in const char[] array*/ 359 | uint8_t getStringIndex(String s, const char* array[], uint8_t len) { 360 | Log("[getStringIndex] Length of array: %d\n", len); 361 | for(uint8_t i = 0; i < len; i++) { 362 | if(s == array[i]) return i; 363 | } 364 | return NULL; 365 | } 366 | 367 | void nextLevelOnCurrentEffect() { 368 | int limit = 4; 369 | switch(currentEffect()) { 370 | case Surround: 371 | limit = 4; 372 | break; 373 | case Music: 374 | limit = 3; 375 | break; 376 | case Stereo: 377 | limit = 2; 378 | break; 379 | } 380 | currentMode = currentMode >= limit ? (Mode)1 : (Mode)(currentMode + 1); 381 | } 382 | 383 | void printSettings() { 384 | Serial.printf("\nInput: %s\n", inputs[currentInput]); 385 | Serial.printf("Soundlevel: %d\t", soundLevel[0]); 386 | Serial.printf("Bass: %d\t", soundLevel[1]); 387 | Serial.printf("Rear: %d\n", soundLevel[2]); 388 | Serial.printf("Center: %d\n", soundLevel[3]); 389 | Serial.printf("Effect: %s\n\n", effects[currentEffect()]); 390 | } 391 | 392 | void blinkStatusLed(int8_t times, unsigned long interval, TaskOnEnable onEnable, TaskOnDisable onDisable) { 393 | Serial.println("[blinkStatusLed] Called"); 394 | tBlink.setIterations(times); 395 | tBlink.setInterval(interval); 396 | tBlink.setOnEnable(onEnable); 397 | tBlink.setOnDisable(onDisable); 398 | taskManager.addTask(tBlink); 399 | tBlink.enable(); 400 | } 401 | 402 | void blinkStatusLed(int8_t times, unsigned long interval) { 403 | blinkStatusLed(times, interval, NULL, NULL); 404 | } 405 | 406 | void blinkStatusLedCallback() { 407 | digitalWrite(STATUS_LED, !digitalRead(STATUS_LED)); 408 | } 409 | 410 | void blinkStatusLedDisabledCallback() { 411 | digitalWrite(STATUS_LED, LOW); 412 | taskManager.deleteTask(tBlink); 413 | } 414 | 415 | void setupOTA() { 416 | Logln("[OTA] Initializing..."); 417 | // Port defaults to 8266 418 | // ArduinoOTA.setPort(8266); 419 | 420 | // Hostname defaults to esp8266-[ChipID] 421 | #ifdef HOSTNAME 422 | ArduinoOTA.setHostname(HOSTNAME); 423 | #endif 424 | 425 | // No authentication by default 426 | // ArduinoOTA.setPassword("admin"); 427 | 428 | // Password can be set with it's md5 value as well 429 | // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 430 | // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); 431 | 432 | ArduinoOTA.onStart([]() { 433 | String type; 434 | if (ArduinoOTA.getCommand() == U_FLASH) { 435 | type = "sketch"; 436 | } else { // U_SPIFFS 437 | type = "filesystem"; 438 | } 439 | 440 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 441 | Serial.println("Start updating " + type); 442 | }); 443 | ArduinoOTA.onEnd([]() { 444 | digitalWrite(STATUS_LED, HIGH); 445 | Serial.println("\nEnd"); 446 | }); 447 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 448 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 449 | if(progress % 5 == 0) 450 | digitalWrite(STATUS_LED, !digitalRead(STATUS_LED)); 451 | }); 452 | ArduinoOTA.onError([](ota_error_t error) { 453 | Serial.printf("Error[%u]: ", error); 454 | if (error == OTA_AUTH_ERROR) { 455 | Serial.println("Auth Failed"); 456 | } else if (error == OTA_BEGIN_ERROR) { 457 | Serial.println("Begin Failed"); 458 | } else if (error == OTA_CONNECT_ERROR) { 459 | Serial.println("Connect Failed"); 460 | } else if (error == OTA_RECEIVE_ERROR) { 461 | Serial.println("Receive Failed"); 462 | } else if (error == OTA_END_ERROR) { 463 | Serial.println("End Failed"); 464 | } 465 | }); 466 | ArduinoOTA.begin(); 467 | Serial.println("[OTA] Done."); 468 | } 469 | 470 | void setupWifiManager() { 471 | // WiFiManager 472 | // Local intialization. Once its business is done, there is no need to keep it around 473 | WiFiManager wifiManager; 474 | // reset saved settings 475 | // wifiManager.resetSettings(); 476 | 477 | wifiManager.setConfigPortalTimeout(180); 478 | 479 | // set custom ip for portal 480 | // wifiManager.setAPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0)); 481 | 482 | // fetches ssid and pass from eeprom and tries to connect 483 | // if it does not connect it starts an access point with the specified name 484 | // and goes into a blocking loop awaiting configuration 485 | #ifdef HOSTNAME 486 | wifiManager.autoConnect(HOSTNAME); 487 | WiFi.hostname(HOSTNAME); 488 | #else 489 | // use this for auto generated name ESP + ChipID 490 | wifiManager.autoConnect(); 491 | #endif 492 | 493 | if(WiFi.isConnected()) { 494 | Serial.print("WiFi Connected: "); 495 | Serial.println(WiFi.localIP()); 496 | } else { 497 | ESP.restart(); 498 | } 499 | } 500 | 501 | void setupIR() { 502 | Logln("[IRSend] Begin"); 503 | irsend.begin(); 504 | irrecv.setUnknownThreshold(MIN_UNKNOWN_SIZE); 505 | irrecv.enableIRIn(); 506 | } 507 | 508 | void setupEEPROM() { 509 | EEPROM.begin(512); 510 | mute = false; // Reset mute in memory 511 | EEPROM.write(MUTE_ADDR, mute); 512 | EEPROM.commit(); 513 | loadSettings(); 514 | } 515 | 516 | bool publishMQTT(const char* topic, const char* payload){ 517 | String printString = ""; 518 | bool returnBool = false; 519 | if(mqttclient.publish(topic, payload)) { 520 | returnBool = true; 521 | printString = String("[publishMQTT] '" + String(payload) + "' was sent sucessfully to: "); 522 | } else { 523 | returnBool = false; 524 | printString = String("[publishMQTT] ERROR sending: '" + String(payload) + "' to: "); 525 | } 526 | printString += topic; 527 | Logln(printString); 528 | return returnBool; 529 | } 530 | 531 | /** Connects to the MQTT broker and subscribes to the topic */ 532 | bool connectMQTT() { 533 | while (!mqttclient.connected()) { 534 | Log("[MQTT] Connecting to MQTT server... "); 535 | 536 | //if connected, subscribe to the topic(s) we want to be notified about 537 | if (mqttclient.connect(MQTTClientId, MQTTUsername, MQTTPassword, WillTopic,\ 538 | WillQoS, WillRetain, willMessage)) { 539 | Logln("MTQQ Connected!"); 540 | if (mqttclient.subscribe(CommandTopic)) 541 | Log("[MQTT] Sucessfully subscribed to %s\n", CommandTopic); 542 | publishMQTT(DebugTopic, FirstMessage); 543 | return true; 544 | } 545 | } 546 | Logln("Failed to connect to MQTT Server"); 547 | return false; 548 | } 549 | 550 | bool publishMQTT(const char* topic, String payload){ 551 | return publishMQTT(topic, payload.c_str()); 552 | } 553 | 554 | void checkMQTTStatusCallback() { 555 | Log("Checking MQTT connection.."); 556 | if(!mqttclient.connected()) { 557 | Log("[checkMQTTStatusCallback] Reconnecting to MQTT server...\n"); 558 | connectMQTT(); 559 | } 560 | } 561 | 562 | String payloadToString(byte* payload, int length) { 563 | char message_buff[length]; 564 | int i = 0; 565 | for (i = 0; i < length; i++) { 566 | message_buff[i] = payload[i]; 567 | } 568 | message_buff[i] = '\0'; 569 | return String(message_buff); 570 | } 571 | 572 | /** For handling requests, both the MQTT and REST requests are parsed here 573 | * Returns: response string (with settings formatted as json) 574 | */ 575 | String handleJSONReq(String req) { 576 | StaticJsonDocument<256> reqDoc; 577 | auto error = deserializeJson(reqDoc, req); 578 | 579 | if (error) { 580 | Err("deserializeJson() failed with code "); 581 | Err(error.c_str()); 582 | return ""; 583 | } 584 | 585 | Serial.print("[handleJSON] Payload: "); 586 | serializeJson(reqDoc, Serial); 587 | Serial.println(""); 588 | 589 | const size_t bufferSize = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(7); 590 | DynamicJsonDocument resDoc(bufferSize); 591 | JsonObject json = resDoc.to(); 592 | 593 | String response = ""; 594 | String method = reqDoc["method"]; 595 | 596 | if(method == "turnOn") { 597 | Logln("[handleJSON] Calling turnOn"); 598 | turnOn(); 599 | } else if(method == "turnOff") { 600 | Logln("[handleJSON] Calling turnOff"); 601 | turnOff(); 602 | } 603 | 604 | // Getters 605 | else if(method == "getSettings") { 606 | Logln("[handleJSON] Calling getSettings"); 607 | getSettings(json); 608 | } else if(method == "getMode") { 609 | Logln("[handleJSON] Calling getMode"); 610 | Logln(modes[currentMode]); 611 | json["mode"] = modes[currentMode]; 612 | } else if(method == "getSoundLevel") { 613 | Logln("[handleJSON] Calling getSoundLevel"); 614 | json["soundlevel"] = soundLevel; 615 | } else if(method == "getInput") { 616 | Logln("[handleJSON] Calling getInput"); 617 | json["input"] = inputs[currentInput]; 618 | } else if(method == "getEffect") { 619 | Logln("[handleJSON] Calling getEffect"); 620 | json["effect"] = effects[currentEffect()]; 621 | } 622 | 623 | // Setters 624 | else if(method == "setSettings") { 625 | if(currentMode != On) { 626 | /* Turn on the speakers if they're not yet on 627 | (THIS COULD CAUSE PROBLEMS IF YOU'RE STUPID AS ME AND FORGETS ABOUT THIS)*/ 628 | turnOn(); 629 | // If the speakers was Off or in Level mode we have to wait until 630 | // it's for sure is in On mode 631 | delay(3500); // change dis later to a more appropriate value 632 | } 633 | 634 | bool somethingChanged = false; 635 | 636 | const char* input = reqDoc["input"]; 637 | if(input) { 638 | Logln("[setSettings] Input setting detected"); 639 | changeInput((Input)getStringIndex(input, inputs, ARRAY_SIZE(inputs))); 640 | somethingChanged = true; 641 | } 642 | 643 | const char* effect = reqDoc["effect"]; 644 | if(effect) { 645 | Logln("[setSettings] Effect setting detected"); 646 | changeEffect((Effect)getStringIndex(effect, effects, ARRAY_SIZE(effects))); 647 | somethingChanged = true; 648 | } 649 | 650 | const char* modeStr = reqDoc["mode"]; 651 | if(modeStr) { 652 | Logln("[setSettings] Mode setting detected"); 653 | changeMode((Mode)getStringIndex(modeStr, modes, ARRAY_SIZE(modes))); 654 | somethingChanged = true; 655 | } 656 | 657 | int soundlevel = reqDoc["soundlevel"]; 658 | if(soundlevel) { 659 | Logln("[setSettings] Soundlevel setting detected"); 660 | changeSoundLevel(soundlevel); 661 | somethingChanged = true; 662 | } 663 | 664 | if(somethingChanged) { 665 | getSettings(json); 666 | } else { 667 | json["message"] = "You didn't specify input, effect or soundlevel"; 668 | } 669 | } 670 | 671 | // reset 672 | else if(method = "reset") { 673 | blinkStatusLed(2, 300); 674 | soundLevel[0] = 10; 675 | soundLevel[1] = 25; 676 | soundLevel[2] = 25; 677 | soundLevel[3] = 25; 678 | currentInput = Input1; 679 | for(uint8_t i= 0; i < 5; i++) { 680 | currentEffectOnInput[i] = Surround; 681 | } 682 | saveSettings(); 683 | json["message"] = "Settings resetted"; 684 | getSettings(json); 685 | } 686 | 687 | // just else 688 | else { 689 | String error = "Method: \"" + String(method) + "\" does not exist"; 690 | Logln(error.c_str()); 691 | publishMQTT(DebugTopic, error); 692 | } 693 | 694 | serializeJson(resDoc, response); 695 | Log("[handleJSON] Response: %s\n", response.c_str()); 696 | return response; 697 | } 698 | 699 | void setupWebServer() { 700 | Logln("[Webserver] Initializing..."); 701 | server.on("/", HTTP_GET, [](){ 702 | server.send(200, "text/plain", "It works!"); 703 | }); 704 | 705 | server.on("/", HTTP_POST, [](){ 706 | // Print message 707 | Logln("\nPOST \"\\\": "); 708 | server.send(200, "application/json", \ 709 | handleJSONReq(server.arg("plain"))); 710 | }); 711 | 712 | // Start webserver 713 | server.begin(); 714 | Logln("[Webserver] Done."); 715 | } 716 | 717 | void mqttCallback(char* topic, byte* payload, int length) { 718 | //convert topic to string to make it easier to work with 719 | String topicStr = topic; 720 | String payloadStr = payloadToString(payload, length); 721 | 722 | Logln("[MQTT][callback] Callback update."); 723 | Logln(String("[MQTT][callback] Topic: " + topicStr)); 724 | 725 | if(topicStr.equals(CommandTopic)) 726 | publishMQTT(StateTopic, handleJSONReq(payloadStr)); 727 | } 728 | 729 | void WiFiDisconnectedCallback() { 730 | if(WiFi.getMode() == 1 && WiFi.status() == WL_CONNECTED) { 731 | Serial.println("[WiFiDisconnectedCallback] Connected to WiFi!"); 732 | digitalWrite(STATUS_LED, LOW); 733 | tBlink.disable(); 734 | tWifiStatus.setCallback(&checkWifiStatusCallback); 735 | } 736 | } 737 | 738 | void checkWifiStatusCallback() { 739 | Debugf("[checkWifiStatusCallback]"); 740 | if(WiFi.getMode() != 1 && WiFi.status() != WL_CONNECTED) { 741 | Logln("[checkWifiStatusCallback] Not connected to WiFi..."); 742 | blinkStatusLed(TASK_FOREVER, 500); 743 | tWifiStatus.setCallback(&WiFiDisconnectedCallback); 744 | } 745 | } 746 | 747 | /** Be sure to setup WIFI before running this method! */ 748 | void setupMQTT() { 749 | mqttclient = PubSubClient(Broker, Port, mqttCallback, wificlient); 750 | connectMQTT(); 751 | } 752 | 753 | uint64_t lastState = 0; 754 | void handleIR() { 755 | if (irrecv.decode(&results)) { 756 | const size_t bufferSize = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(7); 757 | DynamicJsonDocument doc(bufferSize); 758 | JsonObject json = doc.to(); 759 | String resp = ""; 760 | 761 | uint64_t state = results.value; 762 | 763 | if(state == 0xFFFFFFFF && 764 | (lastState == 0x11E728E || lastState == 0xABB1A8D2)) { 765 | Logln("[handleIR] Repeat."); 766 | state = lastState; 767 | } 768 | 769 | switch (state) { 770 | case 0xFFFFFFFF: 771 | Logln("[handleIR] Not repeatable."); 772 | break; 773 | case 0x63C98B53: 774 | Logln("[handleIR] Power button pressed."); 775 | currentMode = currentMode == On ? Off : On; 776 | break; 777 | case 0xEFA4E63F: 778 | Logln("[handleIR] Input button pressed."); 779 | setNextInput(); 780 | break; 781 | case 0x92CA878C: 782 | Logln("[handleIR] Mute button pressed."); 783 | mute = !mute; 784 | break; 785 | case 0x58B863E3: 786 | Logln("[handleIR] Level button pressed."); 787 | nextLevelOnCurrentEffect(); 788 | break; 789 | case 0x11E728E: 790 | Logln("[handleIR] Minus button pressed."); 791 | soundLevel[currentLevel()] -= soundLevel[currentLevel()] < 2 ? 0 : 1; 792 | break; 793 | case 0xABB1A8D2: 794 | Logln("[handleIR] Plus button pressed."); 795 | soundLevel[currentLevel()] += soundLevel[currentLevel()] >= 100 ? 0 : 2; 796 | break; 797 | case 0x48C7229F: 798 | Logln("[handleIR] Effect button pressed."); 799 | setNextEffect(); 800 | break; 801 | default: 802 | Log("No such ir code case: %X\n", results.value); 803 | irrecv.resume(); 804 | return; 805 | } 806 | // Things that has to be done in all standard states 807 | if(state != 0xFFFFFFFF) { 808 | lastState = state; 809 | } 810 | saveSettings(); 811 | getSettings(json); 812 | serializeJson(doc, Serial); 813 | publishMQTT(StateTopic, resp); 814 | irrecv.resume(); 815 | } 816 | } 817 | 818 | /** Returns a json formatted string with chip status */ 819 | String getChipStatsJSON() { 820 | const size_t bufferSize = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(4); 821 | DynamicJsonDocument doc(bufferSize); 822 | JsonObject root = doc.as(); 823 | JsonObject chip = root.createNestedObject("chip"); 824 | 825 | chip["id"] = String(ESP.getFlashChipId(), HEX); 826 | chip["mode"] = String(ESP.getFlashChipMode()); 827 | chip["size"] = String(String(ESP.getFlashChipRealSize()) + " bytes"); 828 | chip["speed"] = String(String(ESP.getFlashChipSpeed()) + " Hz"); 829 | 830 | String resp; 831 | serializeJsonPretty(doc, resp); 832 | return resp; 833 | } 834 | 835 | /** Prints chip status to serial */ 836 | void printChipStatus() { 837 | uint32_t realSize = ESP.getFlashChipRealSize(); 838 | uint32_t ideSize = ESP.getFlashChipSize(); 839 | FlashMode_t ideMode = ESP.getFlashChipMode(); 840 | 841 | Serial.printf("Flash real id: %08X\n", ESP.getFlashChipId()); 842 | Serial.printf("Flash real size: %u bytes\n\n", realSize); 843 | Serial.printf("Flash ide size: %u bytes\n", ideSize); 844 | Serial.printf("Flash ide speed: %u Hz\n", ESP.getFlashChipSpeed()); 845 | Serial.printf("Flash ide mode: %s\n\n", (ideMode == FM_QIO ? "QIO" : ideMode == FM_QOUT ? "QOUT" : ideMode == FM_DIO ? "DIO" : ideMode == FM_DOUT ? "DOUT" : "UNKNOWN")); 846 | Serial.printf("Sketch size:\t\t%u bytes\n", ESP.getSketchSize()); 847 | Serial.printf("Free sketch space:\t%u bytes\n", ESP.getFreeSketchSpace()); 848 | 849 | if (ideSize != realSize) { 850 | Serial.println("Flash Chip configuration wrong!\n"); 851 | } else { 852 | Serial.println("Flash Chip configuration ok.\n"); 853 | } 854 | } 855 | 856 | void sendStatesMQTT() { 857 | const size_t bufferSize = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(7); 858 | DynamicJsonDocument doc(bufferSize); 859 | JsonObject json = doc.to(); 860 | getSettings(json); 861 | String payload = ""; 862 | serializeJson(doc, payload); 863 | publishMQTT(StateTopic, payload); 864 | } 865 | 866 | void testingFunction() { 867 | String jsonString = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.75608,2.302038]}"; 868 | StaticJsonDocument<256> doc; 869 | auto error = deserializeJson(doc, jsonString); 870 | 871 | if(error) { 872 | Err("deserializeJson() failed with code "); 873 | Err(error.c_str()); 874 | return; 875 | } 876 | 877 | String one = doc["sensor"]; 878 | String two = "lol"; 879 | if(!doc["yes"]) { 880 | two = "yes"; 881 | } else if(doc["yes"] == "") { 882 | two = "lel"; 883 | } 884 | 885 | Serial.println("1: " + one); 886 | Serial.println("2: " + two); 887 | Serial.println(getStringIndex("Input3", inputs, ARRAY_SIZE(inputs))); 888 | 889 | loadSettings(); 890 | unsigned long start = micros(); 891 | saveSettings(); 892 | unsigned long finish = micros(); 893 | Serial.printf("Done, took %lu µs", finish - start); 894 | } 895 | 896 | void setup() { 897 | Serial.begin(115200); 898 | Serial.println("Booting"); 899 | 900 | #ifdef DEBUG_ENABLED 901 | printChipStatus(); 902 | #endif 903 | 904 | pinMode(ON_LED, INPUT); 905 | pinMode(STATUS_LED, OUTPUT); 906 | digitalWrite(STATUS_LED, HIGH); 907 | 908 | tWifiStatus.enable(); 909 | taskManager.execute(); 910 | 911 | setupWifiManager(); 912 | setupOTA(); 913 | setupEEPROM(); 914 | printSettings(); 915 | setupWebServer(); 916 | setupMQTT(); 917 | setupIR(); 918 | 919 | tCheckIfStillOn.enable(); 920 | tSendStatesMQTT.enable(); 921 | tCheckMQTTStatus.enable(); 922 | Serial.println("Ready"); 923 | Serial.print("IP address: "); 924 | Serial.println(WiFi.localIP()); 925 | digitalWrite(STATUS_LED, LOW); 926 | } 927 | 928 | void loop() { 929 | taskManager.execute(); 930 | 931 | if(OTA_ON) { 932 | noInterrupts(); 933 | ArduinoOTA.handle(); 934 | interrupts(); 935 | } 936 | 937 | server.handleClient(); 938 | mqttclient.loop(); 939 | handleIR(); 940 | 941 | // We need to go back to On-mode after a while 942 | if(isOn && currentMode >= BassLevel) { 943 | if(currentMode != lastMode) { 944 | levelTimeout = millis() + LEVEL_TIMEOUT; 945 | } else if(millis() > levelTimeout) { 946 | currentMode = On; 947 | saveSettings(); 948 | const size_t bufferSize = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(7); 949 | DynamicJsonDocument doc(bufferSize); 950 | JsonObject json = doc.to(); 951 | String resp = ""; 952 | getSettings(json); 953 | serializeJson(doc, resp); 954 | Logln("[Loop] Ending level mode.."); 955 | publishMQTT(StateTopic, resp); 956 | } 957 | } 958 | lastMode = currentMode; 959 | } --------------------------------------------------------------------------------