Skip to content

andy778/UClean1

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

106 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Uponor Clean 1

OpenSSF Scorecard

Investigate if its possible to get Uponor Clean 1 into Home Assistant

Hypothesis

  • From the modem port connect an ESPHOME (e.g ESP32 S3) and insert values into Home Assistant
  • It's an 868.2 MHz (channel 117) radio between inner unit and outer unit, and make decoding of this device in rtl_433

PCB with text

Status

Confirmed: serial/GSM telemetry (CYCLE COUNTER/PLANT STATUS/ALARM STATUS over the modem port, no SIM needed); the 868.2 MHz radio frame (address, 32-byte payload, CRC-16) and its messaging protocol (node IDs, heartbeat, all 5 alarm types); the E-code/S-code/radio-type numbering; the 7-channel actuator output mux (MV1MV5/Compressor/Spare) and its self-test → alarm-code path; the High water sensor input, traced end to end from silkscreen to alarm code.

Open — highest value first: the RF service/handheld command protocol (needs a live capture, static analysis can't get further); the physical Test-button GPIO/timer; the other 3 sensor inputs (Startup level, Chemical pressure, Spare have no traced firmware consumer yet); an ESP32+nRF905 TX proof-of-concept (radio config is fully known, but nothing has transmitted yet — the actual open question is whether the real panel accepts it).

Where to look:

Doc Covers
docs/u2-serial-protocol.md Serial/GSM telemetry — the working Home Assistant route
docs/rtl433-decoder.md Radio frame spec, capture recipes, rtl_433 decoder
docs/ghidra/README.md Payload byte map, who writes/sends it
docs/eeprom-map.md EEPROM layout, alarm/E-code table, actuator + sensor firmware traces
docs/ghidra/codes.md The 3 separate numbering spaces (E-code/S-code/radio type), kept apart
docs/ghidra-firmware-analysis.md How the MC9S08 Ghidra analysis was run, driver map
docs/nrf9e5-firmware.md U1's embedded 8051/radio firmware
docs/ghidra/register-map.md Peripheral registers, cross-checked against datasheets

Two boards (dump vs. radio provenance)

This project uses two physical Clean 1 boards, and it matters which artifact came from which:

  • Board A — the original, retired after OC13 (a MOC3063 output-driver optotriac, one of the OC8–OC14 channels) failed. All firmware and memory dumps in dumps/ were read from board A (u2-mc9s08gt-flash.*, u3-m24128-eeprom.bin). OC13 is only an output stage, so its CPU / serial / EEPROM / radio are intact — board A is the safe bench mule.
  • Board B — the new active replacement, currently installed. All 868 MHz rtl_433 radio captures come from board B, as does the display's cycle counter (satsräknare).

So firmware/EEPROM analysis describes board A and on-air behaviour describes board B. They are the same model but not guaranteed byte-identical — the EEPROM config (code + phone numbers) and the counter differ per board, and the radios may be individually paired. Confirm SW Ver: / Proc Ver: match on both before using board-A firmware to explain board-B radio.

Modem port / serial (Path A) — working

This is now the validated primary telemetry route. The U2 CPU runs a full GSM/SMS controller: it drives a Telit modem over SCI2 at 9600 8N1 and answers a text command interface that reports the plant state in plain ASCII.

Impersonating that modem on the bench (no SIM) reads it directly — send the unit's 4-character access code and it replies:

CYCLE COUNTER:2129
PLANT STATUS:S102      (S102 = Aeration; S1xx clean / S2xx wait / S3xx maint / S4xx test)
ALARM STATUS:NO

The J5 "Modem" header is RS-232 via U10/SP3232 (not TTL), which is why the first attempt — a bare USB-serial cable sweeping baud rates — saw nothing: it was reading inverted RS-232 levels, and with no modem attached U2 mostly stays quiet. Tap the SP3232 TTL side (or use an RS-232 adapter), impersonate the modem, and the telemetry falls out. Full write-up, wiring, command grammar, and the tools/fake_telit.py bench tool: docs/u2-serial-protocol.md.

Datasheets

No Description IC
U1 Transceiver nRF9E5
U2 CPU MC9S08GT32ACFBE
U10 Serial sipex 3232
U3 I2C Mem 4128BWP 8424K
OC8–OC14 Optoisolator MOC3063 1512
D6–D12 Mosfet VNE46 AC 4DLMG
OC1–OC7 Mosfet 1435 814

The board carries a row of seven identical output-driver channels (the repeated stages that switch the magnetventiler MV1–MV5, the compressor and the pumps), so the designators fall into three matched series — one part per channel:

  • D6–D12 are all the same VNE46 mosfet as D11.
  • OC1–OC7 are all the same 1435 814 mosfet as OC6.
  • OC8–OC14 are all the same MOC3063 optoisolator as OC13.

The two OC series interleave across the row as two descending runs (…OC14, OC7, OC13, OC6, OC12, OC5… reading left to right), read off the high-resolution silkscreen in docs/uclean1-pcb.jpg.

Confirmed by silkscreen (a second board photo, terminal-block edge): the output row's screw terminals are labelled, left to right, MV1 Compressor, MV2, MV3, MV4, MV5, Spare, Alarm — matching docs/eeprom-map.md's EEPROM-derived fault table exactly (E040 compressor, E041E045 = MV1–MV5) and confirming Spare as a genuine unused 7th channel, not a numbering gap. Firmware-confirmed (FUN_a138/ FUN_c9e1, docs/eeprom-map.md): MV1 Compressor is actually two separate adjacent channels (Compressor = index 0, MV1 = index 1) that just read as one combined label on the silkscreen — the self-test loop addresses all 7 channels through one shared PTBD mux bus, not individual GPIOs, and its per-channel fault codes (channel_index + 0x28) match E040E045 exactly. Alarm is a separate 2-pin terminal (not part of the output row) — likely a dry-contact/relay output for an external alarm siren or building-management input, not yet traced in firmware.

Same photo also shows, near U2: a 4-digit 7-segment display, a physical push-button (S1) silkscreened Test directly above the display, and a 2-pin screw terminal (J16) silkscreened Test btn directly below it — plus a TEST 464 PASSED QC sticker elsewhere on the board (unrelated; just a factory QC-pass label).

Correction from an earlier read of this photo: S1 and J16 are not separate factory-vs-field interfaces — they're adjacent, on the same small sub-circuit next to the display, and confirmed by a continuity check (multimeter, S1's pads to J16's terminals) to be the same net: S1 is the onboard equivalent of the customer's external green Test button (the manual's 5s/10s/14s hold behavior, eeprom-map.md), not a separate factory-only path. This also fits the firmware: codes.md found only one flag (DAT_014a) for the physical button's long-press behavior, no evidence of two independent button inputs. The QC sticker was unrelated electrically and shouldn't have been cited as evidence either way. Distinct from the still-open "handheld service device" question in docs/ghidra/codes.md. Also visible: a gold SMA-style antenna connector next to U1, and the Modem header from docs/u2-serial-protocol.md.

Sensor inputs, confirmed by silkscreen (J4, top-left, 8-pin screw terminal): the board has exactly 4 sensor inputs, left to right: Startup level, High water, Chemical pressure, Spare. This resolves the "High water?" uncertainty in docs/eeprom-map.md — it's the confirmed silkscreen name, not a guess — and shows the chemical/flocculant side uses a pressure sensor, not a level float. Spare here is a second genuine unused channel (distinct from the output row's Spare). Two 14-pin SOICs (U4/U5, part numbers not legible in the photo) sit directly behind the header, likely conditioning the 4 raw signals before U2 reads them. Firmware-traced for the High water channel specifically (docs/eeprom-map.md): sensor → mux → FUN_a0da poll → FUN_c03c debounce → alarm code 0x1F, exactly EEPROM E031 "High water; tank". The other 3 channels don't have an equivalent traced handler yet.

I2C Memory (U3, 128-Kbit / 16 KB)

U3 is an M24128 EEPROM on the I2C bus at address 0x50, read with a Raspberry Pi and i2c-tools. Wiring the Pi to the chip, reading the full 16 KB (the i2cdump above only reaches the first 256 bytes), and the dump analysis now live on their own page: docs/eeprom-map.md.

Flash content

To read out the flash content from the CPU using reader like USBDM will get you and srecord dumps/u2-mc9s08gt-flash.s19 file. This file one need to convert to binary file dumps/u2-mc9s08gt-flash.bin with e.g objcopy.

objcopy --input-target=srec --output-target=binary dumps/u2-mc9s08gt-flash.s19 dumps/u2-mc9s08gt-flash.bin

Reading the flash back out (BDM dump vs. security)

Reading the flash is a debug operation, not a programming one. A plain flash programmer only exposes the program flow (blank-check → erase → program → verify) and has no "read". To dump memory you use the BDM debug channel and a tool that issues the BDM memory-read command:

  • USBDM + GDB server — start the HCS08 GDB server that ships with USBDM, then in the HCS08 gdb:

    target remote localhost:1234
    dump srec memory flash.s19 0x8000 0x10000   # 32 KB flash of the GT32
    dump srec memory ram.s19   0x0080 0x1080    # RAM, optional
    

    dump srec memory FILE START END walks the range over BDM and writes an S-record (use dump ihex memory … for Intel-hex). MC9S08GT32 map: 32 KB flash at 0x8000–0xFFFF, RAM at 0x0080–0x107F.

  • CodeWarrior / P&E Multilink — connect in the debugger and use Import/Export Memory → Export (or the save command) over the flash range.

Flash security caveat. If read-back is blocked, the part is secured: the FSEC byte at 0xFFBF (SEC01:SEC00) makes a programmer able only to mass-erase, never read. When secured, BDM reads of flash and RAM are blocked and the only legal operation is a mass-erase (which unsecures by wiping everything — useless for dumping). The one non-destructive escape is the 8-byte backdoor key at 0xFFB0–0xFFB7, if the firmware enabled KEYEN and you know it. The existing dumps/u2-mc9s08gt-flash.s19 came out cleanly, so U2 on this board was not secured — a straight BDM dump is enough.

Firmware analysis (Path B)

The U2 CPU flash (dumps/u2-mc9s08gt-flash.s19) was disassembled in Ghidra. How the headless analysis was run, the gotchas, and the recovered driver map (I2C EEPROM access + the SPI-slave finding) now live on their own page: docs/ghidra-firmware-analysis.md.

nRF9E5 firmware (Path C)

U1's radio firmware (8051, on-air Manchester/frame/nRF905 config) has no external boot chip — it's embedded in the U2 flash we already dumped, and has been extracted + disassembled. See docs/nrf9e5-firmware.md.

Radio decoding (rtl_433)

The 868 MHz frame (address, 32-byte payload, CRC-16) is confirmed and the decoder is enabled and CRC-gated in the fork: andy778/rtl_433, branch add-uponor-clean1. Spec, byte mapping, capture recipes, and what's still open: docs/rtl433-decoder.md.

About

Uponor Clean 1 into Home Assistant

Resources

License

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors