Looking to transition from ST, looking for device help!

Hello,

I'm here to investigate the feasibility of transitioning my home automation from SmartThings to Hubitat. I'm not new to home automation, but when I read some of the threads here, I know there is plenty I have to (or at least may want to) learn. Including Amazon Echo personal assistants, I currently have 118 automation related devices. Obviously, I want to ensure a transition will be successful before I undertake this task. There is a lot to move across. The vast majority of my devices are Z-Wave and Lutron.

I have searched the list of supported devices, along with the supplemental list in the Community with others that users confirm work. A few of mine are not listed in either, but I'm most concerned about a couple that would be difficult to replace with an alternative. These are:

Smartenit ZBMLC30 Dual Load Controller (Zigbee) Model 4040B
This device currently works correctly with ST using a combination of a "Smart App" and device handler that I had to load through the web based user portal.

GoControl FS20Z-1 Isolated Contact Fixture Module
This is a single load 20A (Z-Wave) device which I suspect can likely be configured as a generic switch. I'm using three of these but really don't want to lose them in a transition.

GE/Jasco 34193 Enbrighten Portable Smart Motion Sensor
If absolutely necessary, I could find an alternate product to use in place of this. I only have one.

Other Devices I Could Not Find, But Can Likely Be Configured As Generic Switches/Devices
GE/Jasco 14288 (Z-Wave Plus) Enbrighten In-Wall Receptacle/Outlet
EcoNet Controls (Z-Wave Plus) EZW1204 Water Sensor
EcoNet Controls (Z-Wave Plus) EVC200 Water Valve
Philips HUE Play (Single) Light
Swidget (Z-Wave Plus) In-Wall Receptacle/Outlet

I don't have the time I can invest in writing custom device handlers since this would be a fairly big learning curve for me. I am open however to hiring someone (as necessary and dependent on cost) to do this for me. Most importantly for the Smartenit Load Controller. Since there is already some compatible code for ST, I'm hoping it may only require some tweaking.

If anyone can put forward thoughts, suggestions or other information to help me best decide how to transition, I will be most appreciative!

Thanks.

Can you post a link to the code for the drivers you referenced as someone knowledgeable enough may be able to provide some guidance as to whether this are simple to port over?

Also have you looked into hubconnect? This will allow you to leave devices on ST if there isn't a compatible driver in HE and still use them in your automatons.

1 Like

@at9 Thanks for the quick reply! I was unaware of HubConnect, but this may be an option. My preference is to move away from ST altogether, but in the event I can't support these other devices, I will look further into this.

This is the Device Handler code I am using in ST:

And the Smart App:

If anyone is able to check this out, it would be great :slight_smile:

Thanks again!

I received my hub 3 days ago and finally started to play around with it today. One of the first things I've tried is to pair this Smartenit dual load controller (ZBMLC30) model #4040B. It paired without any problem at all. I have not checked into the power monitoring side of things yet, but I'm very pleased to find the DH and App written for SmartThings works without modification. Each load can be independently switched on/off. This is a big relief for me!

I can also confirm that the GoControl FS20Z-1 20A Relay/Smart Fixture Module does pair correctly as a generic switch.

1 Like

regarding the DH the only thing I see in it that is not used with Hubitat is the tile code

