Minoston MP22ZP Outdoor Plug with Power Measurement

Is there a driver for the Minoston MP22ZP? I can't seem to find an appropriate one to use.

Have you tried the EVA LOGIK drivers? Ministon devices are known to work with those.

2 Likes

Yeah, that works for the regular outdoor plug, but not the energy-monitoring one.

Ah, bummer, I figured they would have one. I have a BUNCH of the regular MP22Z.

If you are interested in working on it... I am curious enough to start writing a driver for it (based on my own MP22Z devices). I have not worked on Z-Wave devices much (I have one driver for the WADWAZ contact sensor) but willing to give it a go.

1 Like

I don't have time to work on it right now, but I can test for you, if you like. Let me know if there's some debugging info I can give you.

No problems, you can check things whenever you want. Since I have the older MP22Z I can test simple stuff like on/off (working) and some REAL basic stuff... but I will definitely have to have some logging when it comes to the power data returned.

I will let you know when I have a basic version ready (hopefully a couple days at most).

If you need a base to start with, feel free to use this: Hubitat/zooz-zen-plugs.groovy at main ยท jtp10181/Hubitat ยท GitHub

I do have a some slightly complex stuff in there for the parameter handling, but it could be reworked for another device without too much trouble I think.

The EVA / Minoston stuff is also very similar to Zooz. In fact the ZEN05 outdoor plug looks identical but does not have power monitoring. That driver however is also for the ZEN04 which has the power monitoring.

Thanks! I might take a look at some point. I was mostly working from a driver I never released that just did on/off and looking at the Inovelli outdoor smart plug driver.

Here is a link to the initial version of the driver I started working on.
"https://www.drdsnell.com/projects/hubitat/drivers/Minoston MP22ZP.groovy"

I took a bit longer because (since this deals with power) I really wanted to make sure the basic on/off worked and reported properly. Please note that I do NOT have an MP22ZP, but only an MP22Z. That said:

  1. On/Off should be working just fine.
  2. There are commands to get/set specific parameters on the device (some are listed in the device manual). Eventually I would want to add most of these type of things into preferences.
  3. There is a preference for Power Monitoring. If enabled it will try to get the Meter and Indicator class information (since the Z does not support those). Even if it is disabled the ZP should work normally, you just would not get Meter or Indicator information. At this time any resulting data from the Meter or Indicator just gets dumped into the log. Since I have no idea how it will be presented, I did not want to dump "bad" information into an Event.
  4. Please enable Trace logging for a bit, try functions out, like enabling the Power Monitoring then performing a refresh command. Trying the various features you can do with the button on the device. Then, please send me the resulting logs in a message. I will try to handle the resulting data and make sure it gets supported in the driver.
  5. With Power Monitoring on, there are a few things I would want checked specifically:
    a) refresh with the outlet off.
    b) refresh with the outlet on, but nothing plugged into it.
    c) refresh with the outlet on, and something plugged into it that will use power.

That link is 404. I tried removing the space and replacing it with %20 to no avail.

My bad... I just double-checked and discovered that when I created the file I mis-named it. I left off the P. I just renamed it so here is the link again. Sorry about that:
https://www.drdsnell.com/projects/hubitat/drivers/Minoston%20MP22ZP.groovy

That link is also 404.

I just right-clicked and opened in a new tab, that one is there. Maybe the browser is holding the previous 404 cached? Please try Shift-refresh for it if using Chrome.

As backup (need the URL to work for import), I will try to paste it below:

/*
* Minoston MP22Z/P Outdoor Plugs
*
* Description:
* This Hubitat driver is designed for use with the following device(s):
*   Minoston MP22Z
*   Minoston MP22ZP
*
* Features List:
* Ability to turn on/off the switch
* Ability to report the current power usage
* Ability to check a website (mine) if there is a newer version of the driver available
* 
* Licensing:
* Copyright 2021 David Snell
* 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.
*
* Version Control:
* 0.1.0 - Initial attempt at driver
* 
* Thank you(s):
* I would like to thank @Cobra his contributions to the community. Parts based on Cobra's driver update code
* have been included at the bottom of the driver and are noted as such.
*/

// Returns the driver name
def DriverName(){
    return "Minoston MP22ZP"
}

// Returns the driver version
def DriverVersion(){
    return "0.1.0"
}

