Meross: python library with mqtt

Hi,
I am using two Meross MSS425e, which is a Wifi enabled power strip with 3 fullsized sockets individually switchable and 4 usbports, which can only be switched together. The powerstrips by meross appear to be mostly based on a MediaTek chipset. Thus they cannot be flashed with the tuya firmware. There is an option to use IFTTT to connect them with openhab here, but I rather do not want introduce another online service. There also appears to be no binding as of now. However, there is a python library on github.
As recommended to me by vzorglub I used this library and mqtt to expose status and control to openhab. I recently installed a mqtt broker because of zigbee2mqtt (many good tutorials available), which is why I won’t describe it here. Furthermore, I tried to mimic way how zigbee2mqtt publishes information using JSON.
For checking if the routes and information are ‘set’ and published correctly I can recommend the tool mqtt.fx.
As I only have two power strips without energy consumption sensor, that’s all I could test for now.

I am running openhab2.5M3 on a raspberrypi3.

I basically followed the tutorial for installing the Paho MQTT Python Client here. Please mind that the meross library is only tested for python3.5+.

pip3 install paho-mqtt

Then the meross library

pip3 install meross_iot==0.3.1.5 --upgrade

I added a script meross2mqtt.py in /etc/openhab2/scripts

import paho.mqtt.client as mqtt
from meross_iot.manager import MerossManager
from meross_iot.meross_event import MerossEventType
from meross_iot.cloud.devices.light_bulbs import GenericBulb
from meross_iot.cloud.devices.power_plugs import GenericPlug
from meross_iot.cloud.devices.door_openers import GenericGarageDoorOpener
from random import randint
import time
import os
import json


EMAIL = "<your email used for registration with meross>"
PASSWORD = "<your password used for registration with meross>"
connectMsg = json.dumps({"state":True})

ident = "meross"
client = mqtt.Client(ident)
client.connect("<ip address of openhab server>")
client.publish(ident, payload=connectMsg, qos=0, retain=False)

def on_message(client, userdata, message):
    print("message received " ,str(message.payload.decode("utf-8")))
    print("message topic=",message.topic)
    print("message qos=",message.qos)
    print("message retain flag=",message.retain)
    
    msg_split = message.topic.split("/")
    if len(msg_split)>2:
        if msg_split[0] == ident and msg_split[-1]=="set":
            handle_message("/".join(msg_split[1:-1]), str(message.payload.decode("utf-8")))
    
client.on_message=on_message


manager = None

def event_handler(eventobj):
    if eventobj.event_type == MerossEventType.DEVICE_ONLINE_STATUS:
        print("Device online status changed: %s went %s" % (eventobj.device.name, eventobj.status))
        client.publish("meross/%s" %(eventobj.device.name),getJsonMsg(eventobj.device)) 
        pass

    elif eventobj.event_type == MerossEventType.DEVICE_SWITCH_STATUS:
        print("Switch state changed: Device %s (channel %d) went %s" % (eventobj.device.name, eventobj.channel_id,
                                                                        eventobj.switch_state))
        client.publish("meross/%s/channel_%s" %(eventobj.device.name,eventobj.channel_id),getJsonMsg(eventobj.device,eventobj.channel_id))
        
        channel = eventobj.device.get_channels()[eventobj.channel_id]
        if 'devName' in channel:
            client.publish("meross/%s/%s" %(eventobj.device.name,channel['devName']),getJsonMsg(eventobj.device,eventobj.channel_id))                                                                           
    elif eventobj.event_type == MerossEventType.CLIENT_CONNECTION:
        print("MQTT connection state changed: client went %s" % eventobj.status)

        # TODO: Give example of reconnection?

    elif eventobj.event_type == MerossEventType.GARAGE_DOOR_STATUS:
        print("Garage door is now %s" % eventobj.door_state)
        

    else:
        print("Unknown event!")

def initialize_meross():
    global manager
    # Initiates the Meross Cloud Manager. This is in charge of handling the communication with the remote endpoint
    manager = MerossManager(meross_email=EMAIL, meross_password=PASSWORD)

    # Register event handlers for the manager...
    manager.register_event_handler(event_handler)

    # Starts the manager
    manager.start()
    
    subscribe_broker(manager)
    
    return manager
    
def subscribe_broker(manager):
    #print("All the supported devices I found:")
    all_devices = manager.get_supported_devices()
    for d in all_devices:
        
        if hasattr(d, 'get_channels'):
            channels = d.get_channels()
            for i in range(len(channels)):
                if 'devName' in channels[i]:
                    client.subscribe("meross/%s/%s/set" %(d.name,channels[i]['devName']))
                print(client.subscribe("meross/%s/channel_%i/set" %(d.name,i)))
        
        print(client.subscribe("meross/%s/set" %(d.name)))
        
def getJsonMsg(d, cNo = None):

    info = {"state":getState(d, cNo), "type": d.type, "friendlyName":d.name, "online": d.online}  
    
    if d.online:
        if cNo is None: 
            info.update(d.get_sys_data())
        else:
            info.update(d.get_channels()[cNo])

    return json.dumps(info)
        
def getState(d, cNo=None):
    status = False
    
    if d.online:
        if cNo is None:
            status = d.get_status()
        else:
            status = d.get_status(cNo)
    
    if status:
        return "ON"
    return "OFF"
    
    
