Sage by Hughes

I modified the door bell driver for the switch. This is basically a 2 buttons device now.

/**
 *  SAGE Switch
 *
 *  For device information and images, questions or to provide feedback on this device handler, 
 *  please visit: 
 *
 *      darwinsden.com/sage-doorbell
 *
 *  White wire: common
 *  Green wire: doorbell 1 / front
 *  Yellow wire: doorbell 2 / back
 *
 * Factory reset:
 * Remove the plastic cover and the battery.
 * Press and hold the tiny RESET button (next to where the wires attach to the circuit board) while you reinstall the battery.
 * Continue holding RESET until the red LED blinks.
 *
 *  Copyright 2016 DarwinsDen.com
 *
 *  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.
 *
 *	Author: Darwin@DarwinsDen.com
 *	Date: 2016-06-13
 *
 *	Changelog:
 *
 *  0.40 (03/23/2017) - set numberOfButtons attribute for those smart apps that rely on this
 *  0.30 (11/20/2016) - Removed non-operational battery capability; was preventing device display on 2.2.2 mobile app
 *  0.20 (08/02/2016) - Added preference option for allowed time between presses to eliminate duplicate notifications on some systems
 *  0.10 (06/13/2016) - Initial 0.1 pre-beta Test Code
 *
 */
 
metadata {
	definition (name: "SAGE Switch", namespace: "darwinsden", author: "darwin@darwinsden.com") {
    	//capability "Battery"
		capability "Configuration"
        capability "Pushable Button"
		capability "Refresh"
     
        command "enrollResponse"
 	   
        fingerprint endpointId: "12", inClusters: "0000,0003,0009,0001", outClusters: "0003,0006,0008,0019", model: "Bell", manufacturer: "Echostar"
    }
      
    preferences {
        input "timeBetweenPresses", "number", title: "Seconds allowed between presses (increase this value to eliminate duplicate notifications)",  defaultValue: 10,  displayDuringSetup: true, required: false	
    } 
}
 
def parse(String description) {
	//log.debug "description: $description"
    
	Map map = [:]
	if (description?.startsWith('catchall:')) {
		map = parseCatchAllMessage(description)
	}
    else if (description?.startsWith('read attr -')) {
		map = parseReportAttributeMessage(description)
	}
    
	log.debug "Parse returned $map"
	def result = map ? createEvent(map) : null
    
    if (description?.startsWith('enroll request')) {
    	List cmds = enrollResponse()
        ////log.debug "enroll response: ${cmds}"
        result = cmds?.collect { new hubitat.device.HubAction(it) }
    }
    return result
}
 
private Map parseCatchAllMessage(String description) {
    Map resultMap = [:]
    def cluster = zigbee.parse(description)
    if (shouldProcessMessage(cluster)) { 
        //log.debug ("cluster: $cluster")
        switch(cluster.clusterId) {
            case 0x0006:
            	resultMap = getDoorbellPressResult(cluster)
                break
        }
    }
    return resultMap
}

private boolean shouldProcessMessage(cluster) {
    // 0x0B is default response indicating message got through
    // 0x07 is bind message
    boolean ignoredMessage = cluster.profileId != 0x0104 || 
        cluster.command == 0x0B ||
        cluster.command == 0x07 ||
        (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
    return !ignoredMessage
}

private Map parseReportAttributeMessage(String description) {
	Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
		map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
	}
	//log.debug "Desc Map: $descMap"
 
	Map resultMap = [:]
	if (descMap.cluster == "0001" && descMap.attrId == "0020") {
        log.debug descMap.value
		resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
	}
 
	return resultMap
}

private Map getBatteryResult(rawValue) {
	log.debug "Battery rawValue = ${rawValue}"
	def linkText = getLinkText(device)

	def result = [
		name: 'battery',
		value: '--',
		translatable: true
	]

	def volts = rawValue / 10

	if (rawValue == 0 || rawValue == 255) {}
	else {
		if (volts > 3.5) {
			result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
		}
		else {
			if (device.getDataValue("manufacturer") == "SmartThings") {
				volts = rawValue // For the batteryMap to work the key needs to be an int
				def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
								  22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
				def minVolts = 15
				def maxVolts = 28

				if (volts < minVolts)
					volts = minVolts
				else if (volts > maxVolts)
					volts = maxVolts
				def pct = batteryMap[volts]
				if (pct != null) {
					result.value = pct
					result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
				}
			}
			else {
				def minVolts = 2.1
				def maxVolts = 3.0
				def pct = (volts - minVolts) / (maxVolts - minVolts)
				result.value = Math.min(100, (int) pct * 100)
				result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
			}
		}
	}

	return result
}

