[PORT] AXIS Gear Blind/Shade Driver

After waiting since November 12, 2015...my AXIS Gear has finally arrived!

It been so long that when I ordered this thing I was using HomeSeer, then switched to SmartThings and now onto Hubitat. It is compatible with ST right out of the box with the custom driver provided by AXIS. Was hoping one of our great code slingers over here could take a look at the code and see if it could work with HE.

The Axis device pairs right up with HE:
* endpointId: 01
* application: 8C
* model: Gear
* manufacturer: AXIS

The driver code loads right in too, without any modifications or errors.

I can click 'configure' and see it in the logs. But I can't control it at all. I don't get any errors in the log but the shade doesn't go up or down either.

ST integration PDF is here:
https://support.helloaxis.com/hc/en-us/articles/360000044207-Integrating-with-SmartThings

Github Code:

Hoping someone can see something easy and get this thing working! :smile:

Looks like a pretty cool device I'd love to see a video, and perhaps a little bit of a review after you get it working =D

Was looking at these last week but was concerned about the wait time good to know they are finally shipping but 2.5 years is a bit much and saw some posts concerning battery life (which may have been addressed).
Let us know how they work out.

Not looking good at this end. I know next to nothing about programming but I was able to get the shade to go up and down a couple of times. Not consistently, almost randomly. Hit the button and nothing, hit the button again and it moves, hit it again and nothing. I can't explain it. One thing that I did notice is that if I try to use the Axis, it hangs the rest of the system. Takes forever to load the logs or change to any screen within HE. Have to reboot the hub to fix. Very repeatable, every time I use the driver it kills the hub. So definitely something with the driver is really messing with the Hub.

For kicks I removed the Axis from HE and added it into ST. BAM, this thing works awesome! Nice and smooth motion. All the way up or down, or move it is 25% increments. It just works. Going to setup a webcore piston to automatically adjust it based on time of sunset. Hopefully be able to talk about battery life at some point. According to the support forum, it was takin care of with the last update. Right now with all my testing it's still showing 100%. Looks like the solar panel is doing its job.

