Exposing the full A/C over Matter: wiring plan
Grounded in the SDK + the hardware-validated driver. Base example is
room_air_conditioner(make aircon_port), NOT the bare thermostat. The RS-485 driver (firmware/src/rs485-driver/hisense_rs485.{h,cpp}) is now fully sniff-validated against real W41H1 frames, both command (TX) and status (RX), so these phases carry no protocol risk, only Matter-glue plumbing.
Real SDK files to touch (under sdk/ameba-rtos-z2/.../application/matter/)
examples/room_air_conditioner/matter_drivers.cpp: the glue:matter_driver_uplink_update_handler(AppEvent*): Matter attribute WRITE → dispatch onpath.mClusterId. TheClusters::Thermostat::Idcase is empty: our main hook.FanControl+OnOffcases already exist (extend them).matter_driver_downlink_update_handler(AppEvent*): device state → Matter attribute store, viaAppEvent::kEventType_Downlink_*_SetValue. Add Thermostat/Fan event types.matter_driver_room_aircon_init(): currently wires a DHT11 + PWM fan. Replace withhisense_init(hisense_on_status)+ ahisense_poll_task(see rs485INTEGRATION.md).
drivers/device/room_aircon_driver.{h,cpp}:MatterRoomAirCon::{Thermostat,FanControl}are PWM/in-memory stubs; the setters stay, their I/O becomes RS-485.examples/room_air_conditioner/room-air-conditioner-app.zap: cluster/attribute config. FanControl already hasSpeedSetting/SpeedMax/RockSetting/AirflowDirectionattributes; just enable the FeatureMap bits. Regenerate with the ZAP tool after editing.
Field mapping (all encodings hardware-confirmed: see rs485 INTEGRATION.md)
| Matter | ↔ | driver | notes |
|---|---|---|---|
Thermostat SystemMode |
↔ | HisenseCommand.mode / HisenseState.mode |
Off→power frame; Cool3/Heat4/FanOnly7/Dry8/Auto1. AUTO cmd byte18=0x90 (idx4), status nibble 5 |
Thermostat OccupiedCooling/HeatingSetpoint |
→ | .setpoint |
int16 ÷100 → whole °C, 16–32 |
Thermostat LocalTemperature |
← | .indoor_temp_c ×100 |
direct °C from status @20 |
FanControl SpeedSetting (SpeedMax=6) |
↔ | fan | 6 speeds: idx 1,5,6,7,8,9 → cmd 0x03/0B/0D/0F/11/13 |
FanControl RockSetting |
↔ | .vswing/.hswing |
bit1=up/down (cmd byte32 0xC0), bit0=left/right (⚠ cmd untested) |
| OnOff | → | hisense_build_power_frame |
on[]/off[] literals |
| manuf cluster 0xFC00 | ↔ | feature/sleep/flags | see below |
Manufacturer cluster (Phase 3): chosen approach
Cluster id (VID << 16) | 0xFC00 (VID from the attestation certs):
0x0000 Ecobool →feature=ECO(cmd byte33 0x30), status flags1 bit20x0001 Turbobool →feature=TURBO(cmd byte33 0x0C), status flags1 bit10x0002 Mutebool → cmd byte35 0x30/0x10, status flags2 bit20x0003 SleepProfileenum8 0-4 → cmd byte17 *profile2+1*, status @17 profile20x0010 CompressorHzuint8 RO ← status @420x0011 OutdoorTempint8 RO ← status @44
Phases (each a go/no-go gate)
- Toolchain + attestation gate. Build STOCK
aircon_port+is_matter, flash via CH341A SPI clip, commission in HA, REQUIRES the DAC/PAI/CD fix (docs/02, theerr 604defect). Nothing proceeds until a stock image commissions. - Core control (no ZAP change): swap stubs →
hisense_init+poll; fill Thermostat uplink (SystemMode/setpoints), OnOff→power; downlink LocalTemperature/SystemMode/fan% via new AppEvents. Gate: mode+temp+on/off from HA. - Fan + swing (ZAP): FanControl FeatureMap += MultiSpeed(SpeedMax=6)+Rocking+AirflowDir; Thermostat Cooling+Heating features (setpoints 16–32). Map SpeedSetting→6 fan idx, RockSetting→v/h swing. Gate: 6 speeds + swing from HA.
- Exotics: add the 0xFC00 manufacturer cluster above; wire uplink→HisenseCommand, downlink←status flags.
- Done: outdoor temp now has its own TemperatureMeasurement
endpoint (ep2); the humidity endpoint was dropped rather than fed (W41H1 status humidity isn’t
mapped). CompressorHz now ticks in the
.zapand reads live (v1.1.4, #38/#82), see below. - Ship: rebuild, flash via clip, commission, verify every control end-to-end. Done: multiple
fielded builds since (
docs/13).
Manufacturer-cluster attach mechanics + telemetry gap
The 0xFFF1FC00 manufacturer cluster attaches to endpoint 1 with 4 live attributes:
Eco/Turbo/Mute bool + SleepProfile enum. The uplink handler maps attr 0x0000-0x0003 →
HisenseFeature/hisense_build_mute_frame/_sleep_frame; downlink mirrors those status flags
back via raw emberAfWriteAttribute. SDK edits captured in firmware/src/sdk-edits/.
⚠️ Telemetry gap, closed.
OutdoorTemp(0x0011) shipped redirected to a standardTemperatureMeasurementendpoint (ep2, HA-readable) instead of the mfg attr, exactly as this section originally proposed; seedocs/07’s parity matrix. The unfed Humidity endpoint was dropped rather than fixed, so ep2 no longer enumerates Humidity at all.CompressorHzshipped in v1.1.4 (#38/#82): matter-server stores it by raw numeric attribute path with no cluster registration needed, and thehisense-unified-acHACS integration renders it assensor.*_compressor_frequency(docs/14).
Attaching the cluster takes more than the GUI tick: a new manufacturer cluster only attaches from a
tool-authored .zap (hand-edits are recognized but not endpoint-counted), AND, because
CHIP only bakes the Id/callbacks into zzz_generated for clusters it ships, needs a
minimal set of generated-table edits (ClusterId.h + include + callback decls + no-op
defs). The clean-but-heavy alternative is zap_regen_all.py. Full recipe in
firmware/src/sdk-edits/README.md.
This doc is the sole source of truth for the manufacturer-cluster layout and the CompressorHz/OutdoorTemp telemetry gap above, other docs (e.g.
docs/05) cite it rather than restate the byte-level mapping.
Residuals carried from driver validation (non-blocking)
- hswing command untested (this unit’s app has no H-swing; remote is IR-only). Status read-back works; the command (byte32 bits4-5 + byte37=0x14) is reference-inherited.
- heat-relay bit = aux/PTC electric heat; only asserts in cold/defrost.
- Combined-frame
HisenseCommandwrites vs the reference’s one-field-per-frame: bench-test early (rs485 INTEGRATION.md §6); fall back to per-field frames if the A/C ignores combined.