def getChannelName(d, cNo):
    return d.get_channels()[cNo]['devName']
    
def handle_message(topic, messageStr):
    print("topic %s -> handling message: %s" %(topic, messageStr)) 
    msg = json.loads(messageStr)
    topic_split = topic.split("/")
    device_name = topic_split[0]
    
    if manager is not None:
        device = manager.get_device_by_name(device_name)
        if device is not None:
            if len(topic_split)==1:
                if 'state' in msg:
                    if msg['state']=='ON':
                        device.turn_on()
                    elif msg['state']=='OFF':
                        device.turn_off()
                
            
            elif len(topic_split)==2:
                channel_name = topic_split[1]
                cNo = -1
                if channel_name.startswith("channel_"):
                    cNo = int(channel_name.replace("channel_",""))
                else:
                    channels = device.get_channels()
                    for i in range(len(channels)):
                        if 'devName' in channels[i] and channels[i]['devName']==channel_name:
                            cNo = i
                if cNo>-1:
                    if 'state' in msg:
                        if msg['state']=='ON':
                            device.turn_on_channel(cNo)
                        elif msg['state']=='OFF':
                            device.turn_off_channel(cNo)
                else:
                    print("Channel '%s' not found for device '%s'!" %(topic_split[1],device_name))
            
    
if __name__ == '__main__':
    manager = initialize_meross()
    client.loop_forever() 
else:
    client.loop_start() 
    

Please change the three entries with “<…>” accordingly.

Running this script your devices will publish their state in topics like meross/device name and eventually meross/device name/channel_x and meross/device name/channel name. The device name is the one used in the meross app. The channel topics appear if the device has several channels like a power strip with a channel for each switch. Please note that channel_0 switches the whole power strip on and off, while the last channel switches USB. Additionally to channel_x the state is also published under the name specified in the meross app, for example meross/device name/light01. The devices also subscribed to the topics meross/device name/set and meross/device name/channel name/set.

These topics can be used to set up devices in openhab

Bridge mqtt:broker:broker "MQTT Broker" [ host="localhost", port=1883, secure=false, clientID="openHAB2" ]
{
    Thing topic merossPlug01 "MEROSS Plug 01" @ "Bedroom" {
    Channels:
        Type switch : PowerStrip "MEROSS Plug 01" [ stateTopic="meross/Schlafz. Leiste", commandTopic="meross/Schlafz. Leiste/set", transformationPattern="JSONPATH:$.state", transformationPatternOut="JS:setZigbeeState.js", on="ON", off="OFF"]
	Type switch : PowerSwitch1 "MEROSS Plug 01 Switch 01" [ stateTopic="meross/Schlafz. Leiste/channel_1", commandTopic="meross/Schlafz. Leiste/channel_1/set", transformationPattern="JSONPATH:$.state", transformationPatternOut="JS:setZigbeeState.js", on="ON", off="OFF"]
	Type switch : PowerSwitch2 "MEROSS Plug 01 Switch 02" [ stateTopic="meross/Schlafz. Leiste/channel_2", commandTopic="meross/Schlafz. Leiste/channel_2/set", transformationPattern="JSONPATH:$.state", transformationPatternOut="JS:setZigbeeState.js", on="ON", off="OFF"]
	Type switch : PowerSwitch3 "MEROSS Plug 01 Switch 03" [ stateTopic="meross/Schlafz. Leiste/channel_3", commandTopic="meross/Schlafz. Leiste/channel_3/set", transformationPattern="JSONPATH:$.state", transformationPatternOut="JS:setZigbeeState.js", on="ON", off="OFF"]
	Type switch : USB "MEROSS Plug 01 USB" [ stateTopic="meross/Schlafz. Leiste/channel_4", commandTopic="meross/Schlafz. Leiste/channel_4/set", transformationPattern="JSONPATH:$.state", transformationPatternOut="JS:setZigbeeState.js", on="ON", off="OFF"]

    }
}

In order to create the correct JSON for the transformationPatternOut (ony available in openhab2.5) I reused a script for zigbee2mqtt setZigbeeState.js (placed in openHab-conf -> transform):

(function(x){

    var result = new Object();
    result.state = x;

    return JSON.stringify(result);
    
})(input)

In order to run the script in the background I created a service
sudo vim /etc/systemd/system/meross2mqtt.service

Type=simple
ExecStart=/usr/bin/python3 /etc/openhab2/scripts/meross2mqtt.py

[Install]
WantedBy=multi-user.target

Then enable (sudo systemctl enable meross2mqtt.service), start (sudo systemctl start meross2mqtt.service) and checking its status (sudo systemctl status meross2mqtt.service).
I found that I had to install paho-mqtt and meross_iot for root again

sudo pip3 install paho-mqtt
sudo pip3 install meross_iot==0.3.1.5 --upgrade

In the PaperUI you can create items as usual and to enable Google Home functionality add an item manually like this:

Switch MEROSSPlug01_Switch02 "Nachttisch" <light> ["Lighting"] { channel="mqtt:topic:broker:merossPlug01:PowerSwitch2" }

In general it should work like this, but there is quite some room for improvement not only for the other device types but also for the setup. I would like to hear about it from the more experienced users and improve this solution.
Thanks, good luck and have fun!

2 Likes