Skip to content

Add support for xiaomi.vacuum.ov42gl (H50 Pro) - #2914

Open
ShanderB wants to merge 11 commits into
al-one:masterfrom
ShanderB:add-xiaomi-vacuum-ov42gl-support
Open

Add support for xiaomi.vacuum.ov42gl (H50 Pro)#2914
ShanderB wants to merge 11 commits into
al-one:masterfrom
ShanderB:add-xiaomi-vacuum-ov42gl-support

Conversation

@ShanderB

@ShanderB ShanderB commented Aug 7, 2026

Copy link
Copy Markdown

Adds device-specific support for xiaomi.vacuum.ov42gl (H50 Pro)

TLDR

Summary

This PR adds support for the Xiaomi H50 Pro vacuum (model xiaomi.vacuum.ov42gl) to the Home Assistant Xiaomi MIoT integration, exposing roughly 150 properties and 88 actions from the device's full MIoT specification.

Features

  • Map camera (camera.map): downloads the encrypted map from Xiaomi's cloud, decrypts it (AES-128-CBC, device-specific key derivation), and renders it as a PNG — robot position, charge station, cleaned path, forbidden zones — refreshed every 30s. Individual layers (base station, robot, zones) can be toggled via native switches. Fully optional: can be disabled per-device via disable_map_camera in device_customizes, which stops the background cloud polling entirely for anyone who doesn't want it. Map listing (save/switch/rename/delete/restore saved maps) is separate, local-only, and unaffected by this flag.
  • Saved-map management: list, switch, rename, delete, restore from backup.
  • Virtual wall / restricted zone editor via dedicated number entities.
  • Per-room and multi-room cleaning with dynamically generated buttons; room names are read live from the device, not hardcoded, so renaming a room in the vendor app is picked up automatically.
  • Weekly cleaning schedule (per-day, time, mode) + do-not-disturb.
  • Extra sensors: fault (with fallback labels for unmapped codes), base station working status (mop washing/drying with progress), sewage tank status, and others.
  • Custom converters decode previously inaccessible properties, including a DND schedule packed into a single uint32 and a weekly schedule stored as JSON.

Testing

Three standalone pytest suites (no Home Assistant dependency, run via direct module loading) cover zone logic, map decrypt/encrypt round-trip, and schedule encode/decode.

Note

This integration was built end-to-end by Claude AI. I don't have a background in Home Assistant integration development; my role was testing everything on my own H50 Pro and reporting back what worked and what didn't.

Long description:

This model's full MIoT spec has 150 properties and 88 actions across 19 services, but only a curated subset becomes entities by default - most of the vacuum's actions (start/stop/pause cleaning variants, self-checks, brush/filter/dust-bag resets, etc.) had no entity at all, several properties needed decoding they don't get generically (DND schedule, weekly schedule, fault codes, base station activity), and several capabilities (per-room/multi-room cleaning, room renaming, zone/wall editing) need dedicated entities since they take parameters no plain property/action mapping can carry.

Changes

core/device_customizes.py - xiaomi.vacuum.ov42gl entry, built from the model's full exported MIoT spec (not guessed), following the same shape already used for xiaomi.vacuum.ov31gl/ov81gl:

  • button_actions: ~40 parameterless actions (start_sweep, pause_sweeping, continue_sweep, start_dust_arrest, reset_brush_life, identify, ...). Actions that require an in parameter (e.g. start_zone_sweep, start_vacuum_room_sweep) are intentionally excluded - they can't work as a bare button (see below for how those are actually handled).
  • sensor_properties/binary_sensor_properties/switch_properties for metrics and toggles not covered by default (statistical clean area, cleaning progress, last clean time, tank/mop status, DND on/off, etc.).
  • exclude_miot_services/exclude_miot_properties for internal/noisy services (map object references, voice packs, AI photo IDs, self-check) and stray string properties that would otherwise show up as unreadable raw blobs. room_information/restricted_sweep_areas/restricted_walls stay excluded here on purpose - they're handled dynamically instead, see vacuum.py below.
  • chunk_coordinators to poll properties in groups by how often they actually change (now also covers base_station_working_status in the fast 11s chunk, and order_clean in the slow 999s one - see below).
  • A xiaomi.vacuum.ov42gl:last_clean_time override so that sensor renders as a real timestamp instead of a raw epoch number.

