Detect and reuse ESPHome wifi secrets in new screen wizard
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
# Wifi-secrets hergebruiken — 0.2.6
|
||||||
|
|
||||||
|
De wizard controleert de ESPHome-map van de add-on. Met geldige wifi_ssid en
|
||||||
|
wifi_password in secrets.yaml worden wifi-velden verborgen en uitgeschakeld.
|
||||||
|
Alleen als dat bestand nog niet bestaat vraagt de wizard om wifi voor de eerste
|
||||||
|
installatie. Een bestaand onvolledig/ongeldig bestand wordt niet overschreven.
|
||||||
|
De browser ontvangt uitsluitend beschikbaarheid, geen secretwaarden.
|
||||||
|
|
||||||
|
API-encryptie- en OTA-sleutels blijven uniek gegenereerd door ESP Screens en staan
|
||||||
|
in de eigen apparaat-YAML. Updates behouden die YAML. Geen firmwarewijziging.
|
||||||
|
|
||||||
|
Controles: 45 Python-tests geslaagd, inclusief hergebruik zonder invoer, ongewijzigd
|
||||||
|
behoud van bestaande secrets, geen secretwaarden in status/profiel, eerste
|
||||||
|
installatie en ongeldige YAML. JavaScript-syntax en Git-diff gecontroleerd.
|
||||||
|
Browserpreview: bij bestaande wifi alleen bevestiging zichtbaar, geen wifi-invoer;
|
||||||
|
profiel aanmaken beschikbaar. Test gebruikt geen echte wifiwaarden.
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 0.2.6
|
||||||
|
|
||||||
|
- Nieuw scherm hergebruikt bestaande ESPHome-wifi-secrets automatisch. De wizard vraagt alleen wifi als secrets.yaml nog niet bestaat. Ontbrekende wifi-sleutels in een bestaand bestand worden gemeld zonder het bestand te overschrijven.
|
||||||
|
|
||||||
## 0.2.5
|
## 0.2.5
|
||||||
|
|
||||||
- Firmware: een vacuumkaart openen toont de echte status, geen opdrachtmelding. Het statuslabel in de robotkaart blijft ook na een actie actueel.
|
- Firmware: een vacuumkaart openen toont de echte status, geen opdrachtmelding. Het statuslabel in de robotkaart blijft ook na een actie actueel.
|
||||||
|
|||||||
@@ -36,16 +36,36 @@ class Firmware:
|
|||||||
raise ValueError('Profiel bestaat niet in de ESPHome-map.')
|
raise ValueError('Profiel bestaat niet in de ESPHome-map.')
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
def wifi_status(self):
|
||||||
|
# Return availability only; never send secret values to the browser.
|
||||||
|
path = self.root / 'secrets.yaml'
|
||||||
|
if not path.exists():
|
||||||
|
return {'state': 'new', 'missing': ['wifi_ssid', 'wifi_password']}
|
||||||
|
try:
|
||||||
|
values = yaml.safe_load(path.read_text())
|
||||||
|
if not isinstance(values, dict):
|
||||||
|
return {'state': 'invalid'}
|
||||||
|
missing = [key for key in ('wifi_ssid', 'wifi_password')
|
||||||
|
if not isinstance(values.get(key), str)]
|
||||||
|
if isinstance(values.get('wifi_ssid'), str) and not values['wifi_ssid'].strip():
|
||||||
|
missing.append('wifi_ssid')
|
||||||
|
return {'state': 'missing' if missing else 'ready', 'missing': missing}
|
||||||
|
except (OSError, yaml.YAMLError, UnicodeError):
|
||||||
|
return {'state': 'invalid'}
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
return {'available': bool(shutil.which('esphome')), 'profiles': self.profiles(),
|
return {'available': bool(shutil.which('esphome')), 'profiles': self.profiles(),
|
||||||
'ports': self.ports(), 'job': self.job, 'logs': list(self.logs)}
|
'ports': self.ports(), 'job': self.job, 'logs': list(self.logs), 'wifi': self.wifi_status()}
|
||||||
|
|
||||||
def create(self, data):
|
def create(self, data):
|
||||||
content = installation_yaml(data)
|
content = installation_yaml(data)
|
||||||
self.root.mkdir(parents=True,exist_ok=True)
|
self.root.mkdir(parents=True,exist_ok=True)
|
||||||
# Existing wifi secrets remain untouched. New owners can supply them once.
|
# Existing wifi secrets remain untouched. New owners can supply them once.
|
||||||
secret_path = self.root / 'secrets.yaml'
|
secret_path = self.root / 'secrets.yaml'
|
||||||
if not secret_path.exists():
|
wifi = self.wifi_status()
|
||||||
|
if wifi['state'] in ('missing', 'invalid'):
|
||||||
|
raise ValueError('Controleer wifi_ssid en wifi_password in ESPHome secrets.yaml. Bestaande secrets blijven behouden.')
|
||||||
|
if wifi['state'] == 'new':
|
||||||
ssid, password = data.get('wifi_ssid'), data.get('wifi_password')
|
ssid, password = data.get('wifi_ssid'), data.get('wifi_password')
|
||||||
if not isinstance(ssid,str) or not ssid.strip() or not isinstance(password,str):
|
if not isinstance(ssid,str) or not ssid.strip() or not isinstance(password,str):
|
||||||
raise ValueError('Vul wifi in voor deze eerste installatie.')
|
raise ValueError('Vul wifi in voor deze eerste installatie.')
|
||||||
|
|||||||
@@ -569,8 +569,33 @@ for (const [value, label] of [
|
|||||||
};
|
};
|
||||||
$("#filters").append(b);
|
$("#filters").append(b);
|
||||||
}
|
}
|
||||||
$("#new-screen").onclick = $("#start").onclick = () =>
|
async function checkWifiSecrets() {
|
||||||
|
const fields = $("#wifi-fields"), status = $("#wifi-status");
|
||||||
|
fields.hidden = true;
|
||||||
|
fields.disabled = true;
|
||||||
|
$("#create-profile").disabled = true;
|
||||||
|
status.textContent = "ESPHome wifi-instellingen controleren…";
|
||||||
|
try {
|
||||||
|
const {wifi} = await (await api("firmware")).json();
|
||||||
|
if (wifi?.state === "ready") {
|
||||||
|
status.textContent = "✓ Bestaande ESPHome-wifi gevonden. Dit scherm gebruikt automatisch wifi_ssid en wifi_password uit je secrets.yaml.";
|
||||||
|
$("#create-profile").disabled = false;
|
||||||
|
} else if (wifi?.state === "new") {
|
||||||
|
status.textContent = "Nog geen ESPHome-secrets gevonden. Vul wifi één keer in; volgende schermen gebruiken deze gegevens automatisch.";
|
||||||
|
fields.hidden = false;
|
||||||
|
fields.disabled = false;
|
||||||
|
$("#create-profile").disabled = false;
|
||||||
|
} else {
|
||||||
|
status.textContent = "Controleer wifi_ssid en wifi_password in ESPHome secrets.yaml. Je bestaande bestand blijft behouden. Sluit en open deze wizard opnieuw na aanpassen.";
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
status.textContent = "ESPHome-secrets konden niet worden gecontroleerd. Sluit en open de wizard opnieuw om het nogmaals te proberen.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#new-screen").onclick = $("#start").onclick = () => {
|
||||||
$("#installer").showModal();
|
$("#installer").showModal();
|
||||||
|
checkWifiSecrets();
|
||||||
|
};
|
||||||
$("#close-install").onclick = () => $("#installer").close();
|
$("#close-install").onclick = () => $("#installer").close();
|
||||||
let generatedName = "";
|
let generatedName = "";
|
||||||
$("#install-form").onsubmit = async (e) => {
|
$("#install-form").onsubmit = async (e) => {
|
||||||
|
|||||||
@@ -202,9 +202,12 @@
|
|||||||
/><small
|
/><small
|
||||||
>Kleine letters, cijfers en streepjes. Elk scherm een andere
|
>Kleine letters, cijfers en streepjes. Elk scherm een andere
|
||||||
naam.</small
|
naam.</small
|
||||||
></label
|
></label>
|
||||||
><label
|
<p id="wifi-status" role="status"></p>
|
||||||
>Wifi-naam (alleen bij eerste installatie)<input
|
<fieldset id="wifi-fields" hidden disabled>
|
||||||
|
<legend>Wifi voor je eerste ESPHome-apparaat</legend>
|
||||||
|
<label
|
||||||
|
>Wifi-naam<input
|
||||||
name="wifi_ssid"
|
name="wifi_ssid"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/></label>
|
/></label>
|
||||||
@@ -214,6 +217,7 @@
|
|||||||
type="password"
|
type="password"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
/></label>
|
/></label>
|
||||||
|
</fieldset>
|
||||||
<button type="button" id="create-profile">
|
<button type="button" id="create-profile">
|
||||||
Bewaar profiel in ESP Screens
|
Bewaar profiel in ESP Screens
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: ESP Screen Manager
|
name: ESP Screen Manager
|
||||||
version: "0.2.5"
|
version: "0.2.6"
|
||||||
slug: esp_screen_manager
|
slug: esp_screen_manager
|
||||||
description: Kies tegels voor je CYD en Guition touchscreen, zonder opnieuw flashen.
|
description: Kies tegels voor je CYD en Guition touchscreen, zonder opnieuw flashen.
|
||||||
url: https://github.com/MaxGramser/homeassistant_espscreen
|
url: https://github.com/MaxGramser/homeassistant_espscreen
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'screen_manager/app'))
|
||||||
|
from firmware import Firmware
|
||||||
|
|
||||||
|
class WifiReuse(unittest.TestCase):
|
||||||
|
def test_existing_wifi_reused_without_browser_values(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
f = Firmware(tmp, tmp)
|
||||||
|
path = Path(tmp) / 'secrets.yaml'
|
||||||
|
original = '# keep comments\nwifi_ssid: example-private-network\nwifi_password: example-private-password\nother_secret: leave-me\n'
|
||||||
|
path.write_text(original)
|
||||||
|
self.assertEqual(f.wifi_status()['state'], 'ready')
|
||||||
|
self.assertNotIn('example-private', json.dumps(f.status()))
|
||||||
|
f.create({'board': 'guition', 'name': 'second-screen', 'friendly_name': 'Second'})
|
||||||
|
self.assertEqual(path.read_text(), original)
|
||||||
|
profile = f.profile('second-screen.yaml').read_text()
|
||||||
|
self.assertIn('ssid: !secret wifi_ssid', profile)
|
||||||
|
self.assertIn('password: !secret wifi_password', profile)
|
||||||
|
self.assertNotIn('example-private', profile)
|
||||||
|
|
||||||
|
def test_missing_keys_do_not_overwrite_existing_secrets(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
f = Firmware(tmp, tmp)
|
||||||
|
path = Path(tmp) / 'secrets.yaml'
|
||||||
|
path.write_text('other: keep\n')
|
||||||
|
self.assertEqual(f.wifi_status()['state'], 'missing')
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
f.create({'board': 'cyd', 'name': 'screen', 'friendly_name': 'Screen',
|
||||||
|
'wifi_ssid': 'replacement', 'wifi_password': 'replacement'})
|
||||||
|
self.assertEqual(path.read_text(), 'other: keep\n')
|
||||||
|
self.assertFalse((Path(tmp) / 'screen.yaml').exists())
|
||||||
|
|
||||||
|
def test_first_install_and_invalid_file(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
f = Firmware(tmp, tmp)
|
||||||
|
self.assertEqual(f.wifi_status()['state'], 'new')
|
||||||
|
f.create({'board': 'cyd', 'name': 'screen', 'friendly_name': 'Screen',
|
||||||
|
'wifi_ssid': 'network', 'wifi_password': ''})
|
||||||
|
self.assertEqual(f.wifi_status()['state'], 'ready')
|
||||||
|
(Path(tmp) / 'secrets.yaml').write_text('wifi_ssid: [invalid')
|
||||||
|
self.assertEqual(f.wifi_status(), {'state': 'invalid'})
|
||||||
Reference in New Issue
Block a user