Meross Wi-Fi MSS426 Power Strip - Working in Hubitat

Bought a Meross Wi-Fi MSS426 Power Strip some time back. Didn't do anything with it until this week. And it took me on quite a journey of discovery, but having started with not knowing how to get the damn thing working, it's now fully functional with 6 child devices and each socket individually controllable. Which is nice.

Most historic help was from @ithinkdancan who has written drivers and explained how to get them connected to Hubitat. However, that documentation is nearly 3 years out of date and a few things have changed.

After quite a search through these forums I ended up here. I followed the instructions, but they didn't work, specifically around getting the key. This was the first issue.

At some point, Meross changed their sign-in page.

You need to use this page with a js script to get a key to use in the Hubitat driver. @ithinkdancan provides a useful js script on the the driver page which works AS LONG AS you use the new link above and also update the link referenced in the js script to the new one shown above. Having run the updated js script in the right web page in Chrome I got the required key I needed to plug in to the driver. First issue resolved.

Then I grabbed the @ithinkdancan meross smart plug mini driver. Installed it in Hubitat using the virtual device option, chose the new driver from the drop down, entered the Power Strip's IP address and the key I got from the js script in to Preferences and tested it. I was able to turn the sockets on/off, but only all at once. So communication was established, but I wanted to be able to switch individual sockets on/off, not just all of them. This was the second issue.

I went back to @ithinkdancan's drivers page and looked through his other driver, the MS120 2 channel driver and wondered whether the same code that managed the 2 channels on the MS120 would work for the 6 channels on the MSS426 if I added the code about child devices in to the driver I was already using in Hubitat I compared the code and brought over the parts of the 2 channel driver related to child devices in to the smart plug mini driver. I also updated the number of child devices it had to 6 in the createChildDevices function.

I removed the device I'd previously created and recreated it using the updated (frankenstein?) driver and lo and behold, all 6 child devices were created and I could select child devices to turn on/off independently. I had a fully functional 6 socket hubitat controlled device. Second issue resolved.

My crudely updated driver is below. Note, the USBs are not controlled within this driver, just the 6 sockets. None of this could have been achieved without @ithinkdancan and the drivers he wrote so thank you. All I've done is bring together code from two drivers to make things work. Sections or lines I've added are remarked as such. Where I had to remove code, I've rem'd it out rather than removed it. No guarantees it'll work for you and I'm far from a competent programmer, so please don't expect me to be able to answer complicated questions about this!