metadata{
    definition( name: "Minoston MP22ZP", namespace: "Snell", author: "David Snell", importUrl: "https://www.drdsnell.com/projects/hubitat/drivers/Minoston MP22ZP.groovy" ){
        capability "Actuator"
		capability "Configuration" // No relevant commands to use this for at this time
	    capability "Refresh"
        capability "Outlet"
        capability "Switch"
        capability "PowerMeter"
		capability "Sensor"
        capability "Health Check"
        capability "PushableButton"
        
        command "ParameterGet", [ [ name:"Parameter", type:"NUMBER", description:"Parameter Number (blank gets all)", constraints:[ "NUMBER" ] ] ]
	    command "ParameterSet",[ [ name:"Parameter", type:"NUMBER", description:"Parameter Number", constraints:[ "NUMBER" ] ], [ name:"Size", type:"NUMBER", description:"Size", constraints:[ "NUMBER" ] ], [ name:"Value", type:"NUMBER", description:"Value", constraints:[ "NUMBER" ] ] ]
        
		// Attributes being built into the device
		attribute "Driver Name", "string" // Identifies the driver's name
		attribute "Driver Status", "string" // Identifies the driver's update status
		attribute "Driver Version", "string" // Identifies the driver's version number
        //attribute "switch", "ENUM", [ "on", "off" ] // Just reference for the switch state
        //attribute "power", "number" // Reference, power in watts
        attribute "Radio Power", "string" // 
        
        fingerprint deviceId: "65287", deviceType: "65280", inClusters: "0x5E,0x6C,0x55,0x9F", outClusters: "", manufacturer: "786", model: "MP22Z", deviceJoinName: "Minoston MP22Z"
        fingerprint deviceId: "65287", deviceType: "65280", inClusters: "0x5E,0x6C,0x55,0x9F", outClusters: "", manufacturer: "786", model: "MP22ZP", deviceJoinName: "Minoston MP22ZP"
	}
	preferences{
		section{
            if( ShowAllPreferences || ShowAllPreferences == null ){ // Show the preferences options
    			input( type: "enum", name: "LogType", title: "<b>Enable Logging?</b>", required: true, multiple: false, options: [ "None", "Info", "Debug", "Trace" ], defaultValue: "Info" )
                input( type: "bool", name: "PowerMonitoring", title: "<b>Enable Power Monitoring?</b>", defaultValue: false )
                input( type: "bool", name: "ShowAllPreferences", title: "<b>Show All Preferences?</b>", defaultValue: true )
            } else {
                input( type: "bool", name: "ShowAllPreferences", title: "<b>Show All Preferences?</b>", defaultValue: true )
            }
		}
	}
}

// Sets some basic values of the driver
def updated(){
    // Clear State Variables
    state.clear()
    
    // Unschedule existing tasks
    unschedule()
    
    // Set the basic driver info known at this moment
    ProcessEvent( "Driver Name", DriverName() )
    ProcessEvent( "Driver Version", DriverVersion() )
    
	// Schedule the daily driver version check
	schedule( new Date(), CheckForUpdate )
    
    Logging( "Updated", 2 )
}

// Used by switches and outlets for on
def on() {
    ZWaveCommands([
        zwave.basicV1.basicSet(value: 0xFF),
        zwave.switchBinaryV1.switchBinaryGet(),
        zwave.basicV1.basicGet()
    ])
}

// Used by switches and outlets for off
def off() {
    ZWaveCommands([
        zwave.basicV1.basicSet(value: 0x00),
        zwave.switchBinaryV1.switchBinaryGet(),
        zwave.basicV1.basicGet()
    ])
}

// Refreshes the device information by clearing states and tamper
def refresh(){
    ZWaveCommands( [
        zwave.switchBinaryV1.switchBinaryGet(),
        zwave.basicV1.basicGet(),
        zwave.powerlevelV1.powerlevelGet()
    ] )
    if( PowerMonitoring == true ){
        ZWaveCommands( [
            zwave.meterV5.meterGet(),
            zwave.indicatorV3.indicatorGet()
        ] )
    }
    
}

// parse handles splitting up the z-wave reports and making sure the commands are handled
def parse( String description ){
    def Split = description.split()
    def CommandClass = Split[ 4 ].take( 2 )
    def Command = Split[ 4 ].drop( 2 ).take( 2 )
    def Type = Split[ 6 ].take( 2 )
    def Value = Split[ 7 ].take( 2 )
	if( description.startsWith( "Err" ) ){
		Logging( "Parse Error: ${ description }", 3 )
	} else {
        switch( CommandClass ){
            case "20": // Basic
                HandleBasic( Command, Type )
                break
            case "73": // COMMAND_CLASS_POWERLEVEL
                HandlePowerLevel( Command, Type )
                break
            case "25": // COMMAND_CLASS_SWITCH_BINARY_V2
                HandleSwitch( Command, Type )
                break
            case "70": // CONFIGURATION
                HandleConfiguration( Command, Type, Value )
                break
            case "84": // WakeUp
                HandleWakeUp( Command, Type, Value )
                break
            case "86": // Version
                HandleVersion( Command, Type, Value )
                break
            // Classes that will (likely) need handling so look for the logging
            case "87": // COMMAND_CLASS_INDICATOR_V3
            case "32": // COMMAND_CLASS_METER_V5
                Logging( "Class=${ CommandClass } Command=${ Command } Description=${ description }", 3 )
                break            
            // Class(es) that will likely not have anything done unless something useful crops up
            case "10": // GENERIC_TYPE GENERIC_TYPE_SWITCH_BINARY
            case "5E": // COMMAND_CLASS_ZWAVEPLUS_INFO_V2
            case "70": // COMMAND_CLASS_CONFIGURATION_V4
            case "85": // COMMAND_CLASS_ASSOCIATION_V3
            case "8E": // COMMAND_CLASS_MULTI_CHANNEL_ASSOCIATION_V4
            case "59": // COMMAND_CLASS_ASSOCIATION_GRP_INFO_V3
            case "71": // COMMAND_CLASS_NOTIFICATION_V8
            case "55": // COMMAND_CLASS_TRANSPORT_SERVICE_V2
            case "86": // COMMAND_CLASS_VERSION_V2
            case "72": // COMMAND_CLASS_MANUFACTURER_SPECIFIC_V2
            case "5A": // COMMAND_CLASS_DEVICE_RESET_LOCALLY
            case "9F": // COMMAND_CLASS_SECURITY_2
            case "7A": // COMMAND_CLASS_FIRMWARE_UPDATE_MD_V5
                Logging( "Class=${ CommandClass } Command=${ Command } Description=${ description }", 3 )
                break
            // Not doing nything with these class(es)
            case "6C": // COMMAND_CLASS_SUPERVISION
                break
            // Unknown/unexpected clases, pop info in debug to determine how they should be handled
            default:
                Logging( "Unexpected CommandClass=${ CommandClass } Command=${ Command } Description=${ description }", 3 )
                break
        }
	}
}

// Handle basic events from Z-Wave
def HandleBasic( Command, Type ){
    def TypeInt = Integer.parseInt( Type, 16 )
    switch( Command ){
        case "03":
            if( TypeInt == 0 ){
                ProcessEvent( "switch", "off", null, true )
            } else if( TypeInt == 255 ){
                ProcessEvent( "switch", "on", null, true )
            }
            break
        default:
            Logging( "Unknown Basic report. Command=${ Command } Type=${ TypeInt }", 3 )
        break
    }
}

// Handle switch events from Z-Wave
def HandleSwitch( Command, Type ){
    def TypeInt = Integer.parseInt( Type, 16 )
    switch( Command ){
        case "03":
            if( TypeInt == 0 ){
                ProcessEvent( "switch", "off", null, true )
            } else if( TypeInt == 255 ){
                ProcessEvent( "switch", "on", null, true )
            }
            break
        case "06":
        default:
            Logging( "Unknown Switch report. Command=${ Command } Type=${ TypeInt }", 3 )
        break
    }
}

// Handle radio power level events from Z-Wave
def HandlePowerLevel( Command, Type ){
    def TypeInt = Integer.parseInt( Type, 16 )
    switch( Command ){
        case "03":
            if( TypeInt == 0 ){
                ProcessEvent( "Radio Power", "Normal" )
            } else {
                ProcessEvent( "Radio Power", "-${ TypeInt } dB" )
            }
            break
        case "06":
        default:
            Logging( "Unknown Power Level report. Command=${ Command } Type=${ TypeInt }", 3 )
        break
    }
}

// Handle configuration events from Z-Wave
def HandleConfiguration( Command, Type, Value ){
    switch( Command ){
        case "06": // Something
            Logging( "Parameter ${ Type } = ${ Value }", 3 )
            break
        default:
            Logging( "Unknown Configuration Command:${ Command } Type:${ Type } = ${ Value }", 3 )
        break
    }
}

// Handle the basic events from Z-Wave
def HandleVersion( Command, Type, Value ){
    switch( Command ){
        case "12": // Something
            Logging( "Version #12: ${ Type } = ${ Value }", 3 )
            break
        case "14": // Something
            Logging( "Version #14: ${ Type } = ${ Value }", 3 )
            break
        default:
            Logging( "Unknown Version Command:${ Command } Type:${ Type } = ${ Value }", 3 )
        break
    }
}

