Skip to content

Commit

Permalink
Implement multiple WiFi
Browse files Browse the repository at this point in the history
- similar to #3705
- solves #2845, #2974, #852, #1228
  • Loading branch information
blazoncek committed Jan 20, 2024
1 parent 5dd8f0a commit bfb217c
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 137 deletions.
102 changes: 67 additions & 35 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,39 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
linked_remote[12] = '\0';
#endif

JsonObject nw_ins_0 = nw["ins"][0];
getStringFromJson(clientSSID, nw_ins_0[F("ssid")], 33);
//int nw_ins_0_pskl = nw_ins_0[F("pskl")];
//The WiFi PSK is normally not contained in the regular file for security reasons.
//If it is present however, we will use it
getStringFromJson(clientPass, nw_ins_0["psk"], 65);

JsonArray nw_ins_0_ip = nw_ins_0["ip"];
JsonArray nw_ins_0_gw = nw_ins_0["gw"];
JsonArray nw_ins_0_sn = nw_ins_0["sn"];
size_t n = 0;
JsonArray nw_ins = nw["ins"];
if (!nw_ins.isNull()) {
// as password are stored separately in wsec.json when reading configuration vector resize happens there, but for dynamic config we need to resize if necessary
if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing
for (JsonObject wifi : nw_ins) {
JsonArray ip = wifi["ip"];
JsonArray gw = wifi["gw"];
JsonArray sn = wifi["sn"];
char ssid[33] = "";
char pass[65] = "";
IPAddress nIP = (uint32_t)0U, nGW = (uint32_t)0U, nSN = (uint32_t)0x00FFFFFF; // little endian
getStringFromJson(ssid, wifi[F("ssid")], 33);
getStringFromJson(pass, wifi["psk"], 65); // password is not normally present but if it is, use it
for (size_t i = 0; i < 4; i++) {
CJSON(nIP[i], ip[i]);
CJSON(nGW[i], gw[i]);
CJSON(nSN[i], sn[i]);
}
if (strlen(ssid) > 0) strlcpy(multiWiFi[n].clientSSID, ssid, 33); // this will keep old SSID intact if not present in JSON
if (strlen(pass) > 0) strlcpy(multiWiFi[n].clientPass, pass, 65); // this will keep old password intact if not present in JSON
multiWiFi[n].staticIP = nIP;
multiWiFi[n].staticGW = nGW;
multiWiFi[n].staticSN = nSN;
if (++n >= WLED_MAX_WIFI_COUNT) break;
}
}