core/converters.py - new converter classes, all wired into the entry above via append_converters:

  • MiotDndStartTimeConv/MiotDndEndTimeConv: this device's DND schedule (enable_time_period, siid 11 piid 2) has no documented encoding in the spec; it packs both times into one uint32: (start_hour << 24) | (start_minute << 16) | (end_hour << 8) | end_minute. Two time entities (time.dnd_start, time.dnd_end) read/write this one property, each doing a read-modify-write using the other's last known value so setting one never clobbers the other.
  • MiotScheduleEnabledConv/MiotScheduleTimeConv/MiotScheduleModeConv/MiotScheduleDay<Weekday>Conv (10 converters total): the weekly cleaning schedule (order_clean, siid 2 piid 19) is a string-format property holding one JSON object with 5 parallel arrays (id/on/week/time/clean_conf); only the single slot at index 0 is managed, matching what the app's own UI exposes for this device. Same read-modify-write idiom as the DND pair - every field converter's decode() caches the full parsed object so any sibling's encode() can merge its own change back in without clobbering the rest. week is a per-weekday bitmask (confirmed one day at a time against the app) plus an always-on flag bit; time is hour*256+minute; clean_conf.mode lines up with the built-in sweep_mop_type enum.
  • MiotFaultLabelConv: fault (siid 2 piid 3) has no value-list in the spec at all (unlike status, which already has a full value-list and needed no new code). Labels are the handful of codes confirmed by triggering real faults and reading this property while the app showed the matching error (wheel error, water tank empty/full, dust compartment missing, wheels suspended, path blocked); unmapped codes fall back to "Unknown fault (code N)" instead of erroring.
  • MiotBaseStationModeConv: base_station_working_status (siid 2 piid 18) is JSON ({"mode":N,"progress":N}), previously fully excluded; decodes the 3 confirmed modes (Drying / Dust Emptying / Mop Washing) plus progress into one sensor.base_station_activity.

vacuum.py - new MiotOv42glVacuumEntity(MiotVacuumEntity), selected the same way MiotRoborockVacuumEntity/MiotViomiVacuumEntity already are but gated on spec capability (room_information + start_vacuum_room_sweep present) rather than a hardcoded model string, so it also covers any other model sharing the exact same spec shape:

  • Per-room and multi-room cleaning: one button per room (built dynamically from room_information, the same way the existing Roborock segment-button code already builds its own per-room buttons), one selection switch per room (pure in-memory UI staging, no MIoT property backing it), and a "Clean Selected Rooms" button that joins whichever rooms are selected into one start_vacuum_room_sweep call.
  • Room renaming: writes {"room_attrs": [{"id": ..., "room_name": ...}]} through set_room_clean_configs (siid 2 aiid 13) - the same action (and the same partial-write behavior) the app's own rename flow uses.
  • Virtual wall / restricted zone editor: 4 number entities (corners in millimeters), a zone-type select (virtual wall / no-sweep-and-mop / no-mop-only), and add/remove buttons. Both zone properties (restricted_sweep_areas/restricted_walls, siid 2 piid 13/14) are read+write, so the current list for the "Zone to Remove" selector is read directly off the device at setup and kept in sync locally after every HA-side edit - no cloud dependency.

core/vacuum_zones.py (new) - the pure zone/wall logic: corner-ordering math (rect_to_fb_point - matches the app's own rectangle tool exactly, order matters beyond just tracing the boundary), full-list rebuilding (the app always rewrites the whole property, never appends), the 10-shape combined cap the app also enforces client-side, and parse_zone_property_values to read the device's own JSON shape back into a plain list. Unit-tested independently of Home Assistant.

core/vacuum_map.py (new) - downloads and decrypts this model's cloud map file: resolves map-obj-name (siid 10 piid 1) locally, exchanges it for a signed URL via /v2/home/get_interim_file_url_pro (the generic /v2/home/get_interim_file_url rejects this device), downloads it, and decrypts the {"version":2,"data":"<base64>"} envelope (AES-128-CBC, fixed IV, key derived only from the device's own model + did - no account/session secret). Wired into Device.update_vacuum_map/Device.vacuum_map_property in core/device.py on its own 30s cloud coordinator, decoded JSON kept on Device.data['vacuum_map']. See "About the map" below - this is download/decrypt only, nothing renders it.

number.py/text.py - two new generic building blocks, NumberSubEntity/TextSubEntity, following the exact same shape as the existing ButtonSubEntity/SelectSubEntity (button.py/select.py): a sub-entity with no backing MIoT property, for pure UI staging values or writes that need a custom action rather than a plain property. Used by the zone editor and room renaming above, reusable elsewhere.