// Handle the Wake Up events from Z-Wave
def HandleWakeUp( Command, Type, Value ){
    switch( Command ){
        case "07":
            Logging( "WakeUp Interval Report Type: ${ Type } = ${ Value }", 4 )
            // Set the interval to every 10 minutes (60000 milliseconds)
	        //sendEvent( name: "checkInterval", value: 60000, displayed: false, data: [ protocol: "zwave", hubHardwareId: device.hub.hardwareID ] )
        default:
            Logging( "Unknown WakeUp Command=${ Command }, Type: ${ Type } = ${ Value }", 3 )
        break
    }
}

// Set a parameter, based on Hubitat examples
List<String> ParameterSet( Parameter, Size, Value ){
    if( Parameter == null ){
        Logging( "Missing required setting: Parameter", 5 )
    } else if( Size == null ){
        Logging( "Missing required setting: Size", 5 )
    } else if( Value == null ){
		Logging( "Missing required setting: Value", 5 )
    } else {
        Logging( "Setting ${ Parameter } to ${ Value }", 4 )
        ZWaveCommands( [
            zwave.configurationV1.configurationSet( scaledConfigurationValue: Value, parameterNumber: Parameter, size: Size ),
	    	zwave.configurationV1.configurationGet( parameterNumber: Parameter )
        ] )
    }
}

// Get information on a parameter, based on Hubitat examples
List<String> ParameterGet( Parameter ){
    if( Parameter != null ){
        Logging( "Getting ${ Parameter }", 4 )
		ZWaveCommand( zwave.configurationV1.configurationGet( parameterNumber: Parameter ) )
    }
}	

// Handle Z-Wave commands to send 
private ZWaveCommand( hubitat.zwave.Command Command ){
    if( getDataValue( "zwaveSecurePairingComplete" ) == "true" && getDataValue( "S2" ) == null ){
        zwave.securityV1.securityMessageEncapsulation().encapsulate( Command ).format()
    } else {
        Command.format()
    }
}

// Put multiple Z-Wave commands together with a delay between them
private ZWaveCommands( Commands, Delay = 500 ){
    delayBetween( Commands.collect{ ZWaveCommand( it ) }, Delay )
}

// Configures the device, typically at install or when preferences are saved
def configure(){
	Logging( "Configuring device...", 2 )
    updated()
}

// installed is called when the device is installed, all it really does is run updated
def installed(){
	Logging( "Installed", 2 )
	updated()
}

// initialize is called when the device is initialized, all it really does is run updated
def initialize(){
	Logging( "Initialized", 2 )
	updated()
}

// Process data to check against current state value and then send an event if it has changed
def ProcessEvent( Variable, Value, Unit = null, ForceEvent = false ){
    if( ( state."${ Variable }" != Value ) || ( ForceEvent == true ) ){
        state."${ Variable }" = Value
        if( Unit != null ){
            Logging( "Event: ${ Variable } = ${ Value }${ Unit }", 4 )
            sendEvent( name: "${ Variable }", value: Value, unit: Unit, isStateChanged: true )
        } else {
            Logging( "Event: ${ Variable } = ${ Value }", 4 )
            sendEvent( name: "${ Variable }", value: Value, isStateChanged: true )
        }
    }
}

// Process data to check against current state value and then send an event if it has changed
def ProcessState( Variable, Value ){
    if( state."${ Variable }" != Value ){
        Logging( "State: ${ Variable } = ${ Value }", 4 )
        state."${ Variable }" = Value
    }
}

// Handles whether logging is enabled and thus what to put there.
def Logging( LogMessage, LogLevel ){
	// Add all messages as info logging
    if( ( LogLevel == 2 ) && ( LogType != "None" ) ){
        log.info( "${ device.displayName } - ${ LogMessage }" )
    } else if( ( LogLevel == 3 ) && ( ( LogType == "Debug" ) || ( LogType == "Trace" ) ) ){
        log.debug( "${ device.displayName } - ${ LogMessage }" )
    } else if( ( LogLevel == 4 ) && ( LogType == "Trace" ) ){
        log.trace( "${ device.displayName } - ${ LogMessage }" )
    } else if( LogLevel == 5 ){
        log.error( "${ device.displayName } - ${ LogMessage }" )
    }
}

