Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa929478f6 | ||
|
|
ae17892441 |
No files matched your search
@@ -0,0 +1,81 @@
|
||||
# Wake Up Light II notes
|
||||
|
||||
ESPHome firmware for a Moes Wake Up Light II (Tuya PID `ya39qahrcdhvxowz`), running fully local. This repo is public, so never commit secrets, Wi-Fi details or device keys.
|
||||
|
||||
## Layout
|
||||
|
||||
- `wake-up-light-ii.yaml`: main config.
|
||||
- `alarm_block.yaml`: controls for one alarm, included 4 times.
|
||||
- `alarm_day.yaml`: one weekday switch, included 7 times per alarm.
|
||||
- `components/uart`: ESPHome 2026.10.2's UART component with `patches/wbr3-pa13-pa14-uart.patch` applied. Recheck it when ESPHome updates.
|
||||
- `server-example.yaml`: the short YAML that goes on an ESPHome server.
|
||||
- `secrets.yaml`: local only, ignored by git. Needs `wifi_ssid`, `wifi_password`, `ap_password`, `api_encryption_key`.
|
||||
|
||||
## Releasing
|
||||
|
||||
1. Set the `ref` in `wake-up-light-ii.yaml`'s `external_components` to the new tag, then commit and tag that commit.
|
||||
2. Push the branch and the tag. On the owner's PC the first push can fail authentication, and a second try works.
|
||||
3. Update `ref` in the server's YAML, then press Install. OTA works, and the lamp restarts by itself.
|
||||
|
||||
Build from a short folder path: Windows path limits break the libsodium build otherwise.
|
||||
|
||||
## Hardware
|
||||
|
||||
- WBR3 module (RTL8720CF, 2 MiB flash), talking TuyaMCU at 9600 8N1 on PA13 (RX) and PA14 (TX).
|
||||
- Knock sensor: a switch between 3.3V and PA18, using the internal pull-down.
|
||||
- UART flashing needs the module desoldered, because PA00 is on the underside. Adapter TX to PA15, RX to PA16, plus GND and a solid 3.3V. Hold PA00 at 3.3V, then tap CEN to GND. PA13 must not be pulled low.
|
||||
- Serial reads with `ltchiptool` run at about 3 KB/s, so a full 2 MiB backup takes about 16 minutes.
|
||||
- If the serial port says "access denied", another app has it. On the owner's PC that was SignalRGB.
|
||||
|
||||
## Data model
|
||||
|
||||
From Tuya's definition for a sibling product (tuya-local issue #3821). See the README's corrections section for the byte layouts of DP127, DP128 and DP107.
|
||||
|
||||
| DP | Meaning |
|
||||
|---|---|
|
||||
| 101, 102 | Main light, brightness 10-1000 |
|
||||
| 103 | Clock, ASCII HHMMSS |
|
||||
| 104 | Display: 0 off, 1 day, 2 night, 3 auto |
|
||||
| 105, 106 | Radio on, volume 1-16 |
|
||||
| 107 | Stations found, current station |
|
||||
| 109, 122, 123, 124 | Alarms 1 to 4 on |
|
||||
| 116, 117 | Snooze minutes 8-15, snooze action enum |
|
||||
| 121, 128 | Sunset on, sunset settings |
|
||||
| 125 | Online time sync |
|
||||
| 126 | Radio scan |
|
||||
| 127 | Alarm settings, 48 bytes on this unit |
|
||||
| 129, 132 | Ambient light on, colour enum |
|
||||
| 130 | Snooze |
|
||||
| 131 | 24-hour clock |
|
||||
|
||||
## Observed behaviour
|
||||
|
||||
- A raw UART capture of a ring, radio, scan and sunset showed only heartbeats and normal datapoint frames. No other message types, and no datapoints beyond these 22.
|
||||
- There is no "ringing" datapoint. A ring starts with DP101, 105, 121, 129 and 130 all OFF in one burst (3 of 3 rings). Arming an alarm inside its sunrise window sends only DP101, 121 and 129.
|
||||
- The stop and volume buttons send nothing to the chip, and neither did the window where a ring should have stopped by itself. So a hand-stopped alarm can't be detected without extra hardware.
|
||||
- A knock during a ring snoozes it. DP130 then stays ON. Outside an alarm it returns to OFF at once.
|
||||
- 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 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.
|
||||
|
||||
## Status
|
||||
|
||||
Verified:
|
||||
- Alarm and sunset settings are echoed by the MCU.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
Decided against for now: an Alarm Ringing sensor, and a web UI password.
|
||||
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
+90
-12
@@ -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<uint8_t> data = {id(radio_total), static_cast<uint8_t>(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"
|
||||
|
||||
Reference in new issue
Block a user