/****************************************************************************
 * DRIVER NAME:	Smartenit Metering Dual Load Controller
 * DESCRIPTION:	Device handler for Smartenit Metering Dual Load Controller (#4040B)
 * Author:     Dhawal Doshi
 * Revision:   3
 * Date:       07/12/2018
 ****************************************************************************
 * This software is owned by Compacta and/or its supplier and is protected
 * under applicable copyright laws. All rights are reserved. We grant You,
 * and any third parties, a license to use this software solely and
 * exclusively on Compacta products. You, and any third parties must reproduce
 * the copyright and warranty notice and any other legend of ownership on each
 * copy or partial copy of the software.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS". COMPACTA MAKES NO WARRANTIES, WHETHER
 * EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
 * ACCURACY OR LACK OF NEGLIGENCE. COMPACTA SHALL NOT, UNDERN ANY CIRCUMSTANCES,
 * BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, SPECIAL,
 * INCIDENTAL OR CONSEQUENTIAL DAMAGES FOR ANY REASON WHATSOEVER.
 *
 * Copyright Compacta International, Ltd 2016. All rights reserved
 ****************************************************************************/
 
 import groovy.transform.Field

 @Field final Endpoint2 = 0x02
 @Field final MeteringCluster = 0x0702
 @Field final MeteringInstantDemand = 0x0400
 @Field final MeteringInstantDemandDivisor = 0x0304
 @Field final MeteringCurrentSummation = 0x0000

 @Field final OnOffCluster = 0x0006
 @Field final OnOffAttr = 0x0000
 @Field final OffCommand = 0x0000
 @Field final OnCommand  = 0x0001

 @Field final BasicCluster = 0x0000
 @Field final ModelIdAttr = 0x0005
 