Tests: test_xiaomi_vacuum_ov42gl_zones.py (new) covers the pure zone-list logic; test_xiaomi_vacuum_ov42gl_map.py (new) covers the map key derivation and decrypt round-trip; test_xiaomi_vacuum_ov42gl.py gained coverage for the schedule decode/encode (against a real captured sample: Mon+Wed+Fri at 09:46), the fault label lookup, and the base-station-activity decode, plus an update to the "noisy properties are excluded" test now that order_clean/base_station_working_status are intentionally no longer in that list.

About the map

This model doesn't fit the format any existing map-extractor tooling supports (it's manufactured by 3iRobotics, not Roborock/Dreame/Viomi) - the working cloud endpoint and the AES decrypt. That part is included in this PR (core/vacuum_map.py, wired into Device.update_vacuum_map on its own 30s coordinator whenever a cloud login is configured) - the decoded map JSON (rooms/walls grid, restricted zones/virtual walls, robot pose, cleaning trail) is downloaded, decrypted, and kept ready on Device.data['vacuum_map'].

Practical effect: creating a zone/wall means typing millimeter coordinates into the number entities rather than clicking two points on a picture. Adding a visual map later only needs a new entity that reads Device.data['vacuum_map'] - the download, decrypt, and polling are already done here; it deserves its own PR and review rather than being bundled into this one.

image I took an image from my map and asked ChatGPT to change the rooms. I do not want to post my home layout on internet, but it is working and you can test it. This is fully decoded on HA. I could follow the path where the robot is sweeping/mopping.

Known limitations

  • Weekly cleaning schedule (order_clean): only tested with a single schedule slot and a single clean_conf shape (vacuum-only, no mop) - behavior with mopping enabled, non-default suction, or multiple schedules is unconfirmed.
  • Fault code and base-station-activity labels: only a handful of numeric codes have been confirmed against real hardware; unmapped codes/modes show as "Unknown fault (code N)"/"Unknown mode (N)" instead of erroring. Happy to add more once verified.
  • The zone/wall list is read once at startup and updated locally after every HA-side add/remove; a change made from the HA needs a restart to show up here (the device doesn't reflect a write back into these properties synchronously enough to just re-read them right after, so this mirrors the room list's own restart-to-refresh behavior).

Happy to follow up on any of these if useful.

Add a device_customizes entry exposing the button actions, extra
sensors/switches/binary_sensors and polling groups that this vacuum's
full MIoT spec (150 properties / 88 actions across 19 services)
supports but generic entity generation misses, following the same
pattern already used for xiaomi.vacuum.ov31gl/ov81gl.

Add MiotDndStartTimeConv/MiotDndEndTimeConv to decode/encode the DND
schedule (`enable_time_period`, siid 11 piid 2), which packs the start
and end time into a single uint32 as
(start_hour<<24)|(start_minute<<16)|(end_hour<<8)|end_minute. Writing
one half does a read-modify-write against the other half's last known
value so it's never clobbered. The packing was verified against two
independently confirmed raw values captured from the real Xiaomi Home
app; both are covered in tests/test_xiaomi_vacuum_ov42gl.py
@ShanderB
ShanderB marked this pull request as draft August 9, 2026 21:01
@ShanderB
ShanderB marked this pull request as ready for review August 9, 2026 21:01
… vacuum

- Implemented `vacuum_zones.py` to handle virtual walls and restricted zones for the Xiaomi vacuum (H50 Pro).
- Added methods for parsing, building, and writing zone data to the device.
- Enhanced `vacuum.py` to integrate zone management features, including adding and removing zones.
- Created staging entities for zone type selection and zone removal.
- Added tests for zone management logic, ensuring correct payload handling and state management.
- Introduced pure-logic tests for the zone editor helpers to validate functionality without Home Assistant dependencies.
@ShanderB
ShanderB marked this pull request as draft August 11, 2026 22:47
…acuum

- Implemented vacuum schedule helpers in vacuum_schedule.py to manage cleaning schedules and Do Not Disturb (DND) periods.
- Added area sweep functionality in vacuum_area_sweep.py to allow ad-hoc cleaning of specified areas without saving them as permanent zones.
- Updated vacuum.py to integrate new schedule and area sweep features, including the addition of new entities for managing schedules and DND settings.
- Enhanced fault labels in converters.py to include a new error message for charging station issues.
- Created unit tests for the new schedule and area sweep functionalities to ensure proper behavior and integration.
@ShanderB
ShanderB marked this pull request as ready for review August 16, 2026 22:02
@ShanderB
ShanderB marked this pull request as draft August 17, 2026 17:00
@ShanderB
ShanderB marked this pull request as ready for review August 17, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant