krahabb / meross_lan

Home Assistant integration for Meross devices
MIT License
417 stars 45 forks source link

Add Support For The MAP100 Air Purifier #388

Closed Nerwyn closed 6 months ago

Nerwyn commented 7 months ago

Is your feature request related to a problem? Please describe. Meross has a smart air purifier, the MAP100. While it does work with HomeKit, the HomeKit integration only exposes filter lifetime and an identify button. image

This integration already has partial support for this device, but full support would be excellent. Currently this integration exposes on/off control as an outlet, Dnd light control, signal strength, and sensor protocol (disabled by default). image

Describe the solution you'd like Add the following controls from the Meross app:

It would make sense to combine power and speed as a fan entity.

Describe alternatives you've considered

Additional context Let me know if I can help in any way! I'm a software engineer and love this integration, and proselytize about it whenever I can. I can work on getting you a diagnostic trace later.

Nerwyn commented 7 months ago

Meross LAN diagnostics: config_entry-meross_lan-7ca1e5a738289c7ae9f62c67dc2e563c.json

krahabb commented 7 months ago

Hello @Nerwyn, Thank you for sharing these info. From the trace you've posted I just see the 'new' command namespaces for this kind of device but sadly enough, the meross_lan euristics were not able to correctly query them so, beside knowing their name, we still have no informations about their structure.

I'm going to add some smarter euristics (since this issue of querying unknown namespaces is common when we have to detect new device abilities) but in the meantime, if you're up for some testing you could manually craft some queries and use the meross_lan.request service to speed up the information gathering.

Actually, the new namespaces to inspect are Appliance.Control.Fan - Appliance.Control.FilterMaintenance

I guess the Appliance.Control.Fan could be queried with a service request like this:

service: meross_lan.request
data:
  method: GET
  namespace: Appliance.Control.Fan
  payload: "{ \"fan\": [ { \"channel\": 0 } ] }"
  device_id: XXXX

The 'FilterMaintenance' query instead might be harder to guess since, by the disconnection happening in the trace, it might be this namespace cannot be queried at all and is just used by the device to push out events through MQTT (this is typical in Meross protocol when a message is async in nature). It is nonetheless implemented in HK though but that's another story and the HK protocol is (beside the name) totally unknown to me.

Anyway, maybe, querying this same namespace (Appliance.Control.FilterMaintenance) with the same payload and method suggested for the 'Fan' one is going to work... like this:

service: meross_lan.request
data:
  method: GET
  namespace: Appliance.Control.FilterMaintenance
  payload: "{ \"filterMaintenance\": [ { \"channel\": 0 } ] }"
  device_id: XXXX

As an addition, if still this route doesn't work (i.e. device disconnection) you could do a last try by changing the request method to 'PUSH' instead of 'GET' (sometimes the HTTP interface in the device likes this way for device originated messages typically pushed over MQTT). As a side note, the 'filterMaintenance' key used in this query payload is another euristic so it isn't sure too. It happens sometimes, some namespaces use an exception to the general camelCaseTheLastSplitOfNamespace rule.

Finally, the device locking, beside already appearing as a feature/request here and there in past issues is still unknown to me. Trying a device 'meross_lan euristic' query (the one used in traces) always led to device disconnection and I really don't know how to craft a message request for this. It might be the same reasoning as for the 'FilterMaintenance' namespace and its related namespace should be Appliance.Control.PhysicalLock

Nerwyn commented 7 months ago

Fan Speed

Auto protocol was unable to find the route to the device with just the device ID, but was able to via HTTP if given it's IP address. Here's the request:

service: meross_lan.request
data:
  protocol: auto
  method: GET
  namespace: Appliance.Control.Fan
  payload: "{ \"fan\": [ { \"channel\": 0 } ] }"
  host: 192.168.0.150

And response:

