diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f92f5d7c..5582843df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ ### Builds after release 0.12.0 +#### Build 2111160 + +- Version bump to 0.13.0-b5 "Toki" +- Improv Serial support (PR #2334) +- Button improvements (PR #2284) +- Added two time zones (PR #2264, 2311) +- JSON in/decrementing support for brightness and presets +- Fixed no gamma correction for JSON individual LED control +- Preset cycle bugfix +- Removed ledCount +- LED settings buffer bugfix +- Network pin conflict bugfix +- Changed default ESP32 partition layout to 4M, 1M FS + #### Build 2110110 - Version bump to 0.13.0-b4 "Toki" diff --git a/platformio.ini b/platformio.ini index 2c529074f..9bc8bedf6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -208,6 +208,8 @@ build_flags = -g -DCONFIG_LITTLEFS_FOR_IDF_3_2 -D CONFIG_ASYNC_TCP_USE_WDT=0 +default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv + lib_deps = ${env.lib_deps} makuna/NeoPixelBus @ 2.6.7 @@ -293,6 +295,7 @@ platform = espressif32@2.0 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET lib_deps = ${esp32.lib_deps} +board_build.partitions = ${esp32.default_partitions} [env:esp32_eth] board = esp32-poe @@ -301,6 +304,7 @@ upload_speed = 921600 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 lib_deps = ${esp32.lib_deps} +board_build.partitions = ${esp32.default_partitions} [env:esp32s2_saola] board = esp32dev @@ -407,6 +411,7 @@ build_flags = ${common.build_flags_esp32} lib_deps = ${esp32.lib_deps} OneWire@~2.3.5 olikraus/U8g2 @ ^2.28.8 +board_build.partitions = ${esp32.default_partitions} [env:m5atom] board = esp32dev @@ -414,6 +419,7 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} -D LEDPIN=27 -D BTNPIN=39 lib_deps = ${esp32.lib_deps} platform = espressif32@3.2 +board_build.partitions = ${esp32.default_partitions} [env:sp501e] board = esp_wroom_02 @@ -499,3 +505,4 @@ monitor_filters = esp32_exception_decoder lib_deps = ${esp32.lib_deps} TFT_eSPI @ ^2.3.70 +board_build.partitions = ${esp32.default_partitions} diff --git a/wled00/button.cpp b/wled00/button.cpp index 39c1e4f7f..aef18c77e 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -299,8 +299,11 @@ void handleIO() #ifdef ESP8266 // turn off built-in LED if strip is turned off // this will break digital bus so will need to be reinitialised on On - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, HIGH); + PinOwner ledPinOwner = pinManager.getPinOwner(LED_BUILTIN); + if (!strip.isOffRefreshRequred && (ledPinOwner == PinOwner::None || ledPinOwner == PinOwner::BusDigital)) { + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + } #endif if (rlyPin>=0) { pinMode(rlyPin, OUTPUT); diff --git a/wled00/const.h b/wled00/const.h index 85f348e4c..a88f0a940 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -272,9 +272,13 @@ #endif #ifdef WLED_USE_ETHERNET -#define E131_MAX_UNIVERSE_COUNT 20 + #define E131_MAX_UNIVERSE_COUNT 20 #else -#define E131_MAX_UNIVERSE_COUNT 10 + #ifdef ESP8266 + #define E131_MAX_UNIVERSE_COUNT 9 + #else + #define E131_MAX_UNIVERSE_COUNT 12 + #endif #endif #define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 2b81199f9..ceaa631f2 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -97,6 +97,12 @@ void onHueConnect(void* arg, AsyncClient* client); void sendHuePoll(); void onHueData(void* arg, AsyncClient* client, void *data, size_t len); +//improv.cpp +void handleImprovPacket(); +void sendImprovStateResponse(uint8_t state, bool error = false); +void sendImprovInfoResponse(); +void sendImprovRPCResponse(uint8_t commandId); + //ir.cpp //bool decodeIRCustom(uint32_t code); void applyRepeatActions(); diff --git a/wled00/improv.cpp b/wled00/improv.cpp new file mode 100644 index 000000000..56ee9e0a0 --- /dev/null +++ b/wled00/improv.cpp @@ -0,0 +1,244 @@ +#include "wled.h" + +#ifdef WLED_DEBUG_IMPROV + #define DIMPROV_PRINT(x) Serial.print(x) + #define DIMPROV_PRINTLN(x) Serial.println(x) + #define DIMPROV_PRINTF(x...) Serial.printf(x) +#else + #define DIMPROV_PRINT(x) + #define DIMPROV_PRINTLN(x) + #define DIMPROV_PRINTF(x...) +#endif + +#define IMPROV_VERSION 1 + +void parseWiFiCommand(char *rpcData); + +enum ImprovPacketType { + Current_State = 0x01, + Error_State = 0x02, + RPC_Command = 0x03, + RPC_Response = 0x04 +}; + +enum ImprovPacketByte { + Version = 6, + PacketType = 7, + Length = 8, + RPC_CommandType = 9 +}; + +enum ImprovRPCType { + Command_Wifi = 0x01, + Request_State = 0x02, + Request_Info = 0x03 +}; + +//File dbgf; + +//blocking function to parse an Improv Serial packet +void handleImprovPacket() { + uint8_t header[6] = {'I','M','P','R','O','V'}; + + //dbgf = WLED_FS.open("/improv.log","a"); + + bool timeout = false; + uint8_t waitTime = 25; + uint16_t packetByte = 0; + uint8_t packetLen = 9; + uint8_t checksum = 0; + + uint8_t rpcCommandType = 0; + char rpcData[128]; + rpcData[0] = 0; + + while (!timeout) { + if (Serial.available() < 1) { + delay(1); + waitTime--; + if (!waitTime) timeout = true; + continue; + } + byte next = Serial.read(); + + DIMPROV_PRINT("Received improv byte: "); DIMPROV_PRINTF("%x\r\n",next); + //f.write(next); + switch (packetByte) { + case ImprovPacketByte::Version: { + if (next != IMPROV_VERSION) { + DIMPROV_PRINTLN(F("Invalid version")); + //dbgf.close(); + return; + } + break; + } + case ImprovPacketByte::PacketType: { + if (next != ImprovPacketType::RPC_Command) { + DIMPROV_PRINTF("Non RPC-command improv packet type %i\n",next); + //dbgf.close(); + return; + } + if (!improvActive) improvActive = 1; + break; + } + case ImprovPacketByte::Length: packetLen = 9 + next; break; + case ImprovPacketByte::RPC_CommandType: rpcCommandType = next; break; + default: { + if (packetByte >= packetLen) { //end of packet, check checksum match + + if (checksum != next) { + DIMPROV_PRINTF("Got RPC checksum %i, expected %i",next,checksum); + sendImprovStateResponse(0x01, true); + //dbgf.close(); + return; + } + + switch (rpcCommandType) { + case ImprovRPCType::Command_Wifi: parseWiFiCommand(rpcData); break; + case ImprovRPCType::Request_State: { + uint8_t improvState = 0x02; //authorized + if (WLED_WIFI_CONFIGURED) improvState = 0x03; //provisioning + if (Network.isConnected()) improvState = 0x04; //provisioned + sendImprovStateResponse(improvState, false); + if (improvState == 0x04) sendImprovRPCResponse(ImprovRPCType::Request_State); + break; + } + case ImprovRPCType::Request_Info: sendImprovInfoResponse(); break; + default: { + DIMPROV_PRINTF("Unknown RPC command %i\n",next); + sendImprovStateResponse(0x02, true); + } + } + //dbgf.close(); + return; + } + if (packetByte < 6) { //check header + if (next != header[packetByte]) { + DIMPROV_PRINTLN(F("Invalid improv header")); + //dbgf.close(); + return; + } + } else if (packetByte > 9) { //RPC data + rpcData[packetByte - 10] = next; + if (packetByte > 137) return; //prevent buffer overflow + } + } + } + + checksum += next; + packetByte++; + } + //dbgf.close(); +} + +void sendImprovStateResponse(uint8_t state, bool error) { + if (!error && improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true); + if (error) improvError = state; + char out[11] = {'I','M','P','R','O','V'}; + out[6] = IMPROV_VERSION; + out[7] = error? ImprovPacketType::Error_State : ImprovPacketType::Current_State; + out[8] = 1; + out[9] = state; + + uint8_t checksum = 0; + for (uint8_t i = 0; i < 10; i++) checksum += out[i]; + out[10] = checksum; + Serial.write((uint8_t*)out, 11); + Serial.write('\n'); +} + +void sendImprovRPCResponse(byte commandId) { + if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true); + uint8_t packetLen = 12; + char out[64] = {'I','M','P','R','O','V'}; + out[6] = IMPROV_VERSION; + out[7] = ImprovPacketType::RPC_Response; + out[8] = 2; //Length (set below) + out[9] = commandId; + out[10] = 0; //Data len (set below) + out[11] = '\0'; //URL len (set below) + + if (Network.isConnected()) + { + IPAddress localIP = Network.localIP(); + uint8_t len = sprintf(out+12, "http://%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]); + if (len > 24) return; //sprintf fail? + out[11] = len; + out[10] = 1 + len; + out[8] = 3 + len; //RPC command type + data len + url len + url + packetLen = 13 + len; + } + + uint8_t checksum = 0; + for (uint8_t i = 0; i < packetLen -1; i++) checksum += out[i]; + out[packetLen -1] = checksum; + Serial.write((uint8_t*)out, packetLen); + Serial.write('\n'); + improvActive = 1; //no longer provisioning +} + +void sendImprovInfoResponse() { + if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true); + uint8_t packetLen = 12; + char out[128] = {'I','M','P','R','O','V'}; + out[6] = IMPROV_VERSION; + out[7] = ImprovPacketType::RPC_Response; + //out[8] = 2; //Length (set below) + out[9] = ImprovRPCType::Request_Info; + //out[10] = 0; //Data len (set below) + out[11] = 4; //Firmware len ("WLED") + out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D'; + uint8_t lengthSum = 17; + uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.0-b5/%i"),VERSION); + out[16] = vlen; lengthSum += vlen; + uint8_t hlen = 7; + #ifdef ESP8266 + strcpy(out+lengthSum+1,"esp8266"); + #else + hlen = 5; + strcpy(out+lengthSum+1,"esp32"); + #endif + out[lengthSum] = hlen; + lengthSum += hlen + 1; + //Use serverDescription if it has been changed from the default "WLED", else mDNS name + bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); + strcpy(out+lengthSum+1,useMdnsName ? cmDNS : serverDescription); + uint8_t nlen = strlen(useMdnsName ? cmDNS : serverDescription); + out[lengthSum] = nlen; + lengthSum += nlen + 1; + + packetLen = lengthSum +1; + out[8] = lengthSum -9; + out[10] = lengthSum -11; + + uint8_t checksum = 0; + for (uint8_t i = 0; i < packetLen -1; i++) checksum += out[i]; + out[packetLen -1] = checksum; + Serial.write((uint8_t*)out, packetLen); + Serial.write('\n'); + DIMPROV_PRINT("Info checksum"); + DIMPROV_PRINTLN(checksum); +} + +void parseWiFiCommand(char* rpcData) { + uint8_t len = rpcData[0]; + if (!len || len > 126) return; + + uint8_t ssidLen = rpcData[1]; + if (ssidLen > len -1 || ssidLen > 32) return; + memset(clientSSID, 0, 32); + memcpy(clientSSID, rpcData+2, ssidLen); + + memset(clientPass, 0, 64); + if (len > ssidLen +1) { + uint8_t passLen = rpcData[2+ssidLen]; + memset(clientPass, 0, 64); + memcpy(clientPass, rpcData+3+ssidLen, passLen); + } + + sendImprovStateResponse(0x03); //provisioning + improvActive = 2; + + forceReconnect = true; + serializeConfig(); +} \ No newline at end of file diff --git a/wled00/json.cpp b/wled00/json.cpp index 5c28916bf..a7c4f8b3b 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -528,7 +528,7 @@ void serializeInfo(JsonObject root) leds[F("fps")] = strip.getFps(); leds[F("maxpwr")] = (strip.currentMilliamps)? strip.ablMilliampsMax : 0; leds[F("maxseg")] = strip.getMaxSegments(); - leds[F("seglock")] = false; //will be used in the future to prevent modifications to segment config + //leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config root[F("str")] = syncToggleReceive; @@ -590,7 +590,7 @@ void serializeInfo(JsonObject root) root[F("resetReason0")] = (int)rtc_get_reset_reason(0); root[F("resetReason1")] = (int)rtc_get_reset_reason(1); #endif - root[F("lwip")] = 0; + root[F("lwip")] = 0; //deprecated #else root[F("arch")] = "esp8266"; root[F("core")] = ESP.getCoreVersion(); diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index b0b84ced7..0dd2df56d 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -137,6 +137,11 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) return false; } +PinOwner PinManagerClass::getPinOwner(byte gpio) { + if (!isPinOk(gpio, false)) return PinOwner::None; + return ownerTag[gpio]; +} + #ifdef ARDUINO_ARCH_ESP32 byte PinManagerClass::allocateLedc(byte channels) { diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 37d08e6db..06188b85c 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -93,6 +93,8 @@ class PinManagerClass { // will return false for reserved pins bool isPinOk(byte gpio, bool output = true); + PinOwner getPinOwner(byte gpio); + #ifdef ARDUINO_ARCH_ESP32 byte allocateLedc(byte channels); void deallocateLedc(byte pos, byte channels); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 24d53b0b7..6c4278ae3 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -312,6 +312,8 @@ void WLED::setup() sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); } + if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); + strip.service(); #ifndef WLED_DISABLE_OTA @@ -329,6 +331,8 @@ void WLED::setup() #ifdef WLED_ENABLE_DMX initDMX(); #endif + + if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); // HTTP server page init initServer(); @@ -659,14 +663,26 @@ void WLED::handleConnection() interfacesInited = false; initConnection(); } - if (now - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) + //send improv failed 6 seconds after second init attempt (24 sec. after provisioning) + if (improvActive > 2 && now - lastReconnectAttempt > 6000) { + sendImprovStateResponse(0x03, true); + improvActive = 2; + } + if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && WLED_WIFI_CONFIGURED) { + if (improvActive == 2) improvActive = 3; initConnection(); + } if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) initAP(); - } else if (!interfacesInited) { // newly connected + } else if (!interfacesInited) { //newly connected DEBUG_PRINTLN(""); DEBUG_PRINT(F("Connected! IP address: ")); DEBUG_PRINTLN(Network.localIP()); + if (improvActive) { + if (improvError == 3) sendImprovStateResponse(0x00, true); + sendImprovStateResponse(0x04); + if (improvActive > 1) sendImprovRPCResponse(0x01); + } initInterfaces(); userConnected(); usermods.connected(); diff --git a/wled00/wled.h b/wled00/wled.h index c927bba2d..0ece58a1e 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -512,6 +512,10 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 25 // blynk WLED_GLOBAL bool blynkEnabled _INIT(false); +//improv +WLED_GLOBAL byte improvActive _INIT(0); //0: no improv packet received, 1: improv active, 2: provisioning +WLED_GLOBAL byte improvError _INIT(0); + //playlists WLED_GLOBAL int16_t currentPlaylist _INIT(-1); //still used for "PL=~" HTTP API command diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index c7d9a9968..13cb78878 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -16,7 +16,7 @@ enum class AdaState { Data_Blue, TPM2_Header_Type, TPM2_Header_CountHi, - TPM2_Header_CountLo + TPM2_Header_CountLo, }; void handleSerial() @@ -41,7 +41,12 @@ void handleSerial() else if (next == 0xC9) { //TPM2 start byte state = AdaState::TPM2_Header_Type; } - else if (next == '{') { //JSON API + else if (next == 'I') { + handleImprovPacket(); + return; + } else if (next == 'v') { + Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION); + } else if (next == '{') { //JSON API bool verboseResponse = false; DEBUG_PRINTLN(F("Serial JSON buffer requested.")); #ifdef WLED_USE_DYNAMIC_JSON @@ -68,6 +73,7 @@ void handleSerial() serializeInfo(info); serializeJson(doc, Serial); + Serial.println(); } releaseJSONBufferLock(); }