Просмотр исходного кода

Leo's Loo Too / MEET: fix weight scale, decode notifications, add MEET variant (#5488)

* Leo's Loo Too / MEET: fix weight scale, decode notifications, add MEET

- weight DP6 scale 0.1 -> 10 (raw is tenths of a kg; 0.1 produced 420 kg),
  matching the other weight litterbox configs (duoqu, vevor, homall)
- add the "MEET" product id (kw4ztiq3kxmcf8dx) seen on identical hardware;
  mark DP2 (mode) and DP5 (delay) optional as the MEET variant omits them
- DP22 fault bitfield -> problem binary_sensor (as sailesi_litterbox)
- decode the DP21 notification bitfield into Bin full / Cleaning /
  UV sterilizing binary_sensors, keeping the raw code as a diagnostic.
  Bits confirmed against observed device behaviour across four units;
  bit0 = bin full was ground-truthed against the physical amber state.
  These are plain device states (no problem/alert semantics) - alerting
  is left to the user's automations
- remove DP7 "times used today": dead constant (=1) that never updates
  locally on any observed unit (also =1 in the dump in #1840)
- remove the DP16 "light" entity: DP16 does not exist on this hardware
- rename the config from the generic "Pet toilet" to "Leo's Loo Too"
  (matched by product id, so other pet toilets are unaffected)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Refine DP21/DP22 bit labels from the device's Tuya data model

The device's own Tuya data model (the notification/fault status_range,
read from the cloud device spec) gives authoritative bit labels:
  DP21 notification: garbage_box_full, excessive_clean_times, cleaning,
                     no_weight
  DP22 fault:        motor_fault, program_fault, g_sensor_fault
These independently confirm the empirically-decoded bit0 = bin full and
the DP22 fault bits, and refine the rest:
  - "Cleaning" now maps bit2 (4) = "cleaning" only, not mask 6 (bit1 is a
    separate excessive_clean_times warning, not a cleaning-active bit)
  - bit6 (64) UV is noted as an undocumented device extension
  - fault bit labels added as comments

Also clarify two removals:
  - DP7 (code excretion_times_day) always reads 1 locally because it is a
    per-use event pulse the Tuya cloud accumulates server-side
    (report_type "sum"); the local value never changes.
  - DP16 light exists in the data model but is never reported on the
    local protocol, so it only stranded an unavailable entity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix DP21 bit1: it is clean-active, not excessive_clean_times

A controlled manual-clean test (timed button press, watched on the
device) settles the DP21 bit1 meaning against the Tuya data model:

  press -> DP21 0->2, held for 71 s (the full clean rotation) -> 0
        -> DP21 0->64 (UV) 6 s later

That is the identical lifecycle to a bit2 auto-clean, just signalled on
bit1. So bit1 (2) = manual/commanded clean rotation active, a sustained
clean state - NOT the "excessive_clean_times" momentary warning the Tuya
data model labels it. A prior commit had trusted that label and narrowed
"Cleaning" to bit2 only (mask 4), which silently dropped every manual and
reset clean.