So unless a programming guru takes a look at the driver (it's only 197 lines)...this thing will live on ST. :slight_smile:

Hmmm, nothing pops out in their driver code that would cause any issues on the hub. I might try using our zigbee dimmer driver with this, may work.

1 Like

Just installed a big firmware update to my Axis. What a difference! It now works great with Hubitat. Nice and smooth and only takes 1 click on the Shade Tile in the Dashboard to move it to any position. Very happy with this update. :smiley:

Are you using V3 or V2 of the code? What attirbute did use in the tile to move it? I only get up and down.

1 more question. Does yours act like a light? For some reason when I ask Alexa to turn on all the lights the shades go up.

Thanks for any help.

I'm using the V3 code. No attribute in tile, I use the 'shades' template tile. This gives you a slider to set the shade to any level.

I don't use Alexa so can't help you with that.

It took me a bit to figure out what everyone was talking about!

I have two dozen or so AXIS products, IP cameras and door controllers mostly. I wonder how long until AXIS (the startup developing window blind controllers) will continue to be able to call themselves AXIS? The company I think of as AXIS invented the network camera! (www.axis.com). They have about 3000 employees worldwide.

considering these Axis blind controllers
what sort of battery life have you gotten when using with the solar panels?
how have you found them overall?
would you recommend them?

Solar panel is crap. They now offer a wall plug, not sure if the new ones ship with the wall plug or not. But definitely need it if you want to open/close more than once or twice a day.

I only have 1 unit but I've had good luck with it (other than the solar panel). If they weren't so expensive I would get another.

thanks
maybe I will wait and until the Ikea blinds come out and decide

I have them working ok also. My solar panels are working good. But the front of my house has sun all day long. So that helps. Luckily I got mine during the initial kickstarter.

I purchased the Axis gear and downloaded the V3 driver. It works very well when it is connected. Seems to stay connected for about a day and then it just loses it's connection until I turn the Axis gear off and then back on (this makes it recheck against the hub I think) then it re-connects. Any ideas???

Mine drops out every now and then but not too often since the last update a while back.

Make sure that the device is fully updated using the Axis app. Then it's up to your mesh and the device to get along. Definitely can be temperamental.

Both my Axis Gears are one firmware behind at the moment so the firmware from January or February and I have not had either stop responding. in order to update the firmware you have to take it back out of home automation mode so its connected to the app via bluetooth.

I will check, but I think I may have already updated.

Ordered a zigbee repeater plug and now the Axis Gear has a stable connection.

Now I have another issue though. I am trying to set a rule to open blinds at a specific time, I can control the blinds just fine from the "Devices" area I have tried both open() and on() for the rule and am getting the same error in the logs the error is "java.lang.NullPointerException: Cannot invoke method replace() on null object (appButtonHandler)"

Same error occurs when trying to send the command through IFTTT or Alexa

I have tried removing the device completely and re-adding it, also tried to move to a different driver and then move back to the Axis Gear V3, but I am at a loss now, I am not sure why this error keeps occurring. Any ideas on what I should try next?

After alot of playing around with this. I fixed it by finally removing all rules and app associations to this shade and then I removed the shade. Rebooted the hub and then re-added this device. Put all associations back IE: IFTTT and Alexa, added my rules, and now this device is working as expected.

I just added my Axis to hubitat, the V5 code that they have on github is not compatible with Hubitat, you need to look through the commit history and find the V3 code. I'd post a link to it, but the forums say I'm not allowed.

Here's the RAW of the driver:

metadata {
    definition (name: "AXIS Gear TEST", namespace: "axis", author: "AXIS Labs") {  
        capability "Actuator"
        capability "Configuration"
        capability "Switch"
        capability "Switch Level"
        capability "Refresh"        
        capability "Battery"
        capability "HealthCheck"
        capability "Window Shade"
		
        //Custom Commandes to achieve 25% increment control
        command "ShadesUp"
        command "ShadesDown"
        
                
        fingerprint profileId: "0200", inClusters: "0000, 0001, 0004, 0005, 0006, 0008, 0100, 0102", manufacturer: "AXIS", model: "GR-ZB01-W", deviceJoinName: "AXIS Gear"
        //ClusterIDs: 0000 - Basic; 0004 - Groups; 0005 - Scenes; 0006 - On/Off; 0008 - Level Control; 0100 - Shade Configuration; 0102 - Window Covering;
        //Updated 2017-06-21
        //Updated 2017-08-24 - added power cluster 0001 - added battery, level, reporting, & health check
        //Updated 2018-01-04 - Axis Inversion & Increased Battery Reporting interval to 1 hour (previously 5 mins)
        //Updated 2018-01-08 - Updated battery conversion from [0-100 : 00 - 64] to [0-100 : 00-C8] to reflect firmware update
        //Updated 2018-10-02 - added in configure reporting for refresh button, close when press on partial shade icon, update handler to parse between 0-254 as a percentage
    }
   
	tiles(scale: 2) {
        multiAttributeTile(name:"shade", type: "lighting", width: 3, height: 3) {
            tileAttribute("device.windowShade", key: "PRIMARY_CONTROL") {
                attributeState("open",  action:"close", icon:"http://i.imgur.com/4TbsR54.png", backgroundColor:"#ffcc33", nextState: "closed")
                attributeState("partial", action:"close", icon:"http://i.imgur.com/vBA17WL.png", backgroundColor:"#ffcc33", nextState: "closed")
                attributeState("closed", action:"open",  icon:"http://i.imgur.com/mtHdMse.png", backgroundColor:"#bbbbdd", nextState: "open")
             
             }
                tileAttribute ("device.level", key: "VALUE_CONTROL") {
              		attributeState("VALUE_UP", action: "ShadesUp")
        			attributeState("VALUE_DOWN", action: "ShadesDown")
             }
   		}
        //Added a "doubled" state to toggle states between positions
        standardTile("main", "device.windowShade"){
        	state("open", label:'Open', action:"close", icon:"http://i.imgur.com/St7oRQl.png", backgroundColor:"#ffcc33", nextState: "closed")
            state("partial", label:'Partial', action:"close",  icon:"http://i.imgur.com/y0ZpmZp.png", backgroundColor:"#ffcc33", nextState: "closed")
            state("closed", label:'Closed',action:"open", icon:"http://i.imgur.com/SAiEADI.png", backgroundColor:"#bbbbdd", nextState: "open")
        }
	 	controlTile("mediumSlider", "device.level", "slider",decoration:"flat",height:1, width: 2, inactiveLabel: true) {
            state("level", action:"setLevel")
        }
        
        valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:1) {
			state "battery", label:'${currentValue}% battery', unit:""
		}
        
		standardTile("refresh", "device.refresh", inactiveLabel:false, decoration:"flat", width:2, height:1) {
			state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
		}
        
        main(["main"])
        details(["shade", "mediumSlider","battery", "refresh"])
        //details('shade', 'levelSliderControl')
        //details('levelSliderControl', 'otherTile', 'anotherTile') //adjustment and order of tiles
	}

}
//Declare Clusters
private getCLUSTER_POWER() {0x0001}
private getCLUSTER_LEVEL() {0x0008}
private getLEVEL_ATTR_LEVEL() {0x0000}
private getPOWER_ATTR_BATTERY() {0x0021}

