One-window install: profile, wifi, USB flash and pairing hints (0.2.27)

"Nieuw scherm" used to span two dialogs plus a copy/download of the
installation YAML. It is now one dialog: board, name (node name derived,
editable), wifi only when secrets.yaml lacks it, the detected USB port,
and Installeren. The add-on writes the profile with unique API/OTA keys,
merges just the missing wifi lines into secrets.yaml (comments and other
secrets untouched, result re-parsed before writing), then builds and
flashes with the live ESPHome log and stage in the same dialog; a failed
build offers a retry without a second profile. The YAML download
endpoint is gone: the profile already lives in the ESPHome folder.

Pairing happens in Home Assistant itself, so the done view and a sidebar
card per not-yet-paired profile say so, with a button to Apparaten &
diensten, the API key to copy and the reminder to allow HA actions.
One profile per screen; the dialog says that too.

Tests: wifi merge cases, install pre-checks, pending list, profile
metadata; the date-anchored hourly forecast test now follows the clock.
Firmware 0.2.22 stays current.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-14 11:11:23 +02:00
parent 5d6651d68b
commit 2327d31261
16 changed files with 848 additions and 287 deletions
+7 -4
View File
@@ -1,6 +1,6 @@
"""Weather card data (hours, rain) and last-run times for scenes, scripts and buttons."""
import asyncio
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from pathlib import Path
import sys
import tempfile
@@ -16,8 +16,10 @@ TZ = ZoneInfo('Europe/Amsterdam')
NOW = datetime(2026, 9, 13, 11, 40, tzinfo=timezone.utc)
def hourly():
return [{'datetime': f'2026-09-13T{h:02d}:00:00+00:00', 'condition': 'rainy' if h < 12 else 'partlycloudy', 'temperature': 18 + h / 10,
def hourly(day=None):
# Whole hours from 08:00 on the given day (default: the fixed NOW day) into the next morning.
start = (day or NOW).replace(hour=0, minute=0, second=0, microsecond=0)
return [{'datetime': (start + timedelta(hours=h)).isoformat(), 'condition': 'rainy' if h < 12 else 'partlycloudy', 'temperature': 18 + h / 10,
'precipitation': 0.2 if h < 12 else 0.0, 'precipitation_probability': 60 if h < 12 else 5} for h in range(8, 30)]
@@ -70,7 +72,8 @@ class HourlySync(unittest.IsolatedAsyncioTestCase):
asked = []
async def forecast(entity, kind='daily'):
asked.append(kind)
return daily() if kind == 'daily' else hourly()
# The manager reads the real clock, so the hourly strip is anchored to today.
return daily() if kind == 'daily' else hourly(datetime.now(timezone.utc))
m.ha.forecast = forecast
m.ha.registry.append({'entity_id': 'sensor.fw', 'platform': 'esphome', 'original_name': 'Schermfirmware'})
m.ha.states['sensor.fw'] = {'state': '0.2.19'}