Improv support (#2334)

* Working Improv device identification

* Improv functional

* Cast fix

* Minor fix for two back-to-back Improv packets

* Improv checksum update and logic simplification

* Improved improv failed connection behavior
This commit is contained in:
Christian Schwinne 2021-11-16 23:20:26 +01:00 committed by GitHub
parent 6df64d0d31
commit 38d8dfe5ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 280 additions and 4 deletions

View File

@ -93,6 +93,12 @@ void onHueConnect(void* arg, AsyncClient* client);
void sendHuePoll(); void sendHuePoll();
void onHueData(void* arg, AsyncClient* client, void *data, size_t len); 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 //ir.cpp
bool decodeIRCustom(uint32_t code); bool decodeIRCustom(uint32_t code);
void applyRepeatActions(); void applyRepeatActions();

244
wled00/improv.cpp Normal file
View File

@ -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();
}

View File

@ -374,6 +374,8 @@ void WLED::setup()
sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6);
} }
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
strip.service(); strip.service();
#ifndef WLED_DISABLE_OTA #ifndef WLED_DISABLE_OTA
@ -391,6 +393,8 @@ void WLED::setup()
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX
initDMX(); initDMX();
#endif #endif
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
// HTTP server page init // HTTP server page init
initServer(); initServer();
@ -720,14 +724,26 @@ void WLED::handleConnection()
interfacesInited = false; interfacesInited = false;
initConnection(); 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(); initConnection();
}
if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN))
initAP(); initAP();
} else if (!interfacesInited) { //newly connected } else if (!interfacesInited) { //newly connected
DEBUG_PRINTLN(""); DEBUG_PRINTLN("");
DEBUG_PRINT(F("Connected! IP address: ")); DEBUG_PRINT(F("Connected! IP address: "));
DEBUG_PRINTLN(Network.localIP()); DEBUG_PRINTLN(Network.localIP());
if (improvActive) {
if (improvError == 3) sendImprovStateResponse(0x00, true);
sendImprovStateResponse(0x04);
if (improvActive > 1) sendImprovRPCResponse(0x01);
}
initInterfaces(); initInterfaces();
userConnected(); userConnected();
usermods.connected(); usermods.connected();

View File

@ -506,6 +506,10 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 25
// blynk // blynk
WLED_GLOBAL bool blynkEnabled _INIT(false); 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 //playlists
WLED_GLOBAL unsigned long presetCycledTime _INIT(0); WLED_GLOBAL unsigned long presetCycledTime _INIT(0);
WLED_GLOBAL int16_t currentPlaylist _INIT(-1); WLED_GLOBAL int16_t currentPlaylist _INIT(-1);

View File

@ -16,7 +16,7 @@ enum class AdaState {
Data_Blue, Data_Blue,
TPM2_Header_Type, TPM2_Header_Type,
TPM2_Header_CountHi, TPM2_Header_CountHi,
TPM2_Header_CountLo TPM2_Header_CountLo,
}; };
void handleSerial() void handleSerial()
@ -41,7 +41,12 @@ void handleSerial()
else if (next == 0xC9) { //TPM2 start byte else if (next == 0xC9) { //TPM2 start byte
state = AdaState::TPM2_Header_Type; 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; bool verboseResponse = false;
{ {
DynamicJsonDocument doc(JSON_BUFFER_SIZE); DynamicJsonDocument doc(JSON_BUFFER_SIZE);
@ -61,6 +66,7 @@ void handleSerial()
serializeInfo(info); serializeInfo(info);
serializeJson(doc, Serial); serializeJson(doc, Serial);
Serial.println();
} }
} }
break; break;