/**

  • Meross Smart Plug Mini
  • Author: Daniel Tijerina
  • Last updated: 2021-10-06
  • New update: 2025-02-02: Steve Clarke
  • Brought across the child elements of the 2 Channel Smart Plug in to this driver
  • to get it working with all 6 sockets on the MSS426. Mostly a copy/paste job
  • but with a few small tweaks - couldn't really call it programming.
  • It's still Daniel Tijerina's code, I just frankensteined the code together.
  • 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 Plug Mini',
namespace: 'ithinkdancan',
author: 'Daniel Tijerina'
) {
capability 'Actuator'
capability 'Switch'
capability 'Refresh'
capability 'Sensor'
capability 'Configuration'

    //added by SGC
    command 'childOn', [[name:'Channel Number', type:'STRING', description:'Provide the channel number to turn on.']]
    command 'childOff', [[name:'Channel Number', type:'STRING', description:'Provide the channel number to turn off.']]
    command 'childRefresh', [[name:'Channel Number', type:'STRING', description:'Provide the channel number to refresh.']]
    command 'componentOn'
    command 'componentOff'
    command 'componentRefresh'    
    //added by SGC        
    
    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: 'Key from login.py', required: true, defaultValue: '')
        input('DebugLogging', 'bool', title: 'Enable debug logging', defaultValue: true)
    }
}

}

def getDriverVersion() {
1
}

// def initialize() {
// log 'Initializing Device'
// refresh()
//
// unschedule(refresh)
// runEvery5Minutes(refresh)
//} Rem'd out by SGC - Initiatization is done elsewhere

def sendCommand(int onoff, int channel) {
if (!settings.deviceIp || !settings.key) {
sendEvent(name: 'switch', value: 'offline', isStateChange: false)
log.warn('missing setting configuration')
return
}
try {
def payloadData = getSign()
def hubAction = new hubitat.device.HubAction([
method: 'POST',
path: '/config',
headers: [
'HOST': settings.deviceIp,
'Content-Type': 'application/json',
],
body: '{"payload":{"togglex":{"onoff":' + onoff + ',"channel":' + channel + '}},"header":{"messageId":"'+payloadData.get('MessageId')+'","method":"SET","from":"http://'+settings.deviceIp+'/config","sign":"'+payloadData.get('Sign')+'","namespace":"Appliance.Control.ToggleX","triggerSrc":"iOSLocal","timestamp":' + payloadData.get('CurrentTime') + ',"payloadVersion":1}}'
])
log hubAction
runIn(0, "refresh")
return hubAction
} catch (e) {
log "runCmd hit exception ${e} on ${hubAction}"
}
}

def refresh() {
log.info('Refreshing')
if (!settings.deviceIp || !settings.key) {
sendEvent(name: 'switch', value: 'offline', isStateChange: false)
log.warn('missing setting configuration')
return
}
try {
def payloadData = getSign()

    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.debug hubAction
    return hubAction
} catch (Exception e) {
    log "runCmd hit exception ${e} on ${hubAction}"
}

}

def on() {
log.info('Turning on')
return sendCommand(1, 0)
}

def off() {
log.info('Turning off')
return sendCommand(0, 0)
}

//Added by SGC
def childOn(String dni) {
log.debug "childOn($dni)"
return sendCommand(1, dni.toInteger())
}

def childOff(String dni) {
log.debug "childOff($dni)"
return sendCommand(0, dni.toInteger())
}

def childRefresh(String dni) {
log.debug "childRefresh($dni)"
refresh()
}

def componentOn(cd) {
log "${device.label?device.label:device.name}: componentOn($cd)"
return childOn(channelNumber(cd.deviceNetworkId))
}

def componentOff(cd) {
log "${device.label?device.label:device.name}: componentOff($cd)"
return childOff(channelNumber(cd.deviceNetworkId))
}

def componentRefresh(cd) {
log "${device.label?device.label:device.name}: componentRefresh($cd)"
return childRefresh(cd.deviceNetworkId)
}
//Added by SGC

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

def parse(String description) {
log "description is: $description" //Added by SGC

def msg = parseLanMessage(description)
def body = parseJson(msg.body)

if(msg.status != 200) {
     log.error("Request failed")
     return
}
log body //Added by SGC
if (body.payload.all) {
    def parent = body.payload.all.digest.togglex[0].onoff
    sendEvent(name: 'switch', value: parent ? 'on' : 'off', isStateChange: true)
    sendEvent(name: 'version', value: body.payload.all.system.firmware.version, isStateChange: false)
    sendEvent(name: 'model', value: body.payload.all.system.hardware.type, isStateChange: false)
    
    //Added by SGC
       childDevices.each {
        childDevice ->
        def channel = channelNumber(childDevice.deviceNetworkId) as Integer
        def childState = body.payload.all.digest.togglex[channel].onoff
        log "channel $channel:  $childState"
        	childDevice.sendEvent(name: 'switch', value: childState ? 'on' : 'off')
     }
} else {
    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)
}
}

//Added by SGC
def initialize() {
log 'initialize()'
if (!childDevices) {
createChildDevices()
}
refresh()

log 'scheduling()'
unschedule(refresh)
runEvery1Minute(refresh)

}
//Added by SGC

def configure() {
log 'configure()'
initialize()
}

//Added by SGC
private channelNumber(String dni) {
dni.split('-ep')[-1]
}

private void createChildDevices() {
state.oldLabel = device.label
for (i in 1..6) {
addChildDevice('hubitat', 'Generic Component Switch', "${device.deviceNetworkId}-ep${i}", [completedSetup: true, label: "${device.displayName} (CH${i})",
isComponent: false, componentName: "ep$i", componentLabel: "Channel $i"
])
}
}
//Added by SGC

Would just like to add for anyone looking for help/information on adding Meross devices that getting the key with the script only works in Chrome.