Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Driver for MSS425F Surge Protector (5 Channel) #11

Open
NoThanksNick opened this issue Oct 30, 2023 · 1 comment
Open

Driver for MSS425F Surge Protector (5 Channel) #11

NoThanksNick opened this issue Oct 30, 2023 · 1 comment

Comments

@NoThanksNick
Copy link

NoThanksNick commented Oct 30, 2023

Apologies if this is not the correct way to go about this, I am not at all familiar with GitHub or how it works. I have a MSS425F Surge Protector with 5 channels (4 plugs, and the fifth channel powers on/off four USB ports). Simply changing the 2-channel driver to look for 5 channels did not appear to work, attempts to communicate with the device timed out.

However, I was able to modify the mini plug driver to add the 'child' language from the 2-channel driver. The child initialization was renamed 'initializechild' since there was already a definition for 'initialize' in the base driver.


/**
 * Meross Surge Protector - 5 Channel
 *
 * Author: Daniel Tijerina
 * Last updated: 2023-10-30
 *
 *
 * 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 Surge Protector - 5 Channel',
        namespace: 'ithinkdancan',
        author: 'Daniel Tijerina'
    ) {
        capability 'Actuator'
        capability 'Switch'
        capability 'Refresh'
        capability 'Sensor'
        capability 'Configuration'

	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'

        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)
}

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)
}

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)
}

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
    }

    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)

        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)
    }
}

def initializechild() {
    log 'initializechild()'
    if (!childDevices) {
        createChildDevices()
    }
    refresh()

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

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

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

private void createChildDevices() {
    state.oldLabel = device.label
    for (i in 1..5) {
        addChildDevice('hubitat', 'Generic Component Switch', "${device.deviceNetworkId}-ep${i}", [completedSetup: true, label: "${device.displayName} (CH${i})",
            isComponent: false, componentName: "ep$i", componentLabel: "Channel $i"
        ])
    }
}
@ithinkdancan
Copy link
Owner

@NoThanksNick THANKS for sharing, feel free to open a pull request to add the driver to the repo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants