Meross Devices in openHAB 3

This very good tutorial on how to integrate Meross Devices (Meross Devices with local MQTT Broker - Cloudless) is afaik a solution for those who want to exclusevily bind their devices to openHAB (and lose, for instance, google home integration); that’s not what I want - I’d like to have my devices here and there. So, I followed the steps from here Meross: python library with mqtt - #21 by Denis_Lambert but now I don’t know what to do with GitHub - OpusTerra/meross2mqttv2: Python Merossiot 0.4 bridge for OpenHAB 2 - how and where should it be installed? Thanks in advance!

Hi Zisco,
If you follow my tutorial (for the Cloudless Meross connection to OH) you can add Metadata to the items and expose them to Google Home over the OpenHab Connector as a switch.
Works perfectly fine for me and it is way faster then the Meross Google Home integration. Most of the times it takes less then a second to switch devices on/off.

I’ve also used the python way before and it is way more complicated and I often ran into rate limiting on Meross Cloud, even though I had configured time outs for my requests, Meross seems to calculate the in another way then python, lol.

Thank you! I followed your instructions (I didn’t use docker, but a plain installation), now I ended up when I try to bind my meross to mqtt broker with that error:

/home/bodhi/Downloads/Meross-master/node_modules/string-kit/lib/unicode.js:237
              return emojiWidthLookup.get( code ) ?? 2 ;
                                                   ^

SyntaxError: Unexpected token ?
    at Module._compile (internal/modules/cjs/loader.js:723:23)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Module.require (internal/modules/cjs/loader.js:692:17)
    at require (internal/modules/cjs/helpers.js:25:18)
    at Object.<anonymous> (/home/bodhi/Downloads/Meross-master/node_modules/string-kit/lib/string.js:54:13)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)

Altough I’m connected to it’s WIFI npx meross info -v says:

npx: installed 27 in 72.531s
Getting info about device with IP 10.10.10.1
> POST /config
> Host: 10.10.10.1
Error Object.fromEntries is not a function

…and the result of

bodhi@bodhi:~$ npx meross setup --wifi-ssid '<SSID>' --wifi-pass '<PASS>' --mqtt mqtts://zshq-ohab:8883
npx: installed 27 in 72.512s
Setting up device with IP 10.10.10.1
┌──────────────────────────────────────────────────────┬────────────────────────
│Primary MQTT broker                                   │zshq-ohab:8883
├──────────────────────────────────────────────────────┼────────────────────────
│Failover MQTT broker                                  │zshq-ohab:8883
└──────────────────────────────────────────────────────┴────────────────────────
Error Unable to connect to device
Error Unable to connect to device
Error Unable to connect to device
Device will reboot...

(device switches two times)

Mh maybe meross meanwhile sets a different default IP.
You also can setup the device over the Meross app, as you would normally do and connect to your wifi. After that run the same command from a device in the same network, but replace the 10.10.10.1 IP with the one the meross device has on in your network. You can get the IP from the meross app in the device settings.

Didn’t change the behaviour - clicking two times, but same error…

This most likely means, that there is something wrong with the mqtt server. One click for the accepted config and the second one is a reset. I’ve experienced this as well as I tried to make my setup work. I would start checking the mqtt server. Verify that there is a certificate served from server for example with a mqtt client, there are plenty of apps for this.

Mosquitto is up and running, but
strace mosquitto_pub -h broker -t 'test/topic' --cafile certs/ca.crt -m 'test message' -p 8883
leads to

openat(AT_FDCWD, "certs/ca.crt", O_RDONLY) = -1 ENOENT (No such file or directory)
write(2, "Error: Problem setting TLS optio"..., 52Error: Problem setting TLS options: File not found.) = 52

File exists ----r-x---+ 1 root root 1.1K Jan 7 17:43 ca.crt, any idea what to try further?

I’m not a pro regarding MQTT, that’s why I’m running it in docker, takes a lot of work of your shoulders regarding permissons etc.
Maybe there is still something fishy with the folder permissions.
Try setting the permissons with

chmod 0700 ./ca_certificates
chmod 0600 ./ca_certificates/*

for the folder and the cert.

After hours of investigating and the help of (many thanks to) simonporter007 (Unable to setup device MSS210 · Issue #55 · bytespider/Meross · GitHub) it has finished in binding to my MQTT broker (mea culpa - I had an old node.js version…)! The device is still not connected to openHAB, I’ll try the docker-way the next days. But now, from here, thank you @ElBobo!

A little update: Plug is finally connected:

1673718921: New connection from 192.168.1.213:49260 on port 8883.
1673718922: Client fmware:2009072623298651802748e1e92fbcb9_Mdpk5bcXwO6q4oFr already connected,
ng old connection.
1673718922: New client connected from 192.168.1.213:49260 as fmware:2009072623298651802748e1e92
_Mdpk5bcXwO6q4oFr (p1, c1, k30, u'48:e1:e9:2f:bc:b9').

But if I want to change the state, it reboots and reconnects, but doesn’t change the state - any ideas left? I think we’re not very far from the goal!

I tried out the local API and could connect my MSG100 garage door opener to my local mqtt broker. after some hours try&error I was able to open and close the garage door. But especially the close command is not reliable. I could not find the root cause and decided to go back to the cloud API. Otherwise my wife would complain :wink:
Is there anyone, who also integrated the Meross garage door opener. Perhaps he solved the issue I had.

I am much the same as you - managed to connect to the local MQTT broker but spend a number of days trying to get the doors to open without success. I have gone back to the Meross Cloud app and waiting for some breakthrough. Are you accessing the Meross cloud API through Openhab?

1 Like

Hi,
I am accessing the cloud API using the meross2mqtt (v2 version) integration from OpenHab.

For the local API I could share my code to reproduce it. With these settings you will be able to open and close the garage door. Perhaps you will not have the problems with the reliability.

For the MQTT topic I used the file-based variant and splitted into two channels: one for the current state and one for the target state. You have to insert your “appliance id” into the channel code.

meross.things:

Thing topic garage "Garage" {
    Channels:
        Type string : state       "state" 		[ stateTopic="/appliance/<appliance id>/publish", transformationPattern="REGEX:(.*GarageDoor.*)∩JSONPATH:$.payload.state..open" ]
        Type switch : targetstate    "targetstate"    [ commandTopic="/appliance/<appliance id>/subscribe" , transformationPatternOut="JS:meross.js", on="1", off="0" ]  
    }    

I adapted the transformation file “meross.js” for the usage with the garage door opener:

meross.js

(function(targetstate) {
	var logger = Java.type("org.slf4j.LoggerFactory").getLogger("myScript");
	logger.warn("mecross.js - start");

	var message = "messsage" + Date.now();
	var timestamp = Math.floor( Date.now() / 1000 );

	var result = {};
	result.header = {};
	result.header.messageId = message;
	//result.header.namespace = "Appliance.Control.ToggleX";
    result.header.namespace = "Appliance.GarageDoor.State";
	result.header.method = "SET";
	result.header.payloadVersion = 1;
	result.header.from = "/appliance/openhab/ack";
	result.header.timestamp = timestamp;
	result.header.sign = MD5(message+timestamp).toString();

	result.payload = {}
	result.payload.state = {};
	result.payload.state.channel = 0;
	result.payload.state.open = targetstate == "1"?1:0;

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

/**
*
*  MD5 (Message-Digest Algorithm)
*  http://www.webtoolkit.info/
*
**/

