Skip to content

Commit 38d8dfe

Browse files
authored
Improv support (wled#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
1 parent 6df64d0 commit 38d8dfe

File tree

5 files changed

+280
-4
lines changed

5 files changed

+280
-4
lines changed

wled00/fcn_declare.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ void onHueConnect(void* arg, AsyncClient* client);
9393
void sendHuePoll();
9494
void onHueData(void* arg, AsyncClient* client, void *data, size_t len);
9595

96+
//improv.cpp
97+
void handleImprovPacket();
98+
void sendImprovStateResponse(uint8_t state, bool error = false);
99+
void sendImprovInfoResponse();
100+
void sendImprovRPCResponse(uint8_t commandId);
101+
96102
//ir.cpp
97103
bool decodeIRCustom(uint32_t code);
98104
void applyRepeatActions();

wled00/improv.cpp

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#include "wled.h"
2+
3+
#ifdef WLED_DEBUG_IMPROV
4+
#define DIMPROV_PRINT(x) Serial.print(x)
5+
#define DIMPROV_PRINTLN(x) Serial.println(x)
6+
#define DIMPROV_PRINTF(x...) Serial.printf(x)
7+
#else
8+
#define DIMPROV_PRINT(x)
9+
#define DIMPROV_PRINTLN(x)
10+
#define DIMPROV_PRINTF(x...)
11+
#endif
12+
13+
#define IMPROV_VERSION 1
14+
15+
void parseWiFiCommand(char *rpcData);
16+
17+
enum ImprovPacketType {
18+
Current_State = 0x01,
19+
Error_State = 0x02,
20+
RPC_Command = 0x03,
21+
RPC_Response = 0x04
22+
};
23+
24+
enum ImprovPacketByte {
25+
Version = 6,
26+
PacketType = 7,
27+
Length = 8,
28+
RPC_CommandType = 9
29+
};
30+
31+
enum ImprovRPCType {
32+
Command_Wifi = 0x01,
33+
Request_State = 0x02,
34+
Request_Info = 0x03
35+
};
36+
37+
//File dbgf;
38+
39+
//blocking function to parse an Improv Serial packet
40+
void handleImprovPacket() {
41+
uint8_t header[6] = {'I','M','P','R','O','V'};
42+
43+
//dbgf = WLED_FS.open("/improv.log","a");
44+
45+
bool timeout = false;
46+
uint8_t waitTime = 25;
47+
uint16_t packetByte = 0;
48+
uint8_t packetLen = 9;
49+
uint8_t checksum = 0;
50+
51+
uint8_t rpcCommandType = 0;
52+
char rpcData[128];
53+
rpcData[0] = 0;
54+
55+
while (!timeout) {
56+
if (Serial.available() < 1) {
57+
delay(1);
58+
waitTime--;
59+
if (!waitTime) timeout = true;
60+
continue;
61+
}
62+
byte next = Serial.read();
63+
64+
DIMPROV_PRINT("Received improv byte: "); DIMPROV_PRINTF("%x\r\n",next);
65+
//f.write(next);
66+
switch (packetByte) {
67+
case ImprovPacketByte::Version: {
68+
if (next != IMPROV_VERSION) {
69+
DIMPROV_PRINTLN(F("Invalid version"));
70+
//dbgf.close();
71+
return;
72+
}
73+
break;
74+
}
75+
case ImprovPacketByte::PacketType: {
76+
if (next != ImprovPacketType::RPC_Command) {
77+
DIMPROV_PRINTF("Non RPC-command improv packet type %i\n",next);
78+
//dbgf.close();
79+
return;
80+
}
81+
if (!improvActive) improvActive = 1;
82+
break;
83+
}
84+
case ImprovPacketByte::Length: packetLen = 9 + next; break;
85+
case ImprovPacketByte::RPC_CommandType: rpcCommandType = next; break;
86+
default: {
87+
if (packetByte >= packetLen) { //end of packet, check checksum match
88+
89+
if (checksum != next) {
90+
DIMPROV_PRINTF("Got RPC checksum %i, expected %i",next,checksum);
91+
sendImprovStateResponse(0x01, true);
92+
//dbgf.close();
93+
return;
94+
}
95+
96+
switch (rpcCommandType) {
97+
case ImprovRPCType::Command_Wifi: parseWiFiCommand(rpcData); break;
98+
case ImprovRPCType::Request_State: {
99+
uint8_t improvState = 0x02; //authorized
100+
if (WLED_WIFI_CONFIGURED) improvState = 0x03; //provisioning
101+
if (Network.isConnected()) improvState = 0x04; //provisioned
102+
sendImprovStateResponse(improvState, false);
103+
if (improvState == 0x04) sendImprovRPCResponse(ImprovRPCType::Request_State);
104+
break;
105+
}
106+
case ImprovRPCType::Request_Info: sendImprovInfoResponse(); break;
107+
default: {
108+
DIMPROV_PRINTF("Unknown RPC command %i\n",next);
109+
sendImprovStateResponse(0x02, true);
110+
}
111+
}
112+
//dbgf.close();
113+
return;
114+
}
115+
if (packetByte < 6) { //check header
116+
if (next != header[packetByte]) {
117+
DIMPROV_PRINTLN(F("Invalid improv header"));
118+
//dbgf.close();
119+
return;
120+
}
121+
} else if (packetByte > 9) { //RPC data
122+
rpcData[packetByte - 10] = next;
123+
if (packetByte > 137) return; //prevent buffer overflow
124+
}
125+
}
126+
}
127+
128+
checksum += next;
129+
packetByte++;
130+
}
131+
//dbgf.close();
132+
}
133+
134+
void sendImprovStateResponse(uint8_t state, bool error) {
135+
if (!error && improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
136+
if (error) improvError = state;
137+
char out[11] = {'I','M','P','R','O','V'};
138+
out[6] = IMPROV_VERSION;
139+
out[7] = error? ImprovPacketType::Error_State : ImprovPacketType::Current_State;
140+
out[8] = 1;
141+
out[9] = state;
142+
143+
uint8_t checksum = 0;
144+
for (uint8_t i = 0; i < 10; i++) checksum += out[i];
145+
out[10] = checksum;
146+
Serial.write((uint8_t*)out, 11);
147+
Serial.write('\n');
148+
}
149+
150+
void sendImprovRPCResponse(byte commandId) {
151+
if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
152+
uint8_t packetLen = 12;
153+
char out[64] = {'I','M','P','R','O','V'};
154+
out[6] = IMPROV_VERSION;
155+
out[7] = ImprovPacketType::RPC_Response;
156+
out[8] = 2; //Length (set below)
157+
out[9] = commandId;
158+
out[10] = 0; //Data len (set below)
159+
out[11] = '\0'; //URL len (set below)
160+
161+
if (Network.isConnected())
162+
{
163+
IPAddress localIP = Network.localIP();
164+
uint8_t len = sprintf(out+12, "http://%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
165+
if (len > 24) return; //sprintf fail?
166+
out[11] = len;
167+
out[10] = 1 + len;
168+
out[8] = 3 + len; //RPC command type + data len + url len + url
169+
packetLen = 13 + len;
170+
}
171+
172+
uint8_t checksum = 0;
173+
for (uint8_t i = 0; i < packetLen -1; i++) checksum += out[i];
174+
out[packetLen -1] = checksum;
175+
Serial.write((uint8_t*)out, packetLen);
176+
Serial.write('\n');
177+
improvActive = 1; //no longer provisioning
178+
}
179+
180+
void sendImprovInfoResponse() {
181+
if (improvError > 0 && improvError < 3) sendImprovStateResponse(0x00, true);
182+
uint8_t packetLen = 12;
183+
char out[128] = {'I','M','P','R','O','V'};
184+
out[6] = IMPROV_VERSION;
185+
out[7] = ImprovPacketType::RPC_Response;
186+
//out[8] = 2; //Length (set below)
187+
out[9] = ImprovRPCType::Request_Info;
188+
//out[10] = 0; //Data len (set below)
189+
out[11] = 4; //Firmware len ("WLED")
190+
out[12] = 'W'; out[13] = 'L'; out[14] = 'E'; out[15] = 'D';
191+
uint8_t lengthSum = 17;
192+
uint8_t vlen = sprintf_P(out+lengthSum,PSTR("0.13.0-b4/%i"),VERSION);
193+
out[16] = vlen; lengthSum += vlen;
194+
uint8_t hlen = 7;
195+
#ifdef ESP8266
196+
strcpy(out+lengthSum+1,"esp8266");
197+
#else
198+
hlen = 5;
199+
strcpy(out+lengthSum+1,"esp32");
200+
#endif
201+
out[lengthSum] = hlen;
202+
lengthSum += hlen + 1;
203+
//Use serverDescription if it has been changed from the default "WLED", else mDNS name
204+
bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0);
205+
strcpy(out+lengthSum+1,useMdnsName ? cmDNS : serverDescription);
206+
uint8_t nlen = strlen(useMdnsName ? cmDNS : serverDescription);
207+
out[lengthSum] = nlen;
208+
lengthSum += nlen + 1;
209+
210+
packetLen = lengthSum +1;
211+
out[8] = lengthSum -9;
212+
out[10] = lengthSum -11;
213+
214+
uint8_t checksum = 0;
215+
for (uint8_t i = 0; i < packetLen -1; i++) checksum += out[i];
216+
out[packetLen -1] = checksum;
217+
Serial.write((uint8_t*)out, packetLen);
218+
Serial.write('\n');
219+
DIMPROV_PRINT("Info checksum");
220+
DIMPROV_PRINTLN(checksum);
221+
}
222+
223+
void parseWiFiCommand(char* rpcData) {
224+
uint8_t len = rpcData[0];
225+
if (!len || len > 126) return;
226+
227+
uint8_t ssidLen = rpcData[1];
228+
if (ssidLen > len -1 || ssidLen > 32) return;
229+
memset(clientSSID, 0, 32);
230+
memcpy(clientSSID, rpcData+2, ssidLen);
231+
232+
memset(clientPass, 0, 64);
233+
if (len > ssidLen +1) {
234+
uint8_t passLen = rpcData[2+ssidLen];
235+
memset(clientPass, 0, 64);
236+
memcpy(clientPass, rpcData+3+ssidLen, passLen);
237+
}
238+
239+
sendImprovStateResponse(0x03); //provisioning
240+
improvActive = 2;
241+
242+
forceReconnect = true;
243+
serializeConfig();
244+
}

wled00/wled.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ void WLED::setup()
374374
sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6);
375375
}
376376

