Added automatic IP detection to setup

This commit is contained in:
EivindH06
2025-10-06 09:50:50 +02:00
parent 61a0a1055d
commit 2051230cb3
6 changed files with 230 additions and 39 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -162,41 +162,77 @@ export function getPriceSourceUrl(code) {
let tries = 0;
export function scanForDevice(sysinfo, updateFn) {
var url = "";
tries++;
var retry = function() {
const targets = buildScanTargets(sysinfo);
if(!targets.length) {
if(updateFn) updateFn("");
setTimeout(scanForDevice, 1500, sysinfo, updateFn);
return;
}
const target = targets[(tries - 1) % targets.length];
if(!target) {
setTimeout(scanForDevice, 1000, sysinfo, updateFn);
return;
}
const url = normalizeTarget(target);
if(console) console.log("Trying url " + url);
if(updateFn) updateFn(url);
const retry = function() {
setTimeout(scanForDevice, 1000, sysinfo, updateFn);
};
if(sysinfo.net.ip && tries%3 == 0) {
if(!sysinfo.net.ip) {
retry();
return;
};
url = "http://" + sysinfo.net.ip;
} else if(sysinfo.hostname && tries%3 == 1) {
url = "http://" + sysinfo.hostname;
} else if(sysinfo.hostname && tries%3 == 2) {
url = "http://" + sysinfo.hostname + ".local";
} else {
url = "";
}
if(console) console.log("Trying url " + url);
if(updateFn) updateFn(url);
var xhr = new XMLHttpRequest();
const xhr = new XMLHttpRequest();
xhr.timeout = 5000;
xhr.addEventListener('abort', retry);
xhr.addEventListener('error', retry);
xhr.addEventListener('timeout', retry);
xhr.addEventListener('load', function(e) {
xhr.addEventListener('load', function() {
window.location.href = url ? url : "/";
});
xhr.open("GET", url + "/is-alive", true);
const healthUrl = url.replace(/\/$/, '') + "/is-alive";
xhr.open("GET", healthUrl, true);
xhr.send();
};
function buildScanTargets(sysinfo = {}) {
const manualTargets = Array.isArray(sysinfo.targets) ? sysinfo.targets : [];
const fallbackTargets = [];
if(sysinfo.net && sysinfo.net.ip) {
fallbackTargets.push(sysinfo.net.ip);
}
if(sysinfo.hostname) {
fallbackTargets.push(sysinfo.hostname);
const looksLikeHost = sysinfo.hostname.indexOf('.') === -1 && sysinfo.hostname.indexOf(':') === -1;
if(looksLikeHost) {
fallbackTargets.push(`${sysinfo.hostname}.local`);
}
}
const candidates = [...manualTargets, ...fallbackTargets];
const deduped = [];
for(const value of candidates) {
if(!value) continue;
const trimmed = value.toString().trim();
if(!trimmed) continue;
if(!deduped.includes(trimmed)) deduped.push(trimmed);
}
return deduped;
}
function normalizeTarget(target) {
if(!target) return "";
const trimmed = target.toString().trim();
if(trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
return trimmed;
}
return `http://${trimmed}`;
}
export function capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}

View File

@@ -1,5 +1,6 @@
<script>
import { sysinfoStore, networksStore } from './DataStores.js';
import { get } from 'svelte/store';
import { translationsStore } from './TranslationService.js';
import Mask from './Mask.svelte'
import SubnetOptions from './SubnetOptions.svelte';
@@ -29,6 +30,7 @@
let staticIp = false;
let connectionMode = 1;
let loadingOrSaving = false;
let reconnectTargets = [];
function updateSysinfo(url) {
sysinfoStore.update(s => {
@@ -53,19 +55,41 @@
let res = (await response.json())
loadingOrSaving = false;
const hostFromForm = (formData.get('sh') ?? '').trim();
const message = typeof res.message === 'string' ? res.message : '';
const hintParts = message.split('|').map(part => part.trim());
const hintHost = hintParts[0] ?? '';
const hintMdns = hintParts[1] ?? '';
const hintIp = hintParts[2] ?? '';
const fallbackHostname = hintHost || hostFromForm || sysinfo.hostname || (sysinfo?.chipId ? `ams-${sysinfo.chipId}` : 'ams-reader');
const fallbackMdns = hintMdns || (fallbackHostname && fallbackHostname.indexOf('.') === -1 && fallbackHostname.indexOf(':') === -1 ? `${fallbackHostname}.local` : fallbackHostname);
const staticIpValue = staticIp ? (formData.get('si') || '').trim() : hintIp;
const uniqueTargets = Array.from(new Set([staticIpValue, fallbackHostname, fallbackMdns].filter(val => val && val.length > 0)));
reconnectTargets = res.reboot ? [...uniqueTargets] : [];
sysinfoStore.update(s => {
s.hostname = formData.get('sh');
s.usrcfg = res.success;
s.booting = res.reboot;
if(!s.net) s.net = {};
const computedHostname = fallbackHostname || s.hostname || hostFromForm;
s.hostname = computedHostname;
if(staticIp) {
s.net.ip = formData.get('si');
s.net.ip = staticIpValue;
s.net.mask = formData.get('su');
s.net.gw = formData.get('sg');
s.net.dns1 = formData.get('sd');
} else if(hintIp) {
s.net.ip = hintIp;
}
if(res.reboot) setTimeout(scanForDevice, 5000, sysinfo, updateSysinfo);
s.targets = [...uniqueTargets];
s.usrcfg = res.success;
s.booting = res.reboot;
return s;
});
const latestSysinfo = get(sysinfoStore);
sysinfo = latestSysinfo;
if(res.reboot) {
setTimeout(() => scanForDevice(latestSysinfo, updateSysinfo), 5000);
}
}
@@ -169,6 +193,16 @@
<div class="my-3">
<button type="submit" class="btn-pri">{translations.btn?.save ?? "Save"}</button>
</div>
{#if reconnectTargets.length}
<div class="mt-4 text-sm text-gray-600 dark:text-gray-300">
<p>{translations.setup?.reconnect?.info ?? "Device will reboot now. Try these addresses to reconnect:"}</p>
<ul class="list-disc pl-5 space-y-1 mt-2">
{#each reconnectTargets as target}
<li><code>{target.startsWith('http://') || target.startsWith('https://') ? target : `http://${target}`}</code></li>
{/each}
</ul>
</div>
{/if}
</form>
</div>
</div>

View File

@@ -174,6 +174,7 @@ private:
void addConditionalCloudHeaders();
void optionsGet();
bool probeNewNetworkIp(NetworkConfig& network, String& ipOut);
};
#endif

View File

@@ -348,7 +348,11 @@ void AmsWebServer::sysinfoJson() {
if(sys.userConfigured) {
NetworkConfig networkConfig;
config->getNetworkConfig(networkConfig);
hostname = String(networkConfig.hostname);
if(strlen(networkConfig.hostname) > 0) {
hostname = String(networkConfig.hostname);
} else {
hostname = "ams-"+chipIdStr;
}
} else {
hostname = "ams-"+chipIdStr;
}
@@ -1239,6 +1243,18 @@ void AmsWebServer::handleSave() {
SystemConfig sys;
config->getSystemConfig(sys);
uint32_t chipId;
#if defined(ESP32)
chipId = ( ESP.getEfuseMac() >> 32 ) % 0xFFFFFFFF;
#else
chipId = ESP.getChipId();
#endif
String chipIdStr = String(chipId, HEX);
String defaultHostname = F("ams-");
defaultHostname += chipIdStr;
String reconnectHost = "";
String reconnectIp = "";
bool success = true;
if(server.hasArg(F("v")) && server.arg(F("v")) == F("true")) {
int boardType = server.arg(F("vb")).toInt();
@@ -1276,17 +1292,25 @@ void AmsWebServer::handleSave() {
network.mode = server.arg(F("sc")).toInt();
if(network.mode > 3 || network.mode == 0) network.mode = 1; // WiFi Client
String requestedHostname = server.hasArg(F("sh")) ? server.arg(F("sh")) : String("");
requestedHostname.trim();
if(network.mode == 3 || strlen(network.ssid) > 0) {
if(server.hasArg(F("sm")) && server.arg(F("sm")) == "static") {
strcpy(network.ip, server.arg(F("si")).c_str());
String ipValue = server.arg(F("si"));
strcpy(network.ip, ipValue.c_str());
reconnectIp = ipValue;
strcpy(network.gateway, server.arg(F("sg")).c_str());
strcpy(network.subnet, server.arg(F("su")).c_str());
strcpy(network.dns1, server.arg(F("sd")).c_str());
} else {
reconnectIp = "";
}
if(server.hasArg(F("sh")) && !server.arg(F("sh")).isEmpty()) {
strcpy(network.hostname, server.arg(F("sh")).c_str());
if(requestedHostname.length() > 0) {
strcpy(network.hostname, requestedHostname.c_str());
network.mdns = true;
reconnectHost = requestedHostname;
} else {
network.mdns = false;
}
@@ -1318,6 +1342,12 @@ void AmsWebServer::handleSave() {
#endif
config->setNetworkConfig(network);
config->setMeterConfig(meterConfig);
if(reconnectIp.length() == 0) {
probeNewNetworkIp(network, reconnectIp);
}
if(reconnectHost.length() == 0 && sys.vendorConfigured) {
reconnectHost = defaultHostname;
}
sys.userConfigured = success;
sys.dataCollectionConsent = 0;
@@ -1394,22 +1424,28 @@ void AmsWebServer::handleSave() {
if(server.hasArg(F("nm"))) {
if(server.arg(F("nm")) == "static") {
strcpy(network.ip, server.arg(F("ni")).c_str());
String ipValue = server.arg(F("ni"));
strcpy(network.ip, ipValue.c_str());
strcpy(network.gateway, server.arg(F("ng")).c_str());
strcpy(network.subnet, server.arg(F("ns")).c_str());
strcpy(network.dns1, server.arg(F("nd1")).c_str());
strcpy(network.dns2, server.arg(F("nd2")).c_str());
reconnectIp = ipValue;
} else if(server.arg(F("nm")) == "dhcp") {
strcpy(network.ip, "");
strcpy(network.gateway, "");
strcpy(network.subnet, "");
strcpy(network.dns1, "");
strcpy(network.dns2, "");
reconnectIp = "";
}
}
network.ipv6 = server.hasArg(F("nx")) && server.arg(F("nx")) == F("true");
network.mdns = server.hasArg(F("nd")) && server.arg(F("nd")) == F("true");
config->setNetworkConfig(network);
if(strlen(network.hostname) > 0) {
reconnectHost = network.hostname;
}
}
}
@@ -1505,6 +1541,15 @@ void AmsWebServer::handleSave() {
strcpy(network.hostname, server.arg(F("gh")).c_str());
}
config->setNetworkConfig(network);
if(server.hasArg(F("gh"))) {
String hostArg = server.arg(F("gh"));
hostArg.trim();
if(hostArg.length() > 0) {
reconnectHost = hostArg;
}
} else if(strlen(network.hostname) > 0) {
reconnectHost = network.hostname;
}
NtpConfig ntp;
config->getNtpConfig(ntp);
@@ -1738,9 +1783,36 @@ void AmsWebServer::handleSave() {
success = false;
}
NetworkConfig currentNetwork;
config->getNetworkConfig(currentNetwork);
if(reconnectHost.length() == 0 && strlen(currentNetwork.hostname) > 0) {
reconnectHost = String(currentNetwork.hostname);
}
if(reconnectHost.length() == 0) {
reconnectHost = defaultHostname;
}
reconnectHost.trim();
if(reconnectIp.length() == 0 && strlen(currentNetwork.ip) > 0) {
reconnectIp = String(currentNetwork.ip);
}
String reconnectMdns = reconnectHost;
reconnectMdns.trim();
if(reconnectMdns.length() > 0) {
bool appearsToBeHostName = reconnectMdns.indexOf('.') == -1 && reconnectMdns.indexOf(':') == -1;
if(!reconnectMdns.endsWith(F(".local")) && appearsToBeHostName) {
reconnectMdns += F(".local");
}
}
String reconnectHint = reconnectHost;
String mdnsForHint = reconnectMdns.length() > 0 ? reconnectMdns : reconnectHost;
reconnectHint += F("|");
reconnectHint += mdnsForHint;
reconnectHint += F("|");
reconnectHint += reconnectIp;
snprintf_P(buf, BufferSize, RESPONSE_JSON,
success ? "true" : "false",
"",
reconnectHint.c_str(),
performRestart ? "true" : "false"
);
server.setContentLength(strlen(buf));
@@ -2830,6 +2902,54 @@ void AmsWebServer::optionsGet() {
server.send(200);
}
bool AmsWebServer::probeNewNetworkIp(NetworkConfig& network, String& ipOut) {
if(network.mode != NETWORK_MODE_WIFI_CLIENT || strlen(network.ssid) == 0) {
return false;
}
if(ipOut.length() > 0) {
return true;
}
bool found = false;
#if defined(ESP8266) || defined(ESP32)
#if defined(ESP8266)
WiFiMode_t originalMode = WiFi.getMode();
#else
wifi_mode_t originalMode = WiFi.getMode();
#endif
if(originalMode != WIFI_AP_STA) {
WiFi.mode(WIFI_AP_STA);
}
WiFi.persistent(false);
if(strlen(network.hostname) > 0) {
#if defined(ESP32)
WiFi.setHostname(network.hostname);
#else
WiFi.hostname(network.hostname);
#endif
}
WiFi.begin(network.ssid, strlen(network.psk) > 0 ? network.psk : NULL);
unsigned long start = millis();
while(millis() - start < 8000) {
wl_status_t status = WiFi.status();
if(status == WL_CONNECTED) {
IPAddress candidate = WiFi.localIP();
if(candidate != IPAddress()) {
ipOut = candidate.toString();
found = true;
break;
}
}
server.handleClient();
delay(120);
yield();
}
WiFi.disconnect(false);
WiFi.mode(originalMode);
#endif
return found;
}
void AmsWebServer::wifiScan() {
if(!checkSecurity(1))
return;