Horstmann ASR-ZW (AKA Secure SSR303)

I'm having an early spring clean of my "box of many gadgets". If I can get them working with HE I'll keep them, if not they are going on Ebay.

I'm currently testing a Hortsmann ASR-ZW. It's a heating controller designed to work with the HRT4-ZW Thermostat, but is also a Zwave Relay/Switch in it's own right. If I can get it working it would be better than my current heating switch (a simple Zwave switch module) as it has easy to access manual override buttons on it and looks a bit more professional than my module in a plastic box solution.

I have got it paired to HE OK. I have tried various generic drivers and a modified ST DTH but the one that has most success is the Generic Zwave relay. Using that I can turn it on and off from HE, but if I use the manual switches on the unit it doesn't report back to HE unless I hit refresh on the driver page.

So I don't think it's far away from working perfectly, can anyone suggest a way to get over the line on this and get it to report back to HE correctly?

This is the device data:

deviceType: 3
inClusters: 0x72,0x86,0x40,0x25
deviceId: 1
MSR: 0059-0003-0001
manufacturer: Horstmann Controls Limited

And the logs when I turn it off and on from the device page:

dev:16662019-02-11 12:07:55.071 pm debugSwitchBinaryReport: 255
dev:16662019-02-11 12:07:55.070 pm debugparse description: zw device: 64, command: 2503, payload: FF
dev:16662019-02-11 12:07:55.001 pm infoHeating Relay Horstmann was turned on
dev:16662019-02-11 12:07:54.997 pm debugSwitchBinaryReport: 255
dev:16662019-02-11 12:07:54.995 pm debugparse description: zw device: 64, command: 2503, payload: FF
dev:16662019-02-11 12:07:51.319 pm infoHeating Relay Horstmann was turned off
dev:16662019-02-11 12:07:51.315 pm debugSwitchBinaryReport: 0
dev:16662019-02-11 12:07:51.312 pm debugparse description: zw device: 64, command: 2503, payload: 00

Thanks in advance

@mike.maxwell Any thoughts on this Mike? Do you think a driver change would be able to get this to automatically send it's local switch changes back to HE? At the moment I'm using RM to refresh it every 10 minutes which is working OK, but it would obviously be cleaner to not have to.

any progress on this? I'll be migrating soon and I already have this working in smartthings using this dth:

/**
 *  Copyright 2015 SmartThings
 *
 *  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.
 *
 *  Device Handler for a Secure SSR303 (aka Horstmann ASR-ZW) Boiler switch
 *
 *  Standard ST Z-Wave switch modified to turn this device on and off. This device identified as a Thermostat (as opposed to a switch).
 *
 *  http://www.vesternet.com/z-wave-horstmann-z-wave-controlled-boiler-receiver-hrt
 *
 *  Note that the Horstmann HRT4-ZW and the ASR-ZW are being sold today as a 'Secure SRT321' (Thermostat) and 'Secure SSR303' (relay switch). 
 *  This DH allows the relay switch to be controlled directly by ST. To clarify, this DH is for the ASR-ZW or the SSR303. 
 * 
 */
metadata {
	definition (name: "Horstmann HRT4-ZW", namespace: "smartthings", author: "SmartThings") {
		capability "Actuator"
		capability "Indicator"
 		capability "Switch"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"

//		fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
//		fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
//		fingerprint mfr:"0063", prod:"5052", deviceJoinName: "Z-Wave Plug-In Switch"
//		fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
//      fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
      fingerprint mfr:"0059", prod:"0003", deviceJoinName: "Secure SSR303 Boiler Switch"

	}

	// simulator metadata
	simulator {
		status "on":  "command: 2003, payload: FF"
		status "off": "command: 2003, payload: 00"

		// reply messages
		reply "2001FF,delay 100,2502": "command: 2503, payload: FF"
		reply "200100,delay 100,2502": "command: 2503, payload: 00"
	}

	preferences {
		input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
	}

	// tile definitions
	tiles(scale: 2) {
		multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
			tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
				attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
				attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
			}
		}

		standardTile("indicator", "device.indicatorStatus", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
			state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off"
			state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on"
			state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit"
		}
		standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
			state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
		}

		main "switch"
		details(["switch","refresh"])
	}
}

def updated(){
  switch (ledIndicator) {
        case "on":
            indicatorWhenOn()
            break
        case "off":
            indicatorWhenOff()
            break
        case "never":
            indicatorNever()
            break
        default:
            indicatorWhenOn()
            break
    }
}