function MD5(string) {
    function RotateLeft(lValue, iShiftBits) {
        return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
    }

    function AddUnsigned(lX, lY) {
        var lX4, lY4, lX8, lY8, lResult;
        lX8 = (lX & 0x80000000);
        lY8 = (lY & 0x80000000);
        lX4 = (lX & 0x40000000);
        lY4 = (lY & 0x40000000);
        lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
        if (lX4 & lY4) {
            return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
        }
        if (lX4 | lY4) {
            if (lResult & 0x40000000) {
                return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
            } else {
                return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
            }
        } else {
            return (lResult ^ lX8 ^ lY8);
        }
    }

    function F(x, y, z) {
        return (x & y) | ((~x) & z);
    }

    function G(x, y, z) {
        return (x & z) | (y & (~z));
    }

    function H(x, y, z) {
        return (x ^ y ^ z);
    }

    function I(x, y, z) {
        return (y ^ (x | (~z)));
    }

    function FF(a, b, c, d, x, s, ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    };

    function GG(a, b, c, d, x, s, ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    };

    function HH(a, b, c, d, x, s, ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    };

    function II(a, b, c, d, x, s, ac) {
        a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
        return AddUnsigned(RotateLeft(a, s), b);
    };

    function ConvertToWordArray(string) {
        var lWordCount;
        var lMessageLength = string.length;
        var lNumberOfWords_temp1 = lMessageLength + 8;
        var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
        var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
        var lWordArray = Array(lNumberOfWords - 1);
        var lBytePosition = 0;
        var lByteCount = 0;
        while (lByteCount < lMessageLength) {
            lWordCount = (lByteCount - (lByteCount % 4)) / 4;
            lBytePosition = (lByteCount % 4) * 8;
            lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
            lByteCount++;
        }
        lWordCount = (lByteCount - (lByteCount % 4)) / 4;
        lBytePosition = (lByteCount % 4) * 8;
        lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
        lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
        lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
        return lWordArray;
    };

    function WordToHex(lValue) {
        var WordToHexValue = "",
            WordToHexValue_temp = "",
            lByte, lCount;
        for (lCount = 0; lCount <= 3; lCount++) {
            lByte = (lValue >>> (lCount * 8)) & 255;
            WordToHexValue_temp = "0" + lByte.toString(16);
            WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
        }
        return WordToHexValue;
    };

    function Utf8Encode(string) {
        string = string.replace(/\r\n/g, "\n");
        var utftext = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }
        }
        return utftext;
    };
    var x = Array();
    var k, AA, BB, CC, DD, a, b, c, d;
    var S11 = 7,
        S12 = 12,
        S13 = 17,
        S14 = 22;
    var S21 = 5,
        S22 = 9,
        S23 = 14,
        S24 = 20;
    var S31 = 4,
        S32 = 11,
        S33 = 16,
        S34 = 23;
    var S41 = 6,
        S42 = 10,
        S43 = 15,
        S44 = 21;
    string = Utf8Encode(string);
    x = ConvertToWordArray(string);
    a = 0x67452301;
    b = 0xEFCDAB89;
    c = 0x98BADCFE;
    d = 0x10325476;
    for (k = 0; k < x.length; k += 16) {
        AA = a;
        BB = b;
        CC = c;
        DD = d;
        a = FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
        d = FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
        c = FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
        b = FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
        a = FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
        d = FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
        c = FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
        b = FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
        a = FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
        d = FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
        c = FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
        b = FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
        a = FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
        d = FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
        c = FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
        b = FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
        a = GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
        d = GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
        c = GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
        b = GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
        a = GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
        d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
        c = GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
        b = GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
        a = GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
        d = GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
        c = GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
        b = GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
        a = GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
        d = GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
        c = GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
        b = GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
        a = HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
        d = HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
        c = HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
        b = HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
        a = HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
        d = HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
        c = HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
        b = HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
        a = HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
        d = HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
        c = HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
        b = HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
        a = HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
        d = HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
        c = HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
        b = HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
        a = II(a, b, c, d, x[k + 0], S41, 0xF4292244);
        d = II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
        c = II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
        b = II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
        a = II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
        d = II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
        c = II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
        b = II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
        a = II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
        d = II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
        c = II(c, d, a, b, x[k + 6], S43, 0xA3014314);
        b = II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
        a = II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
        d = II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
        c = II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
        b = II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
        a = AddUnsigned(a, AA);
        b = AddUnsigned(b, BB);
        c = AddUnsigned(c, CC);
        d = AddUnsigned(d, DD);
    }
    var temp = WordToHex(a) + WordToHex(b) + WordToHex(c) + WordToHex(d);
    return temp.toUpperCase();
}



@ AntPow

Meanwhile, I got my garage door opener MSG100 running using local API.

Here the steps:

  1. You can use the pairer app to connect you Meross device to your local MQTT:
    https://play.google.com/store/apps/details?id=com.albertogeniola.merossconf&pli=1
    The trick is: You start the app and cancel the HA login. You can just input an “0” as Client ID. Then you are able to go into pairing mode using the “menu button in the upper left corner”. Pairing steps should be clear
  2. Unfortunately, the MSG100 device sends every 6 seconds an MQTT message with the namespace “Appliance.Control.Bind”. As long as you do not send an MQTT message back to the device, it will not run reliable…
    To send a message, I used MQTT Explorer on my iPad and send the following message on the subscribe channel (I took the same messageID and appliance ID I got from the device, see the XXXX in the message below). For the rest I took the example from the Home Assistant integration.

{“header”:{“sign”:“9b5ee61405b787401566d48f9a0a1687”,“from”:“/appliance/XXXXXXXXXXXXXXXXXXXXXXXX/subscribe”,“timestampMs”:533,“method”:“SETACK”,“triggerSrc”: “CloudControl”,“from”:“cloud/sub/kIGFRwvtAQP4sbXv/58c35d719350a689”, “payloadVersion”:1,“timestamp”:1705558265,“namespace”:“Appliance.Control.Bind”,“messageId”:“XXXXXXXXXXXXXXXXXXXXXXXXXX”},“payload”:{}}

After this message, the device became “quiet” and was able to react on my MQTT messages.