private Map getDoorbellPressResult(cluster) {
    def linkText = getLinkText(device)
    def buttonNumber = (cluster.command as int)
    def result = [:]
    
    // map buttons per Hughes described defaults for green and yellow wires
    
    switch(buttonNumber) {
        case 0: 
            if (!isDuplicateCall(state.lastButton2Updated, state.timeBetweenPresses) )
            {
		       log.debug ("OFF Pushed!")       
               result = [ name: 'pushed', value: "2", isStateChange: true ]
            }
            state.lastButton2Updated = new Date().time	
            break

        case 1: 
            if (!isDuplicateCall(state.lastButton1Updated, state.timeBetweenPresses) )
            {		
		       log.debug ("ON Pushed!")
               result = [ name: 'pushed', value: "1", isStateChange: true ]
            }
            state.lastButton1Updated = new Date().time
    }
    return result
}

def refresh() {
	log.debug "Refreshing Battery"
    
    def refreshCmds = [
        "he rattr 0x${device.deviceNetworkId} 18 0x0001 0x20", "delay 500", 
	]
    
    setPrefs()
    
	return refreshCmds + enrollResponse()
}

def configure() {

    setPrefs()
	String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
	log.debug "Configuring Reporting, IAS CIE, and Bindings."
	def configCmds = [
		"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 1", "delay 500",

		"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
		"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}",		//checkin time 6 hrs
		"send 0x${device.deviceNetworkId} 1 1", "delay 500",

		"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
		"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
		"send 0x${device.deviceNetworkId} 1 1", "delay 500"
	]
    return configCmds + refresh() // send refresh cmds as part of config
}

def enrollResponse() {
	log.debug "Sending enroll response"
    
	String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
	[
		//Resending the CIE in case the enroll request is sent before CIE is written
		"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
		//Enroll Response
		"raw 0x500 {01 23 00 00 00}",
		"send 0x${device.deviceNetworkId} 1 1", "delay 200"
	]
}

private isDuplicateCall(lastRun, allowedEverySeconds) {
	def result = false
	if (lastRun) {
		result =((new Date().time) - lastRun) < (allowedEverySeconds * 1000)
	}
	result
}

def setPrefs() 
{
   log.debug ("setting preferences")
   if (timeBetweenPresses == null)
   {
      state.timeBetweenPresses = 10
   }
      else if (timeBetweenPresses < 0)
   {
      state.timeBetweenPresses = 0
   }
   else
   {
      state.timeBetweenPresses = timeBetweenPresses
   }  
}

def updated()
{
   setPrefs()
}

private getEndpointId() {
	new BigInteger(device.endpointId, 16).toString()
}

private hex(value) {
	new BigInteger(Math.round(value).toString()).toString(16)
}

private String swapEndianHex(String hex) {
    reverseArray(hex.decodeHex()).encodeHex()
}

private byte[] reverseArray(byte[] array) {
    int i = 0;
    int j = array.length - 1;
    byte tmp;
    while (j > i) {
        tmp = array[j];
        array[j] = array[i];
        array[i] = tmp;
        j--;
        i++;
    }
    return array
}

Does anyone know if the bulbs will pair with the Hue hub, by chance?

Yes, they pair as a generic zigbee bulb. They are terrible Zigbee repeaters, I've added about 7 and I'm now moving them all to my SmartThings hub to see if they will play nice if all over there via HubConnect.

The A19 bulbs actually pair as device. You have to switch them to Generic Zigbee Bulb. I will tag Mike to see if he can add the Fingerprint for the A19 bulb. I just received the A19 Bulbs tonight, they paired easily to my bulb hub. I keep Bulbs on a separate hub to keep them from mucking up my mesh.

@mike.maxwell Can you add the Sage A19 Bulb Fingerprint. It works with the Generic Zigbee Bulb Driver.

1 Like

Hello, I know this is an old post, but the best I could find with my google search lol, I have about 8 boxes of the bulbs. I am very new to the smart stuff, other than having alexa play music. Is there a system these can be used on, or do I donate? I stupidly thought they could just be used as bulbs, not smart, but dont care for the flashing lol

They work with the home automation hub that this community uses - the Hubitat Elevation. While modestly priced, the Hubitat Elevation is both versatile and pretty powerful. It also runs all automations with devices like your lightbulbs locally, and is therefore fast and reliable.

2 Likes

Do yourself a favor and just pitch them. I also had boxes of them, got them very cheap at an auction. Anyway, The problem with them is that they are Zigbee repeaters. This sounds good, but they are extremely bad at repeating. With Hubitat, and probably any other system, they will make your zigbee network a disaster. I tried and tried. When they all were unplugged, the problems all went away.
Hope that helps, Good luck.

2 Likes

As @rick.guthier mentioned these are terrible. I had them on a bulb hub. Randomly one would lock up and then another until half the lights were out in the chandelier. Flipping the switch to power them off and then powering back on makes the locked up lights behave again for some time, but is an annoyance. I have a tall glass display case that is in front of the physical light switch so it is a chore to get them working again. I also bought them from an auction, but not worth keeping them.

1 Like