Meross Garage Door Opener - MSG200

There are other discussions about the Meross Garage Door Opener and guides to install the opener but I wanted to highlight the process in case it gets confusing since there are bugs in the original driver code.

  1. Install the app by adding the app in App Code on your Hubitat. Here is the link: https://github.com/ithinkdancan/hubitat-meross/blob/main/apps/meross_app.groovy
  2. Install the driver code for the garage doors. Copy the code below and add it as a new driver. This code addresses a bug where the firmware version is checked to be 0 or 323. Since the MSG200's latest firmware is at 2.1.3, this causes errors once the device is added. I have changed it to check for firmware above 200 so it resolves the issue.
/**
 * Meross Smart WiFi Garage Door Opener
 *
 * Author: Daniel Tijerina
 * Last updated: 2021-09-26
 *
 *
 * Licensed under the Apache License, Version 2.0 (the 'License'); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

import java.security.MessageDigest

metadata {
    definition(
        name: 'Meross Smart WiFi Garage Door Opener',
        namespace: 'ithinkdancan',
        author: 'Daniel Tijerina'
    ) {
        capability 'DoorControl'
        capability 'GarageDoorControl'
        capability 'Actuator'
        capability 'ContactSensor'
        capability 'Refresh'

        attribute 'model', 'string'
        attribute 'version', 'string'
    }
    preferences {
        section('Device Selection') {
            input('deviceIp', 'text', title: 'Device IP Address', description: '', required: true, defaultValue: '')
            input('key', 'text', title: 'Key', description: 'Required for firmware version 3.2.3 and greater', required: false, defaultValue: '')
            input('messageId', 'text', title: 'Message ID', description: '', required: true, defaultValue: '')
            input('timestamp', 'number', title: 'Timestamp', description: '', required: true, defaultValue: '')
            input('sign', 'text', title: 'Sign', description: '', required: true, defaultValue: '')
            input('uuid', 'text', title: 'UUID', description: '', required: true, defaultValue: '')
            input('channel', 'number', title: 'Garage Door Port', description: '', required: true, defaultValue: 1)
            input('garageOpenCloseTime','number',title: 'Garage Open/Close time (in seconds)', description:'', required: true, defaultValue: 5)
            input('DebugLogging', 'bool', title: 'Enable debug logging', defaultValue: true)
        }
    }
}

def getDriverVersion() {
    1
}

def initialize() {
    log 'Initializing Device'
    refresh()

    unschedule(refresh)
    runEvery5Minutes(refresh)
}

def sendCommand(int open) {
    
    def currentVersion = device.currentState('version')?.value ? device.currentState('version')?.value.replace(".","").toInteger() : 200

    // Firmware version 3.2.3 and greater require different data for request
    if (!settings.deviceIp || !settings.uuid || (currentVersion >= 200 && !settings.key) || (currentVersion < 200 && (!settings.messageId || !settings.sign || !settings.timestamp))) {
        sendEvent(name: 'door', value: 'unknown', isStateChange: false)
        log.warn('missing setting configuration')
        return
    }
    sendEvent(name: 'door', value: open ? 'opening' : 'closing', isStateChange: true)

    try {
        def payloadData = currentVersion >= 200 ? getSign() : [MessageId: settings.messageId, Sign: settings.sign, CurrentTime: settings.timestamp]
        
        def hubAction = new hubitat.device.HubAction([
        method: 'POST',
        path: '/config',
        headers: [
            'HOST': settings.deviceIp,
            'Content-Type': 'application/json',
        ],
        body: '{"payload":{"state":{"open":' + open + ',"channel":' + settings.channel + ',"uuid":"' + settings.uuid + '"}},"header":{"messageId":"'+payloadData.get('MessageId')+'","method":"SET","from":"http://'+settings.deviceIp+'/config","sign":"'+payloadData.get('Sign')+'","namespace":"Appliance.GarageDoor.State","triggerSrc":"AndroidLocal","timestamp":' + payloadData.get('CurrentTime') + ',"payloadVersion":1' + ',"uuid":"' + settings.uuid + '"}}'
    ])
        runIn(settings.garageOpenCloseTime, "refresh")
        return hubAction
    } catch (e) {
        log.error("runCmd hit exception ${e} on ${hubAction}")
    }
}


def refresh() {
    def currentVersion = device.currentState('version')?.value ? device.currentState('version')?.value.replace(".","").toInteger() : 200

    // Firmware version 3.2.3 and greater require different data for request
    if (!settings.deviceIp || !settings.uuid || (currentVersion >= 200 && !settings.key) || (currentVersion < 200 && (!settings.messageId || !settings.sign || !settings.timestamp))) {
        sendEvent(name: 'door', value: 'unknown', isStateChange: false)
        log.warn('missing setting configuration')
        return
    }
    try {
        def payloadData = currentVersion >= 200 ? getSign() : [MessageId: settings.messageId, Sign: settings.sign, CurrentTime: settings.timestamp]

        log.info('Refreshing')
        
        def hubAction = new hubitat.device.HubAction([
            method: 'POST',
            path: '/config',
            headers: [
                'HOST': settings.deviceIp,
                'Content-Type': 'application/json',
            ],
            body: '{"payload":{},"header":{"messageId":"'+payloadData.get('MessageId')+'","method":"GET","from":"http://'+settings.deviceIp+'/subscribe","sign":"'+ payloadData.get('Sign') +'","namespace": "Appliance.System.All","triggerSrc":"AndroidLocal","timestamp":' + payloadData.get('CurrentTime') + ',"payloadVersion":1}}'
        ])
        log hubAction
        return hubAction
    } catch (Exception e) {
        log.debug "runCmd hit exception ${e} on ${hubAction}"
    }
}

def open() {
    log.info('Opening Garage')
    return sendCommand(1)
}

def close() {
    log.info('Closing Garage')
    return sendCommand(0)
}

def updated() {
    log.info('Updated')
    initialize()
}

def parse(String description) {
    def msg = parseLanMessage(description)
    def body = parseJson(msg.body)
    
    if(msg.status != 200) {
         log.error("Request failed")
         return
    }
    
    // Close/Open request was sent
    if(body.header.method == "SETACK") return
    
    if (body.payload.all) {
        def state = body.payload.all.digest.garageDoor[settings.channel.intValue() - 1].open
        sendEvent(name: 'door', value: state ? 'open' : 'closed')
        sendEvent(name: 'contact', value: state ? 'open' : 'closed')
        sendEvent(name: 'version', value: body.payload.all.system.firmware.version, isStateChange: false)
        sendEvent(name: 'model', value: body.payload.all.system.hardware.type, isStateChange: false)
    } else {
        //refresh()
        log.error ("Request failed")
    }
}

def getSign(int stringLength = 16){
    
    // Generate a random string 
    def chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
    def randomString = new Random().with { (0..stringLength).collect { chars[ nextInt(chars.length() ) ] }.join()}    
    
    int currentTime = new Date().getTime() / 1000
    messageId = MessageDigest.getInstance("MD5").digest((randomString + currentTime.toString()).bytes).encodeHex().toString()
    sign = MessageDigest.getInstance("MD5").digest((messageId + settings.key + currentTime.toString()).bytes).encodeHex().toString()
    
    def requestData = [
         CurrentTime: currentTime,
         MessageId: messageId,
         Sign: sign
    ]
    
    return requestData
}

def log(msg) {
    if (DebugLogging) {
        log.debug(msg)
    }
}
  1. Next go to Apps -> add user apps. Then select Meross Garage Door Manager.
  2. Add your credentials that you use to login to your Meross Account and specify the IP address of the opener on your network. If you do not know this, you can login to your router and look at devices on your network and find the Meross Garage Door Opener.
  3. Next select the garage doors you would like to add.

Once this is completed the garage doors should appear in your devices list.
Click into the garage door device and hit refresh. You should see door and contact status along with firmware info. This means you have successfully integrated the MSG200 into Hubitat!

I chose to configure my device with Homekit integration as well since Meross is charging an extra $30 for the privilege. You just need to enable it within the device settings if you have the Homekit integration already enabled in the Apps section.

1 Like

The first two steps (login & device selection):
OK. :+1:

The third step ("Select one or more garage doors to add"):
"0 new doors detected" -> dropdown is empty :sleepy:

Any ideas?

When you go to List Garage Doors, do any show up there? You might have already selected them and so they would not show up in the Add section.

No, the Garage Doors List is empty.

In the meantime I have done the whole process manually (via JavaScript) - the driver itself is now working.
I think I don't need the App anymore, right? :thinking:

BTW: I changed the driver a little bit to have faster responses (1 minute polling, do you think I will have any problems?) and to have different settings for opening and closing time. :sunglasses:

Just installed the Meross Opener and the above App...
while the HomeKit automation works, I do want to add others, so am trying to add the garage to the list in the App...
I have the IP address (and added a static IP for good measure!) but I also need to enter my credentials... When I registered with Meross App it uses email and password... but these are apparently not correct?! Is there a different 'Username' that I should put in?? (I also tried the nickname that is in Meross app.

Thanks

[edit: :grimacing:might have got password wrong… that bit works now, but see below for next issue…]

A

I am facing the same situation... Step 2, the door is recognised, but clicking takes me to Step 3 which is the same, but as I have just recognised the door, the list is now empty and I cannot proceed :frowning: ... I did allow debugging, so will check what it says there...

A

[edit: does it matter that I have the MSG100 rather than the 200!?!]

2 Likes

Same "boat" as aidan with MSG100 and cannot go pass Step 3.

Some suggestions on how to get this to work for the MSG100

  • Download the app listed above -- when installing, immediately hit done
  • Download the driver code, then install the driver as a device

There are probably better ways of doing this, but here is one way to obtain all of the fields required for the driver.

  • Use the app to obtain the UUID and Key....go through the steps until you get to the (failing) step three. Click on the gear icon (upper left). You will find the UUID (the key is also listed).
  • Follow the directions listed by ithinkdancan to obtain the other fields. Specifically, follow the directions listed for generating a key using Chrome: GitHub - ithinkdancan/hubitat-meross: Hubitat Drivers for Meross Smart Plugs

Again, there are probably better ways of doing this...but this was one way I cobbled together a solution.

1 Like

Hi, I am stuck on step 3 with an unexpected error after entering my credentials and IP address. Error shown below. Any ideas on what would cause this? Thanks for your help.

Error: No signature of method: user_app_ajardolino3_Meross_Garage_Door_Manager_131.paragraph() is applicable for argument types: (groovyx.net.http.HttpResponseException) values: [groovyx.net.http.HttpResponseException: status code: 404, reason phrase: Not Found]

Same issue for me. The following approach is a bit of a hack until I devote time to seeing if I can figure out a clean fix to the app code shared above.

  1. Just install the driver code in Hubitat. This workaround does not depend on the app code. (That's the part that doesn't work).
  2. On a system where you can run Python:
%  pip install meross_iot==0.4.6.2
  1. Copy the following code to your system (it's a slightly modified form of what's shared here: GitHub - albertogeniola/MerossIot: Async Python library for controlling Meross devices)
import asyncio
import os

from meross_iot.http_api import MerossHttpClient
from meross_iot.manager import MerossManager
from meross_iot.model.credentials import MerossCloudCreds

EMAIL = os.environ.get('MEROSS_EMAIL') or "MEROSS_EMAIL"
PASSWORD = os.environ.get('MEROSS_PASSWORD') or "MEROSS_PASSWORD"


async def main():
    # Setup the HTTP client API from user-password
    http_api_client = await MerossHttpClient.async_from_user_password(api_base_url='https://iotx-us.meross.com',email=EMAIL, password=PASSWORD)

    creds=http_api_client.cloud_credentials
    print(creds)
    print(f"key: {creds.key}")
    
    # Setup and start the device manager
    manager = MerossManager(http_client=http_api_client)
    await manager.async_init()

    # Discover devices.
    await manager.async_device_discovery()
    meross_devices = manager.find_devices()

    # Print them
    print("I've found the following devices:")
    for dev in meross_devices:
        print(f"- {dev.name} ({dev.type}): {dev.online_status}")
        print(f"uuid: {dev.uuid}")

    # Close the manager and logout from http_api
    manager.close()
    await http_api_client.async_logout()

if __name__ == '__main__':
    if os.name == 'nt':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.stop()
  1. Set the MEROSS_EMAIL and MEROSS_PASSWORD environment variables.
  2. Run the script
  3. Note the following data from the output:
  • key
  • uuid
  1. In Hubitat, ensure that you added the driver code.
  2. Create a new virtual device using the driver Meross Smart WiFi Garage Door Opener
  3. For the required fields, use:
  • Device IP Address - your Meross device IP address
  • Key - the key value from the script's output
  • Message ID - the value N/A
  • Timestamp - the value 0
  • Sign - the value N/A
  • UUID - the value from the script's output
  • Garage Door Port - 1, 2, or 3
  • Garage Open/Close time (in seconds) - As appropriate
  1. Press Save Preferences and then Refresh to validate the Hubitat is able to obtain the current state from the controller.
  2. Add devices for the other garage doors ensuring that change the garage door port for each.