def parse(String description) {
	log.debug("TEST")
	log.debug(description)
	def result = null
	def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
	if (cmd) {
		result = createEvent(zwaveEvent(cmd))
	}
	if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
		result = [result, response(zwave.basicV1.basicGet())]
		log.debug "Was hailed: requesting state update"
	} else {
		log.debug "Parse returned ${result?.descriptionText}"
	}
	return result
}

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
	[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
	[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}

def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
	[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
}

def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
	def value = "when off"
	if (cmd.configurationValue[0] == 1) {value = "when on"}
	if (cmd.configurationValue[0] == 2) {value = "never"}
	[name: "indicatorStatus", value: value, display: false]
}

def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
	[name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false]
}

def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
	log.debug "manufacturerId:   ${cmd.manufacturerId}"
	log.debug "manufacturerName: ${cmd.manufacturerName}"
	log.debug "productId:        ${cmd.productId}"
	log.debug "productTypeId:    ${cmd.productTypeId}"
	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	updateDataValue("MSR", msr)
	updateDataValue("manufacturer", cmd.manufacturerName)
	createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}


def zwaveEvent(physicalgraph.zwave.Command cmd) {
	// Handles all Z-Wave commands we aren't interested in
	[:]
}

def on() {
	delayBetween([
		zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
		zwave.thermostatModeV2.thermostatModeGet().format()
	], standardDelay)
}


/*

def off() {
	delayBetween([
		zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
		zwave.thermostatModeV2.thermostatModeGet().format()
	], standardDelay)
    
    
    
    
def heat() {
	delayBetween([
		zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
		zwave.thermostatModeV2.thermostatModeGet().format()
	], standardDelay)


old code:
		zwave.basicV1.basicSet(value: 0xFF).format(),
		zwave.switchBinaryV1.switchBinaryGet().format()
	])
    
*/


def off() {
	delayBetween([
		zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
		zwave.thermostatModeV2.thermostatModeGet().format()
	], standardDelay)
}

def poll() {
	delayBetween([
		zwave.switchBinaryV1.switchBinaryGet().format(),
		zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
	])
}

def refresh() {
	delayBetween([
		zwave.switchBinaryV1.switchBinaryGet().format(),
		zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
	])
}

void indicatorWhenOn() {
	sendEvent(name: "indicatorStatus", value: "when on", display: false)
	sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
}

void indicatorWhenOff() {
	sendEvent(name: "indicatorStatus", value: "when off", display: false)
	sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
}

void indicatorNever() {
	sendEvent(name: "indicatorStatus", value: "never", display: false)
	sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
}

def invertSwitch(invert=true) {
	if (invert) {
		zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
	}
	else {
		zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
	}
}

I converted and I've been using this for a couple of years now..........

/**
 *
 *  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.
 *
 *  Device Handler for a Secure SSR303 (aka Horstmann ASR-ZW) Boiler switch
 *
 */
metadata {
	definition (name: "Secure SSR303", namespace: "Hubitat", author: "Scruffy-SJB") {
		capability "Actuator"
		capability "Indicator"
 		capability "Switch"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"

      fingerprint mfr:"0059", prod:"0003", deviceJoinName: "Secure SSR303 Boiler Switch"
	}

	// simulator metadata
	simulator {
		status "on":  "command: 2003, payload: FF"
		status "off": "command: 2003, payload: 00"

		// reply messages
		reply "2001FF,delay 100,2502": "command: 2503, payload: FF"
		reply "200100,delay 100,2502": "command: 2503, payload: 00"
	}

	preferences {
		input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
	}
}

def updated(){
  switch (ledIndicator) {
        case "on":
            indicatorWhenOn()
            break
        case "off":
            indicatorWhenOff()
            break
        case "never":
            indicatorNever()
            break
        default:
            indicatorWhenOn()
            break
    }
}

def parse(String description) {
	def result = null 
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
if (cmd) {
   	// log.debug "Command:  $cmd"
	result = createEvent(zwaveEvent(cmd))
	}
if (result?.name == 'hail') {
	result = [result, response(zwave.basicV1.basicGet())]
	// log.debug "Was hailed: requesting state update"
	} 
    return result
}

