dhewg / esphome-miot

ESPHome components for MIoT devices
Other
42 stars 17 forks source link

Add "Event Component" support #16

Closed cristianchelu closed 1 month ago

cristianchelu commented 8 months ago

Add full support for event_occured mcu messages via the new "Event Component" introduced in ESPHome 2024.5 and Home Assistant 2024.5, fixing #4.

As of today, 2024-05-02, this PR only works with latest esphome:dev. Expected esphome stable release is on 15th of may (cadence is every third Wednesday of the month). I have also tested this branch with ESPHome 2024.4 and it compiles & works ok, sans the actual event component.

Limitations & considerations

event_type is a requirement

As far as I have seen, Home Assistant requires an event_type label for every event entity, to distinguish what exactly happened. The example given is for a Button Pressed event, the type can be single_press, long_press, double_press etc. ESPHome also follows this pattern, and requires both a list of all possible event_types when creating the event, as well as the event_type when the event is triggered.

As MIoT events don't map to this pattern and only have one "type" per event, I have chosen to add a single event_type required string to the YAML definition and send this with each event trigger. For the strings, the event name as it appears in the miot-specs seems the most sensible option.

MIoT event properties

ESPHome does not allow for attributes on events so an alternative is required to expose properties related to events.

I think the best approach is to consider the properties as normal sensors, just with poll disabled. This allows for re-use of the existing update_properties function (even if it complicates it a bit more), as well as keeping the simple PID definition syntax.

The only downside is that if the same miot event id produces a variable amount of properties each time it's emitted, we can't easily say which props exactly were part of the event. However, it seems that this is not the case so far.

Automations

Automations also come by default with the event component, via the on_event trigger.

Example usage

Minimal example:

event:
  - platform: "miot"
    miot_siid: 4
    miot_eiid: 1
    event_type: some_miot_event
sensor: 
  - platform: "miot" # Some PID updated as part of an event
    miot_siid: 4
    miot_piid: 2
    miot_poll: false

Complex example:

event:
  - platform: "miot"
    miot_siid: 4
    miot_eiid: 1
    id: feedsuccess_event
    event_type: feedsuccess
    internal: true
    on_event:
      - event.trigger:
          id: feed_event
          event_type: "finished"
      - globals.set: 
          id: portions_dispensed_today
          value: !lambda return id(portions_dispensed_today) + id(outfood_num).state;
      - component.update:
          id: portions_dispensed_today_sensor
  - platform: "miot"
    miot_siid: 4
    miot_eiid: 2
    id: feedstats_event
    event_type: feedstats
    internal: true
    on_event:
      - event.trigger:
          id: feed_event
          event_type: "stats"
  - platform: "miot"
    miot_siid: 4
    miot_eiid: 3
    id: feedstart_event
    event_type: feedstart
    internal: true
    on_event:
      - event.trigger:
          id: feed_event
          event_type: "start"
# Multiple related MIoT events combined into a single template event
  - platform: "template"
    name: 'Feed Event'
    id: feed_event
    event_types:
      - 'start'
      - 'stats'
      - 'finished'

Example device entry for above complex event: Screenshot from 2024-05-02 18-12-01

Why it's needed.

On some (like mmgg.feeder.fi) devices that have properties attached to events in the spec, the properties themselves cannot be read directly as they produce garbage data, but they are only updated as part of an event message.

Example: SIID 4 PIID 4 and SIID 4 PIID 5 always return 5 when directly read, but correct data as part of an event:

// Event
[13:43:40][V][miot:095]: Received MCU message 'event_occured 4 2 4 1 5 255'

[13:43:40][V][miot.sensor:011]: MCU reported sensor 4:4 is: 1.000000
[13:43:40][V][sensor:043]: 'Last Feed Portions': Received new state 1.000000

[13:43:40][V][miot.text_sensor:011]: MCU reported text sensor 4:5 is: 255
[13:43:40][V][text_sensor:013]: 'Last Feed Source': Received new state 255

[13:43:40][V][miot:095]: Received MCU message 'event_occured 4 1 4 1 5 255'

[13:43:40][V][miot.sensor:011]: MCU reported sensor 4:4 is: 1.000000
[13:43:40][V][sensor:043]: 'Last Feed Portions': Received new state 1.000000

[13:43:40][V][miot.text_sensor:011]: MCU reported text sensor 4:5 is: 255
[13:43:40][V][text_sensor:013]: 'Last Feed Source': Received new state 255