metadata {
	// Automatically generated. Make future change here.
	definition (name: "Smartenit ZBMLC30", namespace: "smartenit", author: "Dhawal Doshi") {
		capability "Switch"
		capability "Power Meter"
		capability "Configuration"
		capability "Refresh"
		capability "Sensor"
		capability "Energy Meter"

		command "relay2_on"
        command "relay2_off"
        
        command "relay1_on"
        command "relay1_off" 
       
		// indicates that device keeps track of heartbeat (in state.heartbeat)
		attribute "heartbeat", "string"
        attribute "switch1", "ENUM",["on","off"]
		attribute "switch2", "ENUM",["on","off"]

		fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0702", outClusters: "0019", model: "ZBMLC30", deviceJoinName: "Smartenit Metering Dual Load Controller"
		fingerprint profileId: "0104", inClusters: "0000,0003,0006,0702", outClusters: "0019", model: "ZBMLC30-1", deviceJoinName: "Smartenit Metering Dual Load Controller"
}

	// simulator metadata
	simulator {
		// status messages
		status "on": "on/off: 1"
		status "off": "on/off: 0"

		// reply messages
		reply "zcl on-off on": "on/off: 1"
		reply "zcl on-off off": "on/off: 0"
	}

	preferences {
		section {
			image(name: 'educationalcontent', multiple: true, images: [
				"http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS1.jpg",
				"http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS2.jpg"
				])
		}
	}

def getFPoint(String FPointHex){
    return (Float)Long.parseLong(FPointHex, 16)
}

/*
* Parse incoming device messages to generate events
*/
def parse(String description) {
    def attrName = null
	def attrValue = null

	log.debug "parse... description: ${description}"
    
    def mapDescription = zigbee.parseDescriptionAsMap(description)
	log.debug "parse... mapDescription: ${mapDescription}"
    
    def event = zigbee.getEvent(description)
    log.debug "parse... event: ${event}"
    
    if(mapDescription.cluster == "0702")
    {
        if(mapDescription.attrId == "0400")
        {
            return sendEvent(name:"power", value: getFPoint(mapDescription.value)/100.0)
        }
        else if(mapDescription.attrId == "0000")
        {
            return sendEvent(name:"energy", value: getFPoint(mapDescription.value)/10000.0)
        }
    }
    else if(mapDescription.clusterInt == 6)
    {
        sendEvent(name: "parseSwitch", value: mapDescription)

        if(mapDescription.sourceEndpoint == "01") {
            attrName = "switch1"
        }else if(mapDescription.sourceEndpoint == "02") {
            attrName = "switch2"
        }else{
            return
        }

        if(mapDescription.command == "0B") {
            if(mapDescription.data[0] == "00") { 
                attrValue = "off"
            }else if(mapDescription.data[0] == "01") {
                attrValue = "on"
            }else{
                return
            }
        }else {
            if(mapDescription.value == "00") {
                attrValue = "off"
            }else if(mapDescription.value == "01") {
                attrValue = "on"
            }else{
                return
            }
        }

        sendEvent(name: attrName, value: attrValue)

        def result = createEvent(name: attrName, value: attrValue)
        return result
    }
    else if(mapDescription.clusterInt == 0)
    {
        sendEvent(name: "parseBasic", value: mapDescription)
        if(mapDescription.attrId == "0005")
        {
            attrName = "ModelId"
            attrValue = mapDescription.value.toString()
            log.debug "ModelName attrValue: ${attrValue}"
            state.ModelName = mapDescription.value
            log.debug "ModelName attr received: ${state.ModelName}"
            if (state.ModelName == "5A424D4C4333302D31") 
            {
                state.MeteringEP = 0x01
            }
        }
        sendEvent(name: attrName, value: attrValue)

        def result = createEvent(name: attrName, value: attrValue)
        return result
    }
    else if(mapDescription.clusterInt == 8)
    {
		log.debug "parsing level control..value: ${mapDescription.value}"
        if(mapDescription.value == "00") {
            attrValue = "off"
            sendEvent(name: "switch1", value: attrValue)
            sendEvent(name: "switch2", value: attrValue)
        }else if(mapDescription.value == "80") {
            sendEvent(name: "switch1", value: "off")
            sendEvent(name: "switch2", value: "on")
        }else if(mapDescription.value == "ff") {
            sendEvent(name: "switch1", value: "on")
            sendEvent(name: "switch2", value: "off")
        }else{
            return
        }
    }
    else
    {
    	if(description.contains("on/off")) {
        	log.debug "must be a report, but don't know which Endpoint"
        }else {
        	log.warn "Did not parse message: $description"
        }
    }

    return createEvent([:])
}

def relay1_off() {
	log.info "Turning Off Relay1"
	zigbee.off()
}

def relay1_on() {
	log.info "Turning On Relay1"
	zigbee.on()
}

def relay2_on(){
	log.info "Turning On Relay2"
    //zigbee.command(OnOffCluster, OnCommand, additionalParams=[destEndpoint:Endpoint2])
    def cmds = []
    cmds << "st cmd 0x${device.deviceNetworkId} ${Endpoint2} ${OnOffCluster} ${OnCommand} {}"
	cmds
}

def relay2_off(){
	log.info "Turning Off Relay2"
	//zigbee.command(OnOffCluster, OffCommand, additionalParams=[destEndpoint:Endpoint2])
    def cmds = []
    cmds << "st cmd 0x${device.deviceNetworkId} ${Endpoint2} ${OnOffCluster} ${OffCommand} {}"
	cmds
}

def refresh() {
	if( (state.MeteringEP == null) || (state.ModelName == null) || (state.MeterBound == null)) {
    	log.warn "Device not configured, configuring now.."
        return configure()
    }

    def configCmds = []
    if(state.MeterBound == 0) {
        state.MeterBound = 1
        configCmds = [ "zdo bind 0x${device.deviceNetworkId} ${state.MeteringEP} 0x01 ${MeteringCluster} {${device.zigbeeId}} {}" ]
    }

    sendEvent(name: "heartbeat", value: "alive", displayed:false)

    return (
        zigbee.onOffRefresh() + 
        zigbee.readAttribute(OnOffCluster, OnOffAttr, [destEndpoint:Endpoint2]) +
        zigbee.readAttribute(MeteringCluster, MeteringCurrentSummation, [destEndpoint:state.MeteringEP]) +
        zigbee.readAttribute(MeteringCluster, MeteringInstantDemand, [destEndpoint:state.MeteringEP]) +
        configCmds
    )
}

def configure() {
    log.debug "Configuring Reporting and Bindings."
    
	runEvery15Minutes(refresh)

	state.ModelName = ""
    state.MeteringEP = 0x02
    state.MeterBound = 0
    
    def retrieveModel = [
    	zigbee.readAttribute(BasicCluster, ModelIdAttr)
    ]
    
	def configCmds = [
        //"zdo bind 0x${device.deviceNetworkId} ${state.MeteringEP} 0x01 ${MeteringCluster} {${device.zigbeeId}} {}",
        "zdo bind 0x${device.deviceNetworkId} ${Endpoint2} 0x01 ${OnOffCluster} {${device.zigbeeId}} {}"
    ]

	return  retrieveModel + configCmds + zigbee.onOffConfig()
}

You should use Hubconnect with your SmartThings hub for any cloud based devices like Alexa (using echo speaks) as they are cloud based anyway and their data tends to slow down Hubitat hubs .
Also before you get into creating to many rules you may want to look at Node-Red as well for rule creation.

Thanks for having a look!

I did find that things were much slower with Alexa when controlling Hubitat devices (Lutron) etc., as opposed to creating routines with Alexa using the Lutron device. I have changed all of these over and speed, with those devices is much better. This isn't a device that I would actually need to control by Alexa, so I'm hoping to modify the DH. All my other devices have now been transitioned to HE, and I've removed ST from service.

In regards to the SmartenIt load controller, I copied the DH and App over directly and was able to save them both without errors. So, that's at least a positive sign. I can go into the device and control each relay individually, however there are a few things that are not quite the way they should be. These are:

  1. No child devices were created. These did exist in SmartThings.
  2. Energy consumption is not reporting. There is the heartbeat (alive) along with on/off status of switch 1 and switch 2.
  3. When I started to dig into my logs, I found some issues...

My plan is to dig into device handlers and applications. I've installed all the necessary tools onto my computer, but I'll admit, Groovy is new to me so it's going to be a bit of a learning curve. I used to write software for small business, but this was nearly 30 years ago and in a very dissimilar language. I just need to become familiar with typical coding conventions and practices with Groovy and start experimenting.

If you can offer any suggestions based on the log and other points, I would be very grateful!

hey D I do not have SmartenIt but give this DH a try and see if it reporting energy consumption.
Still learning Groovy as well
(ps. our mutual friend from Chestermere said you had come down the Hubitat rabbit hole you will find a lot of hard core OCD like minded people here!
welcome
Still trying to convince him to forget Wink and do the same)

/****************************************************************************
 * DRIVER NAME:	Smartenit Metering Dual Load Controller
 * DESCRIPTION:	Device handler for Smartenit Metering Dual Load Controller (#4040B)
 * Author:     Dhawal Doshi
 * Revision:   3
 * Date:       07/12/2018
 ****************************************************************************
 * This software is owned by Compacta and/or its supplier and is protected
 * under applicable copyright laws. All rights are reserved. We grant You,
 * and any third parties, a license to use this software solely and
 * exclusively on Compacta products. You, and any third parties must reproduce
 * the copyright and warranty notice and any other legend of ownership on each
 * copy or partial copy of the software.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS". COMPACTA MAKES NO WARRANTIES, WHETHER
 * EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
 * ACCURACY OR LACK OF NEGLIGENCE. COMPACTA SHALL NOT, UNDERN ANY CIRCUMSTANCES,
 * BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, SPECIAL,
 * INCIDENTAL OR CONSEQUENTIAL DAMAGES FOR ANY REASON WHATSOEVER.
 *
 * Copyright Compacta International, Ltd 2016. All rights reserved
 ****************************************************************************/
 
 import groovy.transform.Field

 @Field final Endpoint2 = 0x02
 @Field final MeteringCluster = 0x0702
 @Field final MeteringInstantDemand = 0x0400
 @Field final MeteringInstantDemandDivisor = 0x0304
 @Field final MeteringCurrentSummation = 0x0000

 @Field final OnOffCluster = 0x0006
 @Field final OnOffAttr = 0x0000
 @Field final OffCommand = 0x0000
 @Field final OnCommand  = 0x0001

 @Field final BasicCluster = 0x0000
 @Field final ModelIdAttr = 0x0005
 
metadata {
	// Automatically generated. Make future change here.
	definition (name: "Smartenit ZBMLC30 test", namespace: "smartenit", author: "Dhawal Doshi") {
		capability "Switch"
		capability "Power Meter"
		capability "Configuration"
		capability "Refresh"
		capability "Sensor"
		capability "Energy Meter"

		command "relay2_on"
        command "relay2_off"
        
        command "relay1_on"
        command "relay1_off" 
       
		// indicates that device keeps track of heartbeat (in state.heartbeat)
		attribute "heartbeat", "string"
        attribute "switch1", "ENUM",["on","off"]
		attribute "switch2", "ENUM",["on","off"]

		fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0702", outClusters: "0019", model: "ZBMLC30", deviceJoinName: "Smartenit Metering Dual Load Controller"
		fingerprint profileId: "0104", inClusters: "0000,0003,0006,0702", outClusters: "0019", model: "ZBMLC30-1", deviceJoinName: "Smartenit Metering Dual Load Controller"
	}
	// simulator metadata
	simulator {
		// status messages
		status "on": "on/off: 1"
		status "off": "on/off: 0"

		// reply messages
		reply "zcl on-off on": "on/off: 1"
		reply "zcl on-off off": "on/off: 0"
	}    
}

def getFPoint(String FPointHex){
    return (Float)Long.parseLong(FPointHex, 16)
}

/*
* Parse incoming device messages to generate events
*/
def parse(String description) {
    def attrName = null
	def attrValue = null

	log.debug "parse... description: ${description}"
    
    def mapDescription = zigbee.parseDescriptionAsMap(description)
	log.debug "parse... mapDescription: ${mapDescription}"
    
    def event = zigbee.getEvent(description)
    log.debug "parse... event: ${event}"
    
    if(mapDescription.cluster == "0702")
    {
        if(mapDescription.attrId == "0400")
        {
            return sendEvent(name:"power", value: getFPoint(mapDescription.value)/100.0)
        }
        else if(mapDescription.attrId == "0000")
        {
            return sendEvent(name:"energy", value: getFPoint(mapDescription.value)/10000.0)
        }
    }
    else if(mapDescription.clusterInt == 6)
    {
        sendEvent(name: "parseSwitch", value: mapDescription)

        if(mapDescription.sourceEndpoint == "01") {
            attrName = "switch1"
        }else if(mapDescription.sourceEndpoint == "02") {
            attrName = "switch2"
        }else{
            return
        }

        if(mapDescription.command == "0B") {
            if(mapDescription.data[0] == "00") { 
                attrValue = "off"
            }else if(mapDescription.data[0] == "01") {
                attrValue = "on"
            }else{
                return
            }
        }else {
            if(mapDescription.value == "00") {
                attrValue = "off"
            }else if(mapDescription.value == "01") {
                attrValue = "on"
            }else{
                return
            }
        }

        sendEvent(name: attrName, value: attrValue)

        def result = createEvent(name: attrName, value: attrValue)
        return result
    }
    else if(mapDescription.clusterInt == 0)
    {
        sendEvent(name: "parseBasic", value: mapDescription)
        if(mapDescription.attrId == "0005")
        {
            attrName = "ModelId"
            attrValue = mapDescription.value.toString()
            log.debug "ModelName attrValue: ${attrValue}"
            state.ModelName = mapDescription.value
            log.debug "ModelName attr received: ${state.ModelName}"
            if (state.ModelName == "5A424D4C4333302D31") 
            {
                state.MeteringEP = 0x01
            }
        }
        sendEvent(name: attrName, value: attrValue)

        def result = createEvent(name: attrName, value: attrValue)
        return result
    }
    else if(mapDescription.clusterInt == 8)
    {
		log.debug "parsing level control..value: ${mapDescription.value}"
        if(mapDescription.value == "00") {
            attrValue = "off"
            sendEvent(name: "switch1", value: attrValue)
            sendEvent(name: "switch2", value: attrValue)
        }else if(mapDescription.value == "80") {
            sendEvent(name: "switch1", value: "off")
            sendEvent(name: "switch2", value: "on")
        }else if(mapDescription.value == "ff") {
            sendEvent(name: "switch1", value: "on")
            sendEvent(name: "switch2", value: "off")
        }else{
            return
        }
    }
    else
    {
    	if(description.contains("on/off")) {
        	log.debug "must be a report, but don't know which Endpoint"
        }else {
        	log.warn "Did not parse message: $description"
        }
    }

    return createEvent([:])
}

def relay1_off() {
	log.info "Turning Off Relay1"
	zigbee.off()
}

def relay1_on() {
	log.info "Turning On Relay1"
	zigbee.on()
}

def relay2_on(){
	log.info "Turning On Relay2"
    //zigbee.command(OnOffCluster, OnCommand, additionalParams=[destEndpoint:Endpoint2])
    def cmds = []
    cmds << "cmd 0x${device.deviceNetworkId} ${Endpoint2} ${OnOffCluster} ${OnCommand} {}"
	cmds
}

def relay2_off(){
	log.info "Turning Off Relay2"
	//zigbee.command(OnOffCluster, OffCommand, additionalParams=[destEndpoint:Endpoint2])
    def cmds = []
    cmds << "cmd 0x${device.deviceNetworkId} ${Endpoint2} ${OnOffCluster} ${OffCommand} {}"
	cmds
}

def refresh() {
	if( (state.MeteringEP == null) || (state.ModelName == null) || (state.MeterBound == null)) {
    	log.warn "Device not configured, configuring now.."
        return configure()
    }

    def configCmds = []
    if(state.MeterBound == 0) {
        state.MeterBound = 1
        configCmds = [ "zdo bind 0x${device.deviceNetworkId} ${state.MeteringEP} 0x01 ${MeteringCluster} {${device.zigbeeId}} {}" ]
    }

    sendEvent(name: "heartbeat", value: "alive", displayed:false)

    return (
        zigbee.onOffRefresh() + 
        zigbee.readAttribute(OnOffCluster, OnOffAttr, [destEndpoint:Endpoint2]) +
        zigbee.readAttribute(MeteringCluster, MeteringCurrentSummation, [destEndpoint:state.MeteringEP]) +
        zigbee.readAttribute(MeteringCluster, MeteringInstantDemand, [destEndpoint:state.MeteringEP]) +
        configCmds
    )
}

def configure() {
    log.debug "Configuring Reporting and Bindings."
    
	runEvery15Minutes(refresh)

	state.ModelName = ""
    state.MeteringEP = 0x02
    state.MeterBound = 0
    
    def retrieveModel = [
    	zigbee.readAttribute(BasicCluster, ModelIdAttr)
    ]
    
	def configCmds = [
        //"zdo bind 0x${device.deviceNetworkId} ${state.MeteringEP} 0x01 ${MeteringCluster} {${device.zigbeeId}} {}",
        "zdo bind 0x${device.deviceNetworkId} ${Endpoint2} 0x01 ${OnOffCluster} {${device.zigbeeId}} {}"
    ]

	return  retrieveModel + configCmds + zigbee.onOffConfig()
}

Thank you very much! I'll give it a try and post back my findings.

Also the configure command has the line
"runEvery15Minutes(refresh)"
this command is more like a run every (random time less than 15 minutes)
once selecting configure it will run it all the time to update the info.

Hi,

Did the smartInit DTH work out ?
I'm about to start a migration process from ST as ST are retiring m5 v1 hub - so hubconnect not an option anymore

I'm worried about the Smartenit ZBMLC30 controller. wondering if the controller above worked out ?

Thanks,
Ton

I haven't had a chance to dig into it any further. The problem with the ZBMLC30 is primarily with the child device for the second relay. It certainly is possible to get it working, I just haven't had any time to work on it. Mine is used to control two heating wire circuits on the roof during the winter.

1 Like

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.