377+
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
378+
377379
strip.service();
378380

379381
#ifndef WLED_DISABLE_OTA
@@ -391,6 +393,8 @@ void WLED::setup()
391393
#ifdef WLED_ENABLE_DMX
392394
initDMX();
393395
#endif
396+
397+
if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket();
394398
// HTTP server page init
395399
initServer();
396400

@@ -720,14 +724,26 @@ void WLED::handleConnection()
720724
interfacesInited = false;
721725
initConnection();
722726
}
723-
if (now - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED)
727+
//send improv failed 6 seconds after second init attempt (24 sec. after provisioning)
728+
if (improvActive > 2 && now - lastReconnectAttempt > 6000) {
729+
sendImprovStateResponse(0x03, true);
730+
improvActive = 2;
731+
}
732+
if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && WLED_WIFI_CONFIGURED) {
733+
if (improvActive == 2) improvActive = 3;
724734
initConnection();
735+
}
725736
if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN))
726737
initAP();
727-
} else if (!interfacesInited) { // newly connected
738+
} else if (!interfacesInited) { //newly connected
728739
DEBUG_PRINTLN("");
729740
DEBUG_PRINT(F("Connected! IP address: "));
730741
DEBUG_PRINTLN(Network.localIP());
742+
if (improvActive) {
743+
if (improvError == 3) sendImprovStateResponse(0x00, true);
744+
sendImprovStateResponse(0x04);
745+
if (improvActive > 1) sendImprovRPCResponse(0x01);
746+
}
731747
initInterfaces();
732748
userConnected();
733749
usermods.connected();

wled00/wled.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,10 @@ WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 25
506506
// blynk
507507
WLED_GLOBAL bool blynkEnabled _INIT(false);
508508

509+
//improv
510+
WLED_GLOBAL byte improvActive _INIT(0); //0: no improv packet received, 1: improv active, 2: provisioning
511+
WLED_GLOBAL byte improvError _INIT(0);
512+
509513
//playlists
510514
WLED_GLOBAL unsigned long presetCycledTime _INIT(0);
511515
WLED_GLOBAL int16_t currentPlaylist _INIT(-1);

wled00/wled_serial.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ enum class AdaState {
1616
Data_Blue,
1717
TPM2_Header_Type,
1818
TPM2_Header_CountHi,
19-
TPM2_Header_CountLo
19+
TPM2_Header_CountLo,
2020
};
2121

2222
void handleSerial()
@@ -41,7 +41,12 @@ void handleSerial()
4141
else if (next == 0xC9) { //TPM2 start byte
4242
state = AdaState::TPM2_Header_Type;
4343
}
44-
else if (next == '{') { //JSON API
44+
else if (next == 'I') {
45+
handleImprovPacket();
46+
return;
47+
} else if (next == 'v') {
48+
Serial.print("WLED"); Serial.write(' '); Serial.println(VERSION);
49+
} else if (next == '{') { //JSON API
4550
bool verboseResponse = false;
4651
{
4752
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
@@ -61,6 +66,7 @@ void handleSerial()
6166
serializeInfo(info);
6267

6368
serializeJson(doc, Serial);
69+
Serial.println();
6470
}
6571
}
6672
break;

0 commit comments

Comments
 (0)