// Read directly afterwards
[13:46:40][V][miot:189]: Sending reply 'down get_properties 4 4 4 5' to MCU
[13:46:40][V][miot:095]: Received MCU message 'result 4 4 0 5 4 5 0 5 get_down'

[13:46:40][V][miot.sensor:011]: MCU reported sensor 4:4 is: 5.000000
[13:46:40][V][sensor:043]: 'Last Feed Portions': Received new state 5.000000

[13:46:40][V][miot.text_sensor:011]: MCU reported text sensor 4:5 is: 5
[13:46:40][V][text_sensor:013]: 'Last Feed Source': Received new state 5
dhewg commented 1 month ago

Finally had some time to take a closer look. This looks nice already, thanks!

But it seems I cannot really test this yet, as I run HA core on debian buster, which doesn't have python3.12 and HA requires 3.12 since 2024.04...

But with this PR and this diff:

diff --git a/config/zhimi.airp.rmb1.yaml b/config/zhimi.airp.rmb1.yaml
index be2e5c1..316c23f 100644
--- a/config/zhimi.airp.rmb1.yaml
+++ b/config/zhimi.airp.rmb1.yaml
@@ -202,3 +202,10 @@ button:
     miot_aiid: 1
     name: "Toggle Mode"
     icon: mdi:cached
+
+event:
+  - platform: "miot"
+    miot_siid: 9
+    miot_eiid: 3
+    name: "Door opened"
+    event_type: "door-opened"

I can see this is the log when I turn on the purifier and then "open the door" (which is: detach the head from the body's unit):

[09:07:52][C][miot.event:017]: MIoT Event 'Door opened'
[09:07:52][C][miot.event:018]:   SIID: 9
[09:07:52][C][miot.event:019]:   EIID: 3
[09:07:52][C][miot.event:020]:   TYPE: door-opened
[09:08:07][V][miot:096]: Received MCU message 'event_occured 9 3'
[09:08:07][V][miot.event:011]: MCU reported event 9:3
[09:08:07][D][event:015]: 'Door opened' Triggered event 'door-opened'

So it seems to work, but I don't see anything event related on HA due to my version.

Since that feeder thingy seems funky on multiple fronts: Did you try that with your purifier? Does that work as intended?

cristianchelu commented 1 month ago

Good point, I didn't check until now, but it works:

event:
  - platform: "miot"
    miot_siid: 9
    miot_eiid: 1
    name: "Motor Stuck"
    event_type: "motor-stuck"
  - platform: "miot"
    miot_siid: 9
    miot_eiid: 2
    name: "Child Lock changed"
    event_type: "childlock-trigger"
  - platform: "miot"
    miot_siid: 9
    miot_eiid: 3
    name: "Door Opened"
    event_type: "door-opened"
  - platform: "miot"
    miot_siid: 9
    miot_eiid: 4
    name: "Top Grille"
    event_type: "top-grille"
  - platform: "miot"
    miot_siid: 9
    miot_eiid: 5
    name: "Filter Exhausted"
    event_type: "filter-exhausted"
[12:14:16][V][miot:096]: Received MCU message 'event_occured 9 3 '
[12:14:16][V][miot.event:011]: MCU reported event 9:3
[12:14:16][D][event:015]: 'Door Opened' Triggered event 'door-opened'
[12:14:16][V][miot:200]: Sending reply 'ok' to MCU
Screenshot 2024-09-25 at 12 15 30

( https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-mb5:1 )

dhewg commented 1 month ago

Alright, thanks & merged!

dhewg commented 1 month ago

While this implementation works just fine for basic events like "door opened", that feeder solution with one template event which then get triggered by 3 internal events is not a real beauty ;)

You've probably seen this, the event::Event component allows you to register multiple event_types (that {} dance on cg.RawExpression.

It should be possible to group that with multiple types for a single event like e.g.:

- event:
  - platform: "miot"
    name: 'Feed Event'
    - event_types:
      - event: "feedsuccess"
        miot_siid: 4
        miot_eiid: 1
      - event: "feedstats"
        miot_siid: 4
        miot_eiid: 2
      - event: "feedstart"
        miot_siid: 4
        miot_eiid: 2

(made up, may not be valid, just to illustrate)

Did you try something that? If that's doable it would really simplify the feeder config

cristianchelu commented 1 month ago

It's been a while since I've looked at what's possible, so I don't remember too much.

I do know I first tried to make something in the style of your example. I failed, and resorted to the hack just to have something working. Don't remember the specifics, though.

If you or someone else can manage to implement it like this, yeah, it would be way nicer!