def zwaveEvent(hubitat.zwave.commands.thermostatmodev1.ThermostatModeReport cmd) {
/*    log.debug "ThermostatModeReport Command Value:   ${cmd.mode}"  */
    switch (cmd.mode) {
        case 0:
        	[name: "switch", value: "off" , type: "digital"]
        break
        case 1:
        	[name: "switch", value: "on" , type: "digital"]
        break
    }
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) {
	//	log.debug "BasicReport Command Value:   ${cmd.value}"
	[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
	//	log.debug "BasicSet Command Value:   ${cmd.value}"
	[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}

def zwaveEvent(hubitat.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
	//	log.debug "SwitchBinaryReport Command Value:   ${cmd.value}"
	[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
}

def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
	def value = "when off"
    // log.debug "ConfigurationReport Command Value:   ${cmd.value}"
	if (cmd.configurationValue[0] == 1) {value = "when on"}
	if (cmd.configurationValue[0] == 2) {value = "never"}
	[name: "indicatorStatus", value: value, display: false]
}

def zwaveEvent(hubitat.zwave.commands.hailv1.Hail cmd) {
	[name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false]
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
	// log.debug "manufacturerId:   ${cmd.manufacturerId}"
	// log.debug "manufacturerName: ${cmd.manufacturerName}"
	// log.debug "productId:        ${cmd.productId}"
	// log.debug "productTypeId:    ${cmd.productTypeId}"
	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	updateDataValue("MSR", msr)
	updateDataValue("manufacturer", cmd.manufacturerName)
	createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}

def zwaveEvent(hubitat.zwave.Command cmd) {
    // log.debug "Command Ignored ${cmd}"
	// Handles all Z-Wave commands we aren't interested in
	[:]
}

def on() {
	delayBetween([
		zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
		zwave.thermostatModeV2.thermostatModeGet().format()
	], standardDelay)
}

def off() {
	delayBetween([
		zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
		zwave.thermostatModeV2.thermostatModeGet().format()
	], standardDelay)
}

def poll() {
	delayBetween([
		zwave.switchBinaryV1.switchBinaryGet().format(),
		zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
	])
}

def heat() {
	delayBetween([
		zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
		zwave.thermostatModeV2.thermostatModeGet().format()
	], standardDelay)
}

def refresh() {
	delayBetween([
		zwave.switchBinaryV1.switchBinaryGet().format(),
		zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
	])
}

void indicatorWhenOn() {
	sendEvent(name: "indicatorStatus", value: "when on", display: false)
	sendHubCommand(new hubitat.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
}

void indicatorWhenOff() {
	sendEvent(name: "indicatorStatus", value: "when off", display: false)
	sendHubCommand(new hubitat.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
}

void indicatorNever() {
	sendEvent(name: "indicatorStatus", value: "never", display: false)
	sendHubCommand(new hubitat.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
}

def invertSwitch(invert=true) {
	if (invert) {
		zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
	}
	else {
		zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
	}
}
1 Like

Thanks! Hopefully that's another thing that's going to be good to go!

1 Like

Hey @simon

Finally got around to removing this from ST and adding to my C7.

All in all took about 5 mins, and the response time under hubitat is amazing. ST was laggy as hell after one of their first crappy firmware updates.

Just wanted to say thanks again. Very, very happy.

1 Like

Excellent

1 Like

@simon

I'm using the heating system a lot more now that it's freezing!

Im finding that the relay switch is, quite regularly, losing connection to the system.

Pressing poll/refresh has no effect. The device is reported as being ON, and if I use hubitat to turn it off then back on again, it quite happily loses the red light and clicks the boiler into action, working fine for maybe another 24 hrs. I do seem to notice this in the morning, but that may be coincidental.

Any thoughts please? My gut tells me it's C7 related. I use wifi/zigbee for the majority of my devices and have read horror stories about the zwave issues on the C7.

These things have a fail-safe mechanism. If they don't hear from the system in x minutes they think the system has failed and it's time to shut down to avoid the heating system running forever.

in the app I use to turn the switch on/off I have a 6 minute wake-up and the app resends the last on or off as appropriate...... to be sure to be sure...... This way the fail-safe never kicks in.

Would this be your issue?

1 Like

Ah... Quite possibly. That might explain why it's happening at approx the same in the morning, when the heating has fired up and been left on for a while to initially heat the house for when we're waking up.

Any idea what the failsafe time is?

The manual says if no communication with the controller after 60 minutes it will turn off.

1 Like

Magic, thanks simon. Couldn't find a mention of that in one of the manuals I downloaded. I can't believe that this is (hopefully) the source of my niggles. I've been troubleshooting a little for quite a while now, on and off, and presumed it was a timing issue with my boiler switch webcore Piston (something trying to switch it on occasionally whilst something else was trying to switch off and getting stuffed in the process) or more likely, a comms issue. Which didn't make sense with the regularity of the error.

As I said, colder now, so this is the first time I've really made use of schedules pre wakeup, so thought it may have always been a limitation/error within my system and I hadn't used it extensively enough to trigger it.

Adding a while loop to webcore to see if this vanishes. Either way I probably owe you a curly wurly for that :grin:

1 Like