Restore "Cleaning" to mask 6 (bits 1|2 = any clean rotation) and rewrite
the bit comments to lead with observed behaviour, flagging the model's
bit1 mislabel. Also mark the DP22 fault bit labels as unverified (no fault
ever fired on any unit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Address review: keep DP7/DP16 hidden, fold raw code into Fault, fix brand

Per @make-all's review on #5488:

- DP7 "times used today": rather than removing it (which would leave a
  zombie entity in existing installs), keep it and hide it - add an
  `available` dp that tests the raw value for null and set
  `hidden: unavailable`, so it shows only where the dp is reported.
- DP16 "light": likewise kept with `hidden: true` instead of removed.
- Raw DP21 notification code: drop the standalone sensor and carry it as
  a `notification_code` attribute on the DP22 Fault binary_sensor.
- Product kw4ztiq3kxmcf8dx: this is the same physical Leo's Loo Too, just
  carrying the MEET / Chinese-market firmware (owner-confirmed), so keep
  it under manufacturer "Leo's Loo" with model "Too (MEET firmware)".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Address review: trim comments to device facts, restore generic name + DP7 position

- drop the explanatory header block; trim inline comments to device
  facts only (rationale belongs in the PR/commit history, not the YAML)
- restore top-level name: Pet toilet (the config can match other
  devices, so the generic name must not be rebranded)
- move the DP7 "Times used today" entity back to its original position
  to keep the diff to real changes

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jason Rumney <make-all@users.noreply.github.com>
A. J. Wright 14 часов назад
Родитель
Сommit
b41ea1795b
1 измененных файлов с 97 добавлено и 1 удалено
  1. 97 1
      custom_components/tuya_local/devices/leos_loo_too.yaml

+ 97 - 1
custom_components/tuya_local/devices/leos_loo_too.yaml

@@ -3,14 +3,31 @@ products:
   - id: 1badzyvwh1e1hrog
   - id: 1badzyvwh1e1hrog
     manufacturer: "Leo's Loo"
     manufacturer: "Leo's Loo"
     model: Too
     model: Too
+  # Same hardware under the MEET / Chinese-market firmware, which enrols under a
+  # different Tuya product id - not a separate product.
+  - id: kw4ztiq3kxmcf8dx
+    manufacturer: "Leo's Loo"
+    model: Too (MEET firmware)
 entities:
 entities:
   - entity: sensor
   - entity: sensor
     name: Times used today
     name: Times used today
     icon: "mdi:counter"
     icon: "mdi:counter"
+    # Constant 1 on the local protocol; the cloud sums per-use pulses
+    # server-side, so the LAN value never changes.
+    hidden: unavailable
     dps:
     dps:
       - id: 7
       - id: 7
         type: integer
         type: integer
         name: sensor
         name: sensor
+        optional: true
+      - id: 7
+        type: integer
+        name: available
+        optional: true
+        mapping:
+          - dps_val: null
+            value: false
+          - value: true
   - entity: switch
   - entity: switch
     name: Power
     name: Power
     category: config
     category: config
@@ -27,6 +44,8 @@ entities:
       - id: 2
       - id: 2
         type: string
         type: string
         name: option
         name: option
+        # Absent on some units (the MEET variant omits it).
+        optional: true
         mapping:
         mapping:
           - dps_val: auto_clean
           - dps_val: auto_clean
             value: Auto clean
             value: Auto clean
@@ -40,8 +59,18 @@ entities:
       - id: 13
       - id: 13
         name: switch
         name: switch
         type: boolean
         type: boolean
+  - entity: switch
+    translation_key: sleep
+    category: config
+    class: switch
+    dps:
+      - id: 10
+        name: switch
+        type: boolean
   - entity: light
   - entity: light
     category: config
     category: config
+    # Not reported on the local protocol here.
+    hidden: true
     dps:
     dps:
       - id: 16
       - id: 16
         name: switch
         name: switch
@@ -56,6 +85,8 @@ entities:
       - id: 5
       - id: 5
         type: integer
         type: integer
         name: value
         name: value
+        # Absent on the MEET variant.
+        optional: true
         range:
         range:
           min: 6
           min: 6
           max: 1200
           max: 1200
@@ -69,8 +100,11 @@ entities:
         type: integer
         type: integer
         name: sensor
         name: sensor
         unit: kg
         unit: kg
+        # Live pad load (box + litter + waste), not a per-cat weight.
+        class: measurement
         mapping:
         mapping:
-          - scale: 0.1
+          # Raw value is tenths of a kg (42 = 4.2 kg).
+          - scale: 10
   - entity: button
   - entity: button
     icon: "mdi:heat-pump-outline"
     icon: "mdi:heat-pump-outline"
     name: Manual clean
     name: Manual clean
@@ -78,3 +112,65 @@ entities:
       - id: 9
       - id: 9
         type: boolean
         type: boolean
         name: button
         name: button
+  - entity: binary_sensor
+    class: problem
+    category: diagnostic
+    # Any non-zero DP22 bit -> problem; raw fault_code kept to decode the bit
+    # when one fires (model labels, unverified: bit0 motor, bit1 program,
+    # bit2 g_sensor).
+    dps:
+      - id: 22
+        type: bitfield
+        name: sensor
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true
+      - id: 22
+        type: bitfield
+        name: fault_code
+      - id: 21
+        type: bitfield
+        name: notification_code
+        optional: true
+  - entity: binary_sensor
+    name: Bin full
+    icon: "mdi:tray-full"
+    # DP21 bit0; latches until the bin is emptied and reset.
+    dps:
+      - id: 21
+        type: bitfield
+        name: sensor
+        optional: true
+        mapping:
+          - dps_val: 1
+            value: true
+          - value: false
+  - entity: binary_sensor
+    name: Cleaning
+    class: running
+    category: diagnostic
+    dps:
+      - id: 21
+        type: bitfield
+        name: sensor
+        optional: true
+        mapping:
+          # DP21 bits 1|2 (mask 6): a clean rotation is active (bit2 auto,
+          # bit1 manual/commanded).
+          - dps_val: 6
+            value: true
+          - value: false
+  - entity: binary_sensor
+    name: UV sterilizing
+    class: running
+    category: diagnostic
+    dps:
+      - id: 21
+        type: bitfield
+        name: sensor
+        optional: true
+        mapping:
+          - dps_val: 64
+            value: true
+          - value: false