Add support for xiaomi.vacuum.ov42gl (H50 Pro) - #2914
Open
ShanderB wants to merge 11 commits into
Open
Conversation
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
marked this pull request as draft
August 9, 2026 21:01
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
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.
…for Xiaomi vacuum
ShanderB
marked this pull request as ready for review
August 16, 2026 22:02
ShanderB
marked this pull request as draft
August 17, 2026 17:00
ShanderB
marked this pull request as ready for review
August 17, 2026 19:42
…room information retrieval
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
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 viadisable_map_cameraindevice_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.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.ov42glentry, built from the model's full exported MIoT spec (not guessed), following the same shape already used forxiaomi.vacuum.ov31gl/ov81gl:button_actions: ~40 parameterless actions (start_sweep,pause_sweeping,continue_sweep,start_dust_arrest,reset_brush_life,identify, ...). Actions that require aninparameter (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_propertiesfor 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_propertiesfor 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_wallsstay excluded here on purpose - they're handled dynamically instead, seevacuum.pybelow.chunk_coordinatorsto poll properties in groups by how often they actually change (now also coversbase_station_working_statusin the fast 11s chunk, andorder_cleanin the slow 999s one - see below).xiaomi.vacuum.ov42gl:last_clean_timeoverride 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 viaappend_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'sdecode()caches the full parsed object so any sibling'sencode()can merge its own change back in without clobbering the rest.weekis a per-weekday bitmask (confirmed one day at a time against the app) plus an always-on flag bit;timeishour*256+minute;clean_conf.modelines up with the built-insweep_mop_typeenum.MiotFaultLabelConv:fault(siid 2 piid 3) has novalue-listin the spec at all (unlikestatus, 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 onesensor.base_station_activity.vacuum.py- newMiotOv42glVacuumEntity(MiotVacuumEntity), selected the same wayMiotRoborockVacuumEntity/MiotViomiVacuumEntityalready are but gated on spec capability (room_information+start_vacuum_room_sweeppresent) rather than a hardcoded model string, so it also covers any other model sharing the exact same spec shape: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 onestart_vacuum_room_sweepcall.{"room_attrs": [{"id": ..., "room_name": ...}]}throughset_room_clean_configs(siid 2 aiid 13) - the same action (and the same partial-write behavior) the app's own rename flow uses.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, andparse_zone_property_valuesto 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: resolvesmap-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_urlrejects this device), downloads it, and decrypts the{"version":2,"data":"<base64>"}envelope (AES-128-CBC, fixed IV, key derived only from the device's ownmodel+did- no account/session secret). Wired intoDevice.update_vacuum_map/Device.vacuum_map_propertyincore/device.pyon its own 30s cloud coordinator, decoded JSON kept onDevice.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 existingButtonSubEntity/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.pygained 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 thatorder_clean/base_station_working_statusare 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 intoDevice.update_vacuum_mapon 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 onDevice.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.Known limitations
order_clean): only tested with a single schedule slot and a singleclean_confshape (vacuum-only, no mop) - behavior with mopping enabled, non-default suction, or multiple schedules is unconfirmed."Unknown fault (code N)"/"Unknown mode (N)"instead of erroring. Happy to add more once verified.Happy to follow up on any of these if useful.