diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 180d5935a..e6cc22c2a 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -93,6 +93,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..cf31cc039 --- /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-b4/%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/wled.cpp b/wled00/wled.cpp index 0c2910dc1..fe27166d2 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -374,6 +374,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 @@ -391,6 +393,8 @@ void WLED::setup() #ifdef WLED_ENABLE_DMX initDMX(); #endif + + if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); // HTTP server page init initServer(); @@ -720,14 +724,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 5ac2a58c3..331bcfead 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -506,6 +506,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 unsigned long presetCycledTime _INIT(0); WLED_GLOBAL int16_t currentPlaylist _INIT(-1); diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index bcc5a061c..3d23d9284 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; { DynamicJsonDocument doc(JSON_BUFFER_SIZE); @@ -61,6 +66,7 @@ void handleSerial() serializeInfo(info); serializeJson(doc, Serial); + Serial.println(); } } break;