//Custom command to increment blind position by 25 %
def ShadesUp(){
	def shadeValue = device.latestValue("level") as Integer ?: 0 
    
    if (shadeValue < 100){
      	shadeValue = Math.min(25 * (Math.round(shadeValue / 25) + 1), 100) as Integer
    }else { 
    	shadeValue = 100
	}
    //sendEvent(name:"level", value:shadeValue, displayed:true)
    setLevel(shadeValue)
}

//Custom command to decrement blind position by 25 %
def ShadesDown(){
	def shadeValue = device.latestValue("level") as Integer ?: 0 
    
    if (shadeValue > 0){
      	shadeValue = Math.max(25 * (Math.round(shadeValue / 25) - 1), 0) as Integer
    }else { 
    	shadeValue = 0
	}
    //sendEvent(name:"level", value:shadeValue, displayed:true)
    setLevel(shadeValue)
	   
}

//Send Command through setLevel()
def on() {
	//sendEvent(name:"level", value:0, displayed:true)
    setLevel(100)  
}

//Send Command through setLevel()
def off() {
	setLevel(0)
}
//Command to set the blind position (%) and log the event
def setLevel(value) {
	sendEvent(name:"level", value: value, displayed:true)
    def L = Math.round(value);
    def i = Integer.valueOf(L.intValue());
    setWindowShade(i)
	zigbee.setLevel(i)
    
}
//Send Command through setLevel()
def open() {
    setLevel(100)   
}
//Send Command through setLevel()
def close() {
	setLevel(0)
}

//Reporting of Battery & position levels
def ping(){
	log.debug "Ping() "
    return zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY) +
    	   zigbee.readAttribute(CLUSTER_LEVEL, LEVEL_ATTR_LEVEL)
    
}

//Set blind State based on position (which shows appropriate image) 
def setWindowShade(value){
 if ((value>0)&&(value<99)){
    	sendEvent(name:"windowShade", value: "partial", displayed:true)
    } else if (value >= 99){
    	sendEvent(name:"windowShade", value: "open", displayed:true)
    }else{
    	sendEvent(name:"windowShade", value: "closed", displayed:true)
    }
}

//Refresh command
def refresh() {
	log.debug "parse() refresh"
    return zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY, 0x20, 1, 3600, 0x01) +
           zigbee.configureReporting(CLUSTER_LEVEL, LEVEL_ATTR_LEVEL, 0x20, 1, 3600, 0x01) + 
           zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY) +
    	   zigbee.readAttribute(CLUSTER_LEVEL, LEVEL_ATTR_LEVEL)
}
//configure reporting
def configure() {
    log.debug "Configuring Reporting and Bindings."
    sendEvent(name: "checkInterval", value: 60, displayed: true, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
    def cmds = 
    	zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY, 0x20, 1, 3600, 0x01) +
        zigbee.configureReporting(CLUSTER_LEVEL, LEVEL_ATTR_LEVEL, 0x20, 1, 3600, 0x01)
        log.info "configure() --- cmds: $cmds"
    return refresh + cmds
}

def parse(String description) {
    log.trace "parse() --- description: $description"


    Map map = [:]
    if (description?.startsWith('read attr -')) {
        map = parseReportAttributeMessage(description)
    } else if (description?.startsWith('attr report -')) {
        map = parseReportAttributeMessage(description)
    }

    def result = map ? createEvent(map) : null
    log.debug "parse() --- returned: $result"
    return result
}

private Map parseReportAttributeMessage(String description) {
    Map descMap = zigbee.parseDescriptionAsMap(description)
    Map resultMap = [:]
    if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY) {
        resultMap.name = "battery"
        def batteryValue = Math.round((Integer.parseInt(descMap.value, 16))/2)
        log.debug "parseDescriptionAsMap() --- Battery: $batteryValue"
        if ((batteryValue >= 0)&&(batteryValue <= 100)){
        	resultMap.value = batteryValue
        }
        
    }
    else if (descMap.clusterInt == CLUSTER_LEVEL && descMap.attrInt == LEVEL_ATTR_LEVEL) {
        resultMap.name = "level"
        def levelValue = Math.round(Integer.parseInt(descMap.value, 16))
        def levelValuePercent = Math.round((levelValue/255)*100)
        //Set icon based on device feedback for the  open, closed, & partial configuration
        setWindowShade(levelValuePercent)
        resultMap.value = levelValuePercent
    }
    else {
        log.debug "parseReportAttributeMessage() --- ignoring attribute"
    }
    return resultMap
}