// Checks drdsnell.com for the latest version of the driver
// Original inspiration from @cobra's version checking
def CheckForUpdate(){
    ProcessEvent( "Driver Name", DriverName() )
    ProcessEvent( "Driver Version", DriverVersion() )
	httpGet( uri: "https://www.drdsnell.com/projects/hubitat/drivers/versions.json", contentType: "application/json" ){ resp ->
        switch( resp.status ){
            case 200:
                if( resp.data."${ DriverName() }" ){
                    CurrentVersion = DriverVersion().split( /\./ )
                    if( resp.data."${ DriverName() }".version == "REPLACED" ){
                       ProcessEvent( "Driver Status", "Driver replaced, please use ${ resp.data."${ state.'Driver Name' }".file }" )
                    } else if( resp.data."${ DriverName() }".version == "REMOVED" ){
                       ProcessEvent( "Driver Status", "Driver removed and no longer supported." )
                    } else {
                        SiteVersion = resp.data."${ DriverName() }".version.split( /\./ )
                        if( CurrentVersion == SiteVersion ){
                            Logging( "Driver version up to date", 3 )
				            ProcessEvent( "Driver Status", "Up to date" )
                        } else if( ( CurrentVersion[ 0 ] as int ) > ( SiteVersion [ 0 ] as int ) ){
                            Logging( "Major development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
				            ProcessEvent( "Driver Status", "Major development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" )
                        } else if( ( CurrentVersion[ 1 ] as int ) > ( SiteVersion [ 1 ] as int ) ){
                            Logging( "Minor development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
				            ProcessEvent( "Driver Status", "Minor development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" )
                        } else if( ( CurrentVersion[ 2 ] as int ) > ( SiteVersion [ 2 ] as int ) ){
                            Logging( "Patch development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version", 3 )
				            ProcessEvent( "Driver Status", "Patch development ${ CurrentVersion[ 0 ] }.${ CurrentVersion[ 1 ] }.${ CurrentVersion[ 2 ] } version" )
                        } else if( ( SiteVersion[ 0 ] as int ) > ( CurrentVersion[ 0 ] as int ) ){
                            Logging( "New major release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
				            ProcessEvent( "Driver Status", "New major release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" )
                        } else if( ( SiteVersion[ 1 ] as int ) > ( CurrentVersion[ 1 ] as int ) ){
                            Logging( "New minor release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
				            ProcessEvent( "Driver Status", "New minor release ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" )
                        } else if( ( SiteVersion[ 2 ] as int ) > ( CurrentVersion[ 2 ] as int ) ){
                            Logging( "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available", 2 )
				            ProcessEvent( "Driver Status", "New patch ${ SiteVersion[ 0 ] }.${ SiteVersion[ 1 ] }.${ SiteVersion[ 2 ] } available" )
                        }
                    }
                } else {
                    Logging( "${ DriverName() } is not published on drdsnell.com", 2 )
                    ProcessEvent( "Driver Status", "${ DriverName() } is not published on drdsnell.com" )
                }
                break
            default:
                Logging( "Unable to check drdsnell.com for ${ DriverName() } driver updates.", 2 )
                break
        }
    }
}

Wow, sure enough, my browser was caching the 404. That seems very wrong. Anyway, I was able to download it; now to see if I know how to install it.

You can cheat if you want:

  1. Go to the "Drivers code" page on your Hubitat.
  2. Select the New Driver button.
  3. Select the Import button.
  4. Copy/paste the driver URL into the field and select Import.
  5. Select the Save button.

Then just go to the device's page and change the device's "Type" dropdown to select the Minoston MP22ZP driver... which will likely be ALMOST at the bottom of the huge dropdown list (the Virtual driver section is the very bottom, user-loaded drivers are now inconveniently located above that) and then Save Device.

So, I've added your driver and reconfigured my device to use it. It's not clear what I'm supposed to do to see the power use. Here's screenshots of what I've got so far:


Not sure what the "is not published on drdsnell.com" stuff is.

Every day all of my drivers check my website to see if there is an updated version or not. However, I have not officially published this one yet (I create a project thread and then add the driver into the version file it checks). Drivers cannot update themselves, but at least I can provide users a status that a newer version is available.

Power Monitoring will not show up yet because I do not know how this plug reports it. You have it Power Monitoring on, so that is good. Can you switch the "Enable Logging" to Trace, perform a Refresh, and send me (as a message would be easier) the log information it provides?

At the very least I see there may be a couple bugs to fix since I have no idea why it has a numberOfButtons or battery... Was that something from a previous driver you tried?

Download the Hubitat app