response:
  header:
    messageId: 9f676ca744ac402ab18d9d9b3c5366ac
    namespace: Appliance.Control.Fan
    method: GETACK
    payloadVersion: 1
    from: /appliance/2207048128027773170148e1e99a18f6/publish
    uuid: 2207048128027773170148e1e99a18f6
    timestamp: 1708031668
    timestampMs: 753
    sign: f4c14ae04221e2720831be0d784d1d4c
  payload:
    fan:
      - channel: 0
        speed: 3
        maxSpeed: 4

Using this information, I'm able to change the fan speed!

service: meross_lan.request
data:
  protocol: auto
  method: SET
  namespace: Appliance.Control.Fan
  payload: "{ \"fan\": [ { \"channel\": 0, \"speed\": 1 } ] }"
  host: 192.168.0.150

Playing around with this also clarified what sleep mode is. It's a four speed fan pretending to be a three speed fan.

Nerwyn commented 7 months ago

Unfortunately while the PhysicalLock and FilterMaintenance GET requests do not throw errors, the response bodies are empty. I'm not too bummed about this since filter lifetime is available through HomeKit and child lock isn't that important.

Nerwyn commented 7 months ago

Oh wait, I was in a rush and didn't see the bit about using PUSH instead of GET. Using PUSH for filter maintenance and physical lock works!

FilterMaintenance

Request:

service: meross_lan.request
data:
  method: PUSH
  namespace: Appliance.Control.FilterMaintenance
  payload: "{ \"filterMaintenance\": [ { \"channel\": 0 } ] }"
  device_id: 53c27b165178a3e43fe11f58aaf60a8a
  host: 192.168.0.150

Response:

response:
  header:
    messageId: 924df00fb51ce337c11d393eda77ed76
    namespace: Appliance.Control.FilterMaintenance
    method: PUSH
    payloadVersion: 1
    from: /appliance/2207048128027773170148e1e99a18f6/publish
    uuid: 2207048128027773170148e1e99a18f6
    timestamp: 1708058351
    timestampMs: 536
    sign: d6d36a90f48473105ca6b109ae840fd2
  payload:
    filter:
      - channel: 0
        life: 100
        lmTime: 1707948454

PhysicalLock

Request:

service: meross_lan.request
data:
  method: PUSH
  namespace: Appliance.Control.PhysicalLock
  payload: "{ \"physicalLock\": [ { \"channel\": 0 } ] }"
  device_id: 53c27b165178a3e43fe11f58aaf60a8a
  host: 192.168.0.150

Response:

response:
  header:
    messageId: 97a8a93f63dfb67bc8af6aec1c09fa1a
    namespace: Appliance.Control.PhysicalLock
    method: PUSH
    payloadVersion: 1
    from: /appliance/2207048128027773170148e1e99a18f6/publish
    uuid: 2207048128027773170148e1e99a18f6
    timestamp: 1708058444
    timestampMs: 146
    sign: 6c1132c9ad352074a60004c743c8c11c
  payload:
    lock:
      - channel: 0
        onoff: 0

I'm able to set the child lock using the following request (note that the payload key is lock):

service: meross_lan.request
data:
  method: SET
  namespace: Appliance.Control.PhysicalLock
  payload: "{ \"lock\": [ { \"channel\": 0, \"onoff\": 1 } ] }"
  device_id: 53c27b165178a3e43fe11f58aaf60a8a
  host: 192.168.0.150

Response:

response:
  header:
    messageId: 2edbe6de881c4e16bd834f03c28cda7e
    namespace: Appliance.Control.PhysicalLock
    method: SETACK
    payloadVersion: 1
    from: /appliance/2207048128027773170148e1e99a18f6/publish
    uuid: 2207048128027773170148e1e99a18f6
    timestamp: 1708058934
    timestampMs: 419
    sign: 950284b67aeeba3e85fe7976b86da31c
  payload:
    lock:
      - channel: 0
        onoff: 1

And with that, that's all the possible commands and values for this device!

Nerwyn commented 7 months ago

Here's screenshots of the Meross app device page for reference. I forgot to share them in the original post.

krahabb commented 7 months ago

Awesome!

Nerwyn commented 6 months ago

Can confirm that the new map100 entities work flawlessly! Thank you!