for (byte i = 0; i < 4; i++) {
CJSON(staticIP[i], nw_ins_0_ip[i]);
CJSON(staticGateway[i], nw_ins_0_gw[i]);
CJSON(staticSubnet[i], nw_ins_0_sn[i]);
JsonArray dns = nw[F("dns")];
if (!dns.isNull()) {
for (size_t i = 0; i < 4; i++) {
CJSON(dnsAddress[i], dns[i]);
}
}

JsonObject ap = doc["ap"];
Expand Down Expand Up @@ -212,7 +230,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonObject btn_obj = hw["btn"];
bool pull = btn_obj[F("pull")] | (!disablePullUp); // if true, pullup is enabled
disablePullUp = !pull;
JsonArray hw_btn_ins = btn_obj[F("ins")];
JsonArray hw_btn_ins = btn_obj["ins"];
if (!hw_btn_ins.isNull()) {
for (uint8_t b = 0; b < WLED_MAX_BUTTONS; b++) { // deallocate existing button pins
pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button
Expand Down Expand Up @@ -433,7 +451,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (e131Port == DDP_DEFAULT_PORT) e131Port = E131_DEFAULT_PORT; // prevent double DDP port allocation
CJSON(e131Multicast, if_live[F("mc")]);

JsonObject if_live_dmx = if_live[F("dmx")];
JsonObject if_live_dmx = if_live["dmx"];
CJSON(e131Universe, if_live_dmx[F("uni")]);
CJSON(e131SkipOutOfSequence, if_live_dmx[F("seqskip")]);
CJSON(DMXAddress, if_live_dmx[F("addr")]);
Expand Down Expand Up @@ -665,19 +683,23 @@ void serializeConfig() {
#endif

JsonArray nw_ins = nw.createNestedArray("ins");
for (size_t n = 0; n < multiWiFi.size(); n++) {
JsonObject wifi = nw_ins.createNestedObject();
wifi[F("ssid")] = multiWiFi[n].clientSSID;
wifi[F("pskl")] = strlen(multiWiFi[n].clientPass);
JsonArray wifi_ip = wifi.createNestedArray("ip");
JsonArray wifi_gw = wifi.createNestedArray("gw");
JsonArray wifi_sn = wifi.createNestedArray("sn");
for (size_t i = 0; i < 4; i++) {
wifi_ip.add(multiWiFi[n].staticIP[i]);
wifi_gw.add(multiWiFi[n].staticGW[i]);
wifi_sn.add(multiWiFi[n].staticSN[i]);
}
}

JsonObject nw_ins_0 = nw_ins.createNestedObject();
nw_ins_0[F("ssid")] = clientSSID;
nw_ins_0[F("pskl")] = strlen(clientPass);

JsonArray nw_ins_0_ip = nw_ins_0.createNestedArray("ip");
JsonArray nw_ins_0_gw = nw_ins_0.createNestedArray("gw");
JsonArray nw_ins_0_sn = nw_ins_0.createNestedArray("sn");

for (byte i = 0; i < 4; i++) {
nw_ins_0_ip.add(staticIP[i]);
nw_ins_0_gw.add(staticGateway[i]);
nw_ins_0_sn.add(staticSubnet[i]);
JsonArray dns = nw.createNestedArray(F("dns"));
for (size_t i = 0; i < 4; i++) {
dns.add(dnsAddress[i]);
}

JsonObject ap = root.createNestedObject("ap");
Expand All @@ -693,7 +715,7 @@ void serializeConfig() {
ap_ip.add(2);
ap_ip.add(1);

JsonObject wifi = root.createNestedObject("wifi");
JsonObject wifi = root.createNestedObject(F("wifi"));
wifi[F("sleep")] = !noWifiSleep;
wifi[F("phy")] = force802_3g;

Expand Down Expand Up @@ -721,7 +743,7 @@ void serializeConfig() {
}
#endif

JsonObject hw = root.createNestedObject("hw");
JsonObject hw = root.createNestedObject(F("hw"));

JsonObject hw_led = hw.createNestedObject("led");
hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL
Expand Down Expand Up @@ -1048,8 +1070,17 @@ bool deserializeConfigSec() {

JsonObject root = pDoc->as<JsonObject>();

JsonObject nw_ins_0 = root["nw"]["ins"][0];
getStringFromJson(clientPass, nw_ins_0["psk"], 65);
size_t n = 0;
JsonArray nw_ins = root["nw"]["ins"];
if (!nw_ins.isNull()) {
if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing
for (JsonObject wifi : nw_ins) {
char pw[65] = "";
getStringFromJson(pw, wifi["psk"], 65);
strlcpy(multiWiFi[n].clientPass, pw, 65);
if (++n >= WLED_MAX_WIFI_COUNT) break;
}
}

JsonObject ap = root["ap"];
getStringFromJson(apPass, ap["psk"] , 65);
Expand Down Expand Up @@ -1088,9 +1119,10 @@ void serializeConfigSec() {
JsonObject nw = root.createNestedObject("nw");

JsonArray nw_ins = nw.createNestedArray("ins");

JsonObject nw_ins_0 = nw_ins.createNestedObject();
nw_ins_0["psk"] = clientPass;
for (size_t i = 0; i < multiWiFi.size(); i++) {
JsonObject wifi = nw_ins.createNestedObject();
wifi[F("psk")] = multiWiFi[i].clientPass;
}

JsonObject ap = root.createNestedObject("ap");
ap["psk"] = apPass;
Expand Down
4 changes: 4 additions & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
#define DEFAULT_MDNS_NAME "x"

//increase if you need more
#ifndef WLED_MAX_WIFI_COUNT
#define WLED_MAX_WIFI_COUNT 3
#endif

#ifndef WLED_MAX_USERMODS
#ifdef ESP8266
#define WLED_MAX_USERMODS 4
Expand Down
87 changes: 55 additions & 32 deletions wled00/data/settings_wifi.htm
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
var d = document;
var loc = false, locip, locproto = "http:";
var scanLoops = 0, preScanSSID = "";

var maxNetworks = 3;
function gId(e) { return d.getElementById(e); }
function cE(e) { return d.createElement(e); }
function toggle(el){gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide");}
Expand Down Expand Up @@ -52,13 +52,13 @@
}
scanLoops = 0;

let cs = gId("CS");
if (cs) {
let cs = d.querySelectorAll("#wifi_entries input[type=text]#CS0");
for (let input of (cs||[])) {
let select = cE("select");
select.setAttribute("id", "CS");
select.setAttribute("name", "CS");
select.setAttribute("onchange", "T()");
preScanSSID = cs.value;
select.id = input.id;
select.name = input.name;
select.setAttribute("onchange", "T(this)");
preScanSSID = input.value;

for (let i = 0; i < select.children.length; i++) {
select.removeChild(select.children[i]);
Expand All @@ -70,7 +70,7 @@
option.setAttribute("value", networks[i].ssid);
option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`;

if (networks[i].ssid === cs.value) {
if (networks[i].ssid === input.value) {
option.setAttribute("selected", "selected");
}

Expand All @@ -79,28 +79,59 @@
const option = cE("option");

option.setAttribute("value", "!Cs");
option.textContent = `Other network...`;
option.textContent = "Other network...";
select.appendChild(option);

cs.replaceWith(select);
input.replaceWith(select);
}

button.disabled = false;
button.textContent = "Scan";
});
}
// replace WiFi select with custom SSID input field again
function T() {
let cs = gId("CS");
function T(cs) {
if (!cs || cs.value != "!Cs") return;
let input = cE("input");
input.type = "text";
input.id = "CS";
input.name ="CS";
input.id = cs.id;
input.name = cs.name;
input.setAttribute("maxlength",32);
input.value = preScanSSID;
cs.replaceWith(input);
}
function resetWiFi(maxN = undefined) {
if (maxN) maxNetworks = maxN;
let entries = gId("wifi_entries").children
for (let i = entries.length; i > 0; i--) entries[i-1].remove();
btnWiFi(0);
}
function btnWiFi(i) {
gId("wifi_add").style.display = (i<maxNetworks) ? "inline":"none";
gId("wifi_rem").style.display = (i>1) ? "inline":"none";
}
function addWiFi(ssid="",pass="",ip=0,gw=0,sn=0x00ffffff) { // little endian
var i = gId("wifi_entries").childNodes.length;
if (i >= maxNetworks) return;
var b = `<div id="net${i}"><hr class="sml">
Network name (SSID${i==0?", empty to not connect":""}):<br><input type="text" id="CS${i}" name="CS${i}" maxlength="32" value="${ssid}" ${i>1?"required":""}><br>
Network password:<br><input type="password" name="PW${i}" maxlength="64" value="${pass}"><br>
Static IP (leave at 0.0.0.0 for DHCP)${i==0?"<br>Also used by Ethernet":""}:<br>
<input name="IP${i}0" type="number" class="s" min="0" max="255" value="${ip&0xFF}" required>.<input name="IP${i}1" type="number" class="s" min="0" max="255" value="${(ip>>8)&0xFF}" required>.<input name="IP${i}2" type="number" class="s" min="0" max="255" value="${(ip>>16)&0xFF}" required>.<input name="IP${i}3" type="number" class="s" min="0" max="255" value="${(ip>>24)&0xFF}" required><br>
Static gateway:<br>
<input name="GW${i}0" type="number" class="s" min="0" max="255" value="${gw&0xFF}" required>.<input name="GW${i}1" type="number" class="s" min="0" max="255" value="${(gw>>8)&0xFF}" required>.<input name="GW${i}2" type="number" class="s" min="0" max="255" value="${(gw>>16)&0xFF}" required>.<input name="GW${i}3" type="number" class="s" min="0" max="255" value="${(gw>>24)&0xFF}" required><br>
Static subnet mask:<br>
<input name="SN${i}0" type="number" class="s" min="0" max="255" value="${sn&0xFF}" required>.<input name="SN${i}1" type="number" class="s" min="0" max="255" value="${(sn>>8)&0xFF}" required>.<input name="SN${i}2" type="number" class="s" min="0" max="255" value="${(sn>>16)&0xFF}" required>.<input name="SN${i}3" type="number" class="s" min="0" max="255" value="${(sn>>24)&0xFF}" required></div>`;
gId("wifi_entries").insertAdjacentHTML("beforeend", b);
btnWiFi(i+1);
}
function remWiFi() {
const entries = gId("wifi_entries").children;
const i = entries.length;
if (i < 2) return;
entries[i-1].remove();
btnWiFi(i-1);
}
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = cE("script");
Expand Down Expand Up @@ -158,24 +189,16 @@
<h2>WiFi setup</h2>
<h3>Connect to existing network</h3>
<button type="button" id="scan" onclick="N()">Scan</button><br>
Network name (SSID, empty to not connect):<br>
<input type="text" id="CS" name="CS" maxlength="32"><br>
Network password: <br> <input type="password" name="CP" maxlength="63"><br>
Static IP (leave at 0.0.0.0 for DHCP):<br>
<input name="I0" type="number" class="s" min="0" max="255" required> .
<input name="I1" type="number" class="s" min="0" max="255" required> .
<input name="I2" type="number" class="s" min="0" max="255" required> .
<input name="I3" type="number" class="s" min="0" max="255" required><br>
Static gateway:<br>
<input name="G0" type="number" class="s" min="0" max="255" required> .
<input name="G1" type="number" class="s" min="0" max="255" required> .
<input name="G2" type="number" class="s" min="0" max="255" required> .
<input name="G3" type="number" class="s" min="0" max="255" required><br>
Static subnet mask:<br>
<input name="S0" type="number" class="s" min="0" max="255" required> .
<input name="S1" type="number" class="s" min="0" max="255" required> .
<input name="S2" type="number" class="s" min="0" max="255" required> .
<input name="S3" type="number" class="s" min="0" max="255" required><br>
<div id="wifi">
Wireless networks
<div id="wifi_entries"></div>
<hr class="sml">
<button type="button" id="wifi_add" onclick="addWiFi()">+</button>
<button type="button" id="wifi_rem" onclick="remWiFi()">-</button><br>
</div>
DNS server address:<br>
<input name="D0" type="number" class="s" min="0" max="255" required>.<input name="D1" type="number" class="s" min="0" max="255" required>.<input name="D2" type="number" class="s" min="0" max="255" required>.<input name="D3" type="number" class="s" min="0" max="255" required><br>
<br>
mDNS address (leave empty for no mDNS):<br>
http:// <input type="text" name="CM" maxlength="32"> .local<br>
Client IP: <span class="sip"> Not connected </span> <br>
Expand Down
2 changes: 1 addition & 1 deletion wled00/e131.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ void prepareArtnetPollReply(ArtPollReply *reply) {
// Node is DHCP capable
// Node supports 15 bit Port-Address (Art-Net 3 or 4)
// Node is able to switch between ArtNet and sACN
reply->reply_status_2 = (staticIP[0] == 0) ? 0x1F : 0x1D;
reply->reply_status_2 = (multiWiFi[0].staticIP[0] == 0) ? 0x1F : 0x1D;

// RDM is disabled
// Output style is continuous
Expand Down
15 changes: 15 additions & 0 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau
return true;
}

typedef struct WiFiConfig {
char clientSSID[33];
char clientPass[65];
IPAddress staticIP;
IPAddress staticGW;
IPAddress staticSN;
WiFiConfig(const char *ssid="", const char *pass="", uint32_t ip=0, uint32_t gw=0, uint32_t subnet=0x00FFFFFF) // little endian
: staticIP(ip)
, staticGW(gw)
, staticSN(subnet)
{
strncpy(clientSSID, ssid, 32); clientSSID[32] = 0;
strncpy(clientPass, pass, 64); clientPass[64] = 0;
}
} wifi_config;

//colors.cpp
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
Expand Down
10 changes: 5 additions & 5 deletions wled00/improv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,14 @@ void parseWiFiCommand(char* rpcData) {

uint8_t ssidLen = rpcData[1];
if (ssidLen > len -1 || ssidLen > 32) return;
memset(clientSSID, 0, 32);
memcpy(clientSSID, rpcData+2, ssidLen);
memset(multiWiFi[0].clientSSID, 0, 32);
memcpy(multiWiFi[0].clientSSID, rpcData+2, ssidLen);

memset(clientPass, 0, 64);
memset(multiWiFi[0].clientPass, 0, 64);
if (len > ssidLen +1) {
uint8_t passLen = rpcData[2+ssidLen];
memset(clientPass, 0, 64);
memcpy(clientPass, rpcData+3+ssidLen, passLen);
memset(multiWiFi[0].clientPass, 0, 64);
memcpy(multiWiFi[0].clientPass, rpcData+3+ssidLen, passLen);
}

sendImprovStateResponse(0x03); //provisioning
Expand Down
4 changes: 2 additions & 2 deletions wled00/network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ void WiFiEvent(WiFiEvent_t event)
if (!apActive) {
WiFi.disconnect(true);
}
if (staticIP != (uint32_t)0x00000000 && staticGateway != (uint32_t)0x00000000) {
ETH.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8));
if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) {
ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress);
} else {
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
Expand Down
Loading

0 comments on commit bfb217c

Please sign in to comment.