diff --git a/CLAUDE.md b/CLAUDE.md index deeeef3..9ea71fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,7 +57,7 @@ From Tuya's definition for a sibling product (tuya-local issue #3821). See the R - DP127 writes are ignored while an alarm is snoozed. - A mode without light sets brightness to 0, and a light mode with brightness 0 falls back to no light, so the firmware sets a level when choosing a light mode. - Alarm radio stations were reset to 1 while no stations were saved. -- Radio Volume writes were rejected with the radio off. +- Radio Volume and station writes are ignored with the radio off. With it on, both work, and the station stays selected after turning it off. - Keep `default_transition_length` at 0. A transition sends about 50 MCU writes a second. - LibreTiny before v1.14.0 can hang after a software restart or OTA until powered off for about 30 seconds (LibreTiny issue #396). The config pins v1.14.0. Drop the pin once ESPHome defaults to it or newer. - "Last Restart Reason" reports Power-On even after software restarts. @@ -69,9 +69,12 @@ Verified: - Wednesday-only rang on a Wednesday, which fits bit 0 = Monday. A Tuesday-only silent test was skipped. - Sunset starts and stops. Its sound was heard, and a glow was seen but not clearly caused by it. - A manual scan found 2 stations, while a scan started from the firmware found 0. +- Next/Previous Station and Radio Station change the MCU's station, with wrap-around. Audio on them wasn't confirmed: the owner heard no radio signal. +- Home Assistant is used as the time source when connected, with public NTP as the fallback. Not tested: -- Radio alarms, Radio Station changes and Radio Volume with the radio on. +- Radio alarms. +- Running without internet, or without Home Assistant. - Bedtime Fade and double knock on the real light. - Whether Ambient Color and Snooze Action labels match what the lamp does. diff --git a/README.md b/README.md index 3c1618c..a4efbfb 100644 --- a/README.md +++ b/README.md @@ -418,22 +418,17 @@ That manual is for a related model/revision, so use it as behavioral reference r ## TODO / worthwhile additions -For this lamp, the most worthwhile additions are: +Done: alarm data for all 4 alarms, Alarm 2 settings, sunset settings, ambient colour, snooze controls, grouped web UI, Home Assistant API, Home Assistant as a local time source, next/previous station, and diagnostics including versions and MCU link status. + +Still open: -- **Finish DP127 decoding:** weekday-bit order and unknown bytes 4, 8 and 11. -- **Find Alarm 2 extended settings:** sound, volume, wake-light level and sunrise duration are stored somewhere other than the DP127 middle block on this unit. -- **Fully decode DP128:** make sunset/sleep duration, brightness, sound/radio choice and volume writable. -- **Decode DP107:** turn the raw FM payload into actual frequency/preset information and add previous/next/scan controls; verify DP126. -- **Ambient/color lamp control:** verify DP132 on this revision and expose color/effect controls rather than only DP129 power. -- **Snooze:** verify DP116/117/130 from the exact-PID definition and expose them safely. -- **Better local web UI:** move to Web Server v3 and group Light / Clock / Alarm 1 / Alarm 2 / Radio / Sunset / Diagnostics. -- **Fully autonomous schedules:** add software-only extra alarms or weekday schedules without depending on Tuya cloud/app limits. -- **Better clock correction:** configurable local NTP server, LAN-only synchronization, and optional drift measurement/correction. -- **Astronomical sunrise/sunset:** use ESPHome's local sun calculations for real dusk/dawn behavior. -- **Home Assistant native API:** optional `api:` support while retaining the standalone web UI. -- **Diagnostics:** Wi-Fi RSSI, uptime, reboot reason, ESPHome/LibreTiny version, Tuya-link state and raw-packet viewer. -- **Identify IC1:** obtain a board with readable top marking or otherwise identify the main appliance MCU. - **Upstream the UART fix:** replace the local PA13/PA14 workaround when ESPHome gains proper LibreTiny alternate-UART pin mapping. +- **Astronomical sunrise/sunset:** use ESPHome's local sun calculations for real dusk/dawn behavior. +- **Clock drift:** optional drift measurement and correction. +- **Fully autonomous schedules:** software-only alarms, if 4 hardware alarms ever aren't enough. +- **Identify IC1:** obtain a board with readable top marking or otherwise identify the main appliance MCU. + +FM frequency isn't available: DP107 only holds the station count and current station number. ## Safety / recovery notes diff --git a/server-example.yaml b/server-example.yaml index 5b6d3b3..0adf9a8 100644 --- a/server-example.yaml +++ b/server-example.yaml @@ -7,6 +7,6 @@ substitutions: packages: lamp: url: https://mangavalk.com/mangavalk/esphome-wake-up-light-ii.git - ref: v1.0.0 + ref: v1.0.1 files: [wake-up-light-ii.yaml] refresh: 1d diff --git a/wake-up-light-ii.yaml b/wake-up-light-ii.yaml index 87e6fc5..4a35921 100644 --- a/wake-up-light-ii.yaml +++ b/wake-up-light-ii.yaml @@ -22,7 +22,7 @@ external_components: - source: type: git url: https://mangavalk.com/mangavalk/esphome-wake-up-light-ii.git - ref: v1.0.0 + ref: v1.0.1 components: [uart] refresh: 1d @@ -136,6 +136,12 @@ globals: restore_value: no initial_value: '0' + # When the MCU last sent its once-a-minute clock report. + - id: last_mcu_report + type: uint32_t + restore_value: no + initial_value: '0' + # Last display state the schedule applied: -1 none, 0 day, 1 night. - id: display_schedule_last type: int @@ -165,14 +171,22 @@ time: - script.execute: sync_lamp_clock - script.execute: apply_display_schedule - on_time: - - seconds: 0 - minutes: 0 - then: - - script.execute: sync_lamp_clock - - seconds: 0 - then: - - script.execute: apply_display_schedule + # Home Assistant is preferred when connected, so the clock doesn't need internet. + - platform: homeassistant + id: ha_time + on_time_sync: + then: + - delay: 2s + - script.execute: sync_lamp_clock + - script.execute: apply_display_schedule + +interval: + - interval: 1h + then: + - script.execute: sync_lamp_clock + - interval: 60s + then: + - script.execute: apply_display_schedule ############################################################################### # Tuya MCU @@ -188,6 +202,7 @@ tuya: datapoint_type: string then: - lambda: |- + id(last_mcu_report) = millis(); if (x.size() != 6) return; auto d = [](char c) -> int { return (c >= '0' && c <= '9') ? c - '0' : -1; }; int h1=d(x[0]), h2=d(x[1]), m1=d(x[2]), m2=d(x[3]), s1=d(x[4]), s2=d(x[5]); @@ -329,7 +344,8 @@ script: then: - lambda: |- if (!id(display_schedule_enabled).state) return; - auto now = id(network_time).now(); + auto now = id(ha_time).now(); + if (!now.is_valid()) now = id(network_time).now(); if (!now.is_valid()) return; int start = id(display_night_start).hour * 60 + id(display_night_start).minute; int end = id(display_night_end).hour * 60 + id(display_night_end).minute; @@ -370,6 +386,21 @@ script: call.set_transition_length(0); call.perform(); + - id: radio_step + mode: queued + parameters: + step: int + then: + - lambda: |- + if (!id(radio_valid) || id(radio_total) == 0) { + ESP_LOGW("radio", "No stations found yet; run Radio Scan first"); + return; + } + int total = id(radio_total); + int next = ((id(radio_current) - 1 + step) % total + total) % total + 1; + std::vector data = {id(radio_total), static_cast(next)}; + id(tuya_mcu).set_raw_datapoint_value(107, data); + - id: radio_sleep mode: restart then: @@ -426,9 +457,10 @@ script: mode: restart then: - lambda: |- - auto now = id(network_time).now(); + auto now = id(ha_time).now(); + if (!now.is_valid()) now = id(network_time).now(); if (!now.is_valid()) { - ESP_LOGW("clock", "No valid network time; use the Lamp Clock control in the web UI"); + ESP_LOGW("clock", "No valid time source; use the Lamp Clock control in the web UI"); return; } char buf[7]; @@ -932,6 +964,20 @@ text_sensor: sorting_group_id: group_device sorting_weight: 4 + - platform: version + name: "ESPHome Version" + hide_timestamp: true + web_server: + sorting_group_id: group_device + sorting_weight: 6 + + - platform: libretiny + version: + name: "LibreTiny Version" + web_server: + sorting_group_id: group_device + sorting_weight: 7 + ############################################################################### # Device health ############################################################################### @@ -969,6 +1015,16 @@ sensor: ############################################################################### binary_sensor: + # The MCU reports its clock every minute, so silence means the link is down. + - platform: template + name: "MCU Link" + device_class: connectivity + web_server: + sorting_group_id: group_device + sorting_weight: 0.5 + lambda: |- + return id(last_mcu_report) != 0 && millis() - id(last_mcu_report) < 150000; + - platform: gpio name: "Knock" icon: "mdi:gesture-tap" @@ -1049,6 +1105,28 @@ button: on_press: - lambda: id(tuya_mcu).force_set_boolean_datapoint_value(126, false); + - platform: template + name: "Next Station" + icon: "mdi:skip-next" + web_server: + sorting_group_id: group_radio + sorting_weight: 2.7 + on_press: + - script.execute: + id: radio_step + step: 1 + + - platform: template + name: "Previous Station" + icon: "mdi:skip-previous" + web_server: + sorting_group_id: group_radio + sorting_weight: 2.6 + on_press: + - script.execute: + id: radio_step + step: -1 + - platform: template name: "Start Radio Sleep Timer" icon: "mdi:timer-outline"