Help Using Webcore Pistons & Hue Motion IDE


(Jack) #1

Hi,

I’m new to SmartThings and still very much in the learning stages. I have installed webCoRE as it seems to be the way forward with what i want to achieve but clearly I am doing a few things wrong and kindly ask that people have a look through my pistons and tell me what i am doing wrong.

A bit of background information, I am using Philips Hue bulbs and a couple of Hue Motion sensors with the custom IDE that i got from the blog post on here. It seems to be working well:
/**

  • 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.

*/

metadata {
definition (name: “Hue Motion Sensor”, namespace: “digitalgecko”, author: “digitalgecko”) {

	capability "Motion Sensor"
	capability "Configuration"
	capability "Battery"
	capability "Refresh"
    capability "Temperature Measurement"
    capability "Sensor"
    capability "Illuminance Measurement" //0x0400

    fingerprint profileId: "0104", inClusters: "0000,0001,0003,0406,0400,0402", outClusters: "0019", manufacturer: "Philips", model: "SML001", deviceJoinName: "Hue Motion Sensor"
}

preferences {
		section {
		input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
		input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
	}
}

tiles(scale: 2) {
	multiAttributeTile(name:"motion", type: "generic", width: 6, height: 4){
		tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
			attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
			attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
		}
	}
   
    
	valueTile("temperature", "device.temperature", width: 2, height: 2) {
		state("temperature", label:'${currentValue}°',
			backgroundColors:[
				[value: 31, color: "#153591"],
				[value: 44, color: "#1e9cbb"],
				[value: 59, color: "#90d2a7"],
				[value: 74, color: "#44b621"],
				[value: 84, color: "#f1d801"],
				[value: 95, color: "#d04e00"],
				[value: 96, color: "#bc2323"]
			]
		)
	}
    
    valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
		state "battery", label:'${currentValue}% battery', unit:""
	}
	valueTile("illuminance", "device.illuminance", width: 2, height: 2) {
		state("illuminance", label:'${currentValue}', unit:"lux",
			backgroundColors:[
				[value: 9, color: "#767676"],
				[value: 315, color: "#ffa81e"],
				[value: 1000, color: "#fbd41b"]
			]
		)
	}
    standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
        state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
    }
    standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
        state "default", label:"configure", action:"configure"
    }
    main "motion"
    details(["motion","temperature","battery", "refresh","illuminance",'configure'])
}

}

// Parse incoming device messages to generate events
def parse(String description) {
def msg = zigbee.parse(description)

//log.warn "--"
//log.trace description
//log.debug msg
//def x = zigbee.parseDescriptionAsMap( description )
//log.error x

Map map = [:]
if (description?.startsWith('catchall:')) {
	map = parseCatchAllMessage(description)
}
else if (description?.startsWith('temperature: ')) {
	map = parseCustomMessage(description)
}
else if (description?.startsWith('illuminance: ')) {
	map = parseCustomMessage(description)
}

// else if (description?.startsWith(‘zone status’)) {
// //map = parseIasMessage(description)
// log.trace “zone status”
// }

def result = map ? createEvent(map) : null

if (description?.startsWith('enroll request')) {
	List cmds = enrollResponse()
	result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
else if (description?.startsWith('read attr -')) {
	result = parseReportAttributeMessage(description).each { createEvent(it) }
}
return result

}

/*
Refresh Function
*/
def refresh() {
log.debug “Refreshing Values”

def refreshCmds = []
refreshCmds +=zigbee.readAttribute(0x0001, 0x0020) // Read battery?
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) // Read temp?
refreshCmds += zigbee.readAttribute(0x0400, 0x0000) // Read luminance?
refreshCmds += zigbee.readAttribute(0x0406, 0x0000) // Read motion?

return refreshCmds + enrollResponse()

}

/*
Configure Function
*/
def configure() {

// TODO : device watch?

String zigbeeId = swapEndianHex(device.hub.zigbeeId)
log.debug "Confuguring Reporting and Bindings."


def configCmds = []
configCmds += zigbee.batteryConfig()
configCmds += zigbee.temperatureConfig(30, 600) // Set temp reporting times // Confirmed

configCmds += zigbee.configureReporting(0x406,0x0000, 0x18, 30, 600, null) // motion // confirmed


// Data type is not 0x20 = 0x8D invalid data type Unsigned 8-bit integer

configCmds += zigbee.configureReporting(0x400,0x0000, 0x21, 60, 600, 0x20) // Set luminance reporting times?? maybe    
return refresh() + configCmds 

}

/*
getMotionResult
*/

private Map getMotionResult(value) {
//log.trace "Motion : " + value

def descriptionText = value == "01" ? '{{ device.displayName }} detected motion':
		'{{ device.displayName }} stopped detecting motion'

return [
	name: 'motion',
	value: value == "01" ? "active" : "inactive",
	descriptionText: descriptionText,
	translatable: true,
]

}

/*
getTemperatureResult
*/
private Map getTemperatureResult(value) {

//log.trace "Temperature : " + value
if (tempOffset) {
	def offset = tempOffset as int
	def v = value as int
	value = v + offset
}
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
		'{{ device.displayName }} was {{ value }}°F'

return [
	name: 'temperature',
	value: value,
	descriptionText: descriptionText,
	translatable: true,
	unit: temperatureScale
]

}

def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == “C”){
return Math.round(celsius)
} else {
return Math.round(celsiusToFahrenheit(celsius))
}
}

private Map getLuminanceResult(rawValue) {
log.debug “Luminance rawValue = ${rawValue}”

def result = [
	name: 'illuminance',
	value: '--',
	translatable: true,
	unit: 'lux'
]

result.value = rawValue as Integer
return result

}

/*
getBatteryResult
*/
//TODO: needs calibration
private Map getBatteryResult(rawValue) {
//log.debug “Battery rawValue = ${rawValue}”

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)
			def roundedPct = Math.round(pct * 100)
			if (roundedPct <= 0)
				roundedPct = 1
			result.value = Math.min(100, roundedPct)
			result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
		}
	}
}

return result

}
/*
parseCustomMessage
*/
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}

if (description?.startsWith('illuminance: ')) {
log.warn "value: " + description.split(": ")[1]
        log.warn "proc: " + value

	def value = zigbee.lux( description.split(": ")[1] as Integer ) //zigbee.parseHAIlluminanceValue(description, "illuminance: ", getTemperatureScale())
	resultMap = getLuminanceResult(value)
}
return resultMap

}

/*
parseReportAttributeMessage
*/
private List parseReportAttributeMessage(String description) {
Map descMap = (description - “read attr - “).split(”,”).inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}

List result = []

// Temperature
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
	def value = getTemperature(descMap.value)
	result << getTemperatureResult(value)
}

// Motion
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
	result << getMotionResult(descMap.value)
}

// Battery
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
	result << getBatteryResult(Integer.parseInt(descMap.value, 16))
}

// Luminance
else if (descMap.cluster == "0402" ) { //&& descMap.attrId == "0020") {
	log.error "Luminance Response " + description
    //result << getBatteryResult(Integer.parseInt(descMap.value, 16))
}

return result

}

/*
parseCatchAllMessage
*/
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
// log.debug cluster
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break

		case 0x0400:
        	if (cluster.command == 0x07) { // Ignore Configure Reporting Response
            	if(cluster.data[0] == 0x00) {
					log.trace "Luminance Reporting Configured"
					sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
				}
				else {
					log.warn "Luminance REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
				}
			}
			else {
        		log.debug "catchall : luminance" + cluster
            	resultMap = getLuminanceResult(cluster.data.last());
            }

		break
        
		
        
		case 0x0402:
			if (cluster.command == 0x07) {
				if(cluster.data[0] == 0x00) {
					log.trace "Temperature Reporting Configured"
					sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
				}
				else {
					log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
				}
			}
			else {
				// temp is last 2 data values. reverse to swap endian
				String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
				def value = getTemperature(temp)
				resultMap = getTemperatureResult(value)
			}
		break
	}
}

return resultMap

}

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

// This seems to be IAS Specific and not needed we are not really a motion sensor
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}”, “delay 200”,
// “send 0x${device.deviceNetworkId} 1 1”, “delay 200”
// ]
}

def configureHealthCheck() {
Integer hcIntervalMinutes = 12
refresh()
sendEvent(name: “checkInterval”, value: hcIntervalMinutes * 60, displayed: false, data: [protocol: “zigbee”, hubHardwareId: device.hub.hardwareID])
}

def updated() {
log.debug “in updated()”
configureHealthCheck()
}

def ping() {
return zigbee.onOffRefresh()
}

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

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
}

Kitchen:
Aim: I want to configure my lights to turn on when motion is detected but only when the room is dark enough. I also want them to turn off when i leave the room.
Piston:


Problem: Whilst it is working roughly correctly, the lights turn off when i am still in the room (presumably because the motion sensor isn’t detecting motion) and sometimes they turn back on instantly, as they should, when i move again but sometimes they don’t turn back on at all which is rather annoying! What am i doing wrong?

Living Room:
Aim: The room clearly used to be 2 rooms and when creating one larger room, the electrics were not altered so there are 2 separate switches for the 2 ceiling lights. I want to configure the lights so both turn on when i turn on the switch next to the door (leaving the far switch permanently turned on).
Piston:


Problem: This piston is not working at all. WHat have i got wrong here?

Hallway:
Aim: For the lights to turn on only when dark enough and when either motion is detected or when i open the front door.
Piston:


Problem: It seems to work fairly well but sometimes the lights do not turn off at all. What am i doing wrong?

Thanks in advance for your help.


(jkp) #2

The best place to post your webCoRE questions are in the WC community forum :slight_smile:


(Jack) #3

Yes but for that i need my account verified and over a week later, i still haven’t received the verification email (yes, i have sent a chaser already) so for now i have to ask on here. Plus, i want to know whether i have done something wrong in the IDE too.


#4

If you’re using an outlook or any microsoft email address, there might be an issue with the verification emails. If you have an alternate, I’d suggest you use that for now.

Tagging @RobinWinbourne


(Robin) #5

Sent a PM to @Rugbyjack2005 a little while ago.

We are currently experiencing difficulties with Microsoft powered email accounts (hotmail.com, live.com, msn.com, outlook.com). Emails sent fromthe forum and webcore itself are being blocked.

I have placed a warning message on the registration page for new users but unfortunately there are still 10-15 users in limbo.


#6

Do you have a log from WebCore of when you think the bulb should be turning off? You do realize your else will only fire if the first part is totally false, correct? Typically, I don’t set up any of my off functions as "eles"s just because they are harder to predict. I would set up this piston by saying CHANGES TO ACTIVE in the first part and then a separate off function with CHANGES TO INACTIVE to turn them off. That seems to be more reliable and more straightforward.


(Jack) #7

Sorry Robin, i missed this. I just have sent you a PM with an alternative email address.


(Jack) #8

Hi @Ryan780 Here is the log taken a few minutes ago:

03/09/2018, 19:10:07 +975ms
+1ms ╔Received event [Hue Motion Sensor - Kitchen].motion = active with a delay of 167ms
+118ms ║RunTime Analysis CS > 15ms > PS > 39ms > PE > 64ms > CE
+121ms ║Runtime (38289 bytes) successfully initialized in 39ms (v0.3.107.20180806) (119ms)
+122ms ║╔Execution stage started
+130ms ║║Comparison (enum) active is (string) active = true (1ms)
+132ms ║║Cancelling condition #8’s schedules…
+132ms ║║Condition #8 evaluated true (6ms)
+133ms ║║Cancelling condition #1’s schedules…
+134ms ║║Condition group #1 evaluated true (state changed) (8ms)
+143ms ║║Comparison (integer) 22 is_less_than_or_equal_to (integer) 15 = false (1ms)
+145ms ║║Cancelling condition #10’s schedules…
+146ms ║║Condition #10 evaluated false (9ms)
+146ms ║║Cancelling condition #6’s schedules…
+147ms ║║Condition group #6 evaluated false (state changed) (11ms)
+149ms ║╚Execution stage complete. (28ms)
+150ms ╚Event processed successfully (150ms)
03/09/2018, 19:10:06 +67ms
+1ms ╔Received event [Home].time = 1535998207952 with a delay of -1885ms
+140ms ║RunTime Analysis CS > 22ms > PS > 39ms > PE > 78ms > CE
+142ms ║Runtime (38273 bytes) successfully initialized in 39ms (v0.3.107.20180806) (138ms)
+144ms ║╔Execution stage started
+180ms ║║Executed physical command [Kitchen - Back Hue color spot 1].off() (12ms)
+181ms ║║Executed [Kitchen - Back Hue color spot 1].off (14ms)
+197ms ║║Executed physical command [Kitchen - Back Hue color spot 2].off() (14ms)
+198ms ║║Executed [Kitchen - Back Hue color spot 2].off (15ms)
+213ms ║║Executed physical command [Kitchen - Back Hue color spot 3].off() (12ms)
+214ms ║║Executed [Kitchen - Back Hue color spot 3].off (14ms)
+229ms ║║Executed physical command [Kitchen - Forward Hue color spot 1].off() (12ms)
+230ms ║║Executed [Kitchen - Forward Hue color spot 1].off (14ms)
+244ms ║║Executed physical command [Kitchen - Forward Hue color spot 1].off() (12ms)
+245ms ║║Executed [Kitchen - Forward Hue color spot 1].off (13ms)
+259ms ║║Executed physical command [Kitchen - Forward Hue color spot 2].off() (11ms)
+260ms ║║Executed [Kitchen - Forward Hue color spot 2].off (13ms)
+273ms ║║Executed physical command [Kitchen - Under cabinet lights].off() (11ms)
+274ms ║║Executed [Kitchen - Under cabinet lights].off (13ms)
+277ms ║╚Execution stage complete. (134ms)
+278ms ╚Event processed successfully (278ms)
03/09/2018, 19:09:37 +820ms
+2ms ╔Received event [Hue Motion Sensor - Kitchen].motion = inactive with a delay of 81ms
+108ms ║RunTime Analysis CS > 15ms > PS > 37ms > PE > 56ms > CE
+110ms ║Runtime (38283 bytes) successfully initialized in 37ms (v0.3.107.20180806) (108ms)
+111ms ║╔Execution stage started
+119ms ║║Comparison (enum) inactive is (string) active = false (1ms)
+120ms ║║Cancelling condition #8’s schedules…
+121ms ║║Condition #8 evaluated false (5ms)
+122ms ║║Cancelling condition #1’s schedules…
+123ms ║║Condition group #1 evaluated false (state changed) (7ms)
+124ms ║║Cancelling statement #3’s schedules…
+130ms ║║Executed virtual command [Kitchen - Back Hue color spot 1, Kitchen - Back Hue color spot 2, Kitchen - Back Hue color spot 3, Kitchen - Forward Hue color spot 1, Kitchen - Forward Hue color spot 1, Kitchen - Forward Hue color spot 2, Kitchen - Under cabinet lights].wait (0ms)
+131ms ║║Requesting a wake up for Mon, Sep 3 2018 @ 7:10:07 PM BST (in 30.0s)
+135ms ║╚Execution stage complete. (24ms)
+137ms ║Setting up scheduled job for Mon, Sep 3 2018 @ 7:10:07 PM BST (in 29.996s)
+146ms ╚Event processed successfully (146ms)

I’m new to webcoRE so my knowledge is minimal. Is this what you mean?


#9

No. Go to the WebCore community and look at the example pistons. You can view them without being verified or logged in. There are a ton up there.


(Robin) #10

Logs look good…

Sensor went inactive and scheduled a 30 second wait.

30 second wait ended and lights turned off.

Sensor became active again, but lux was 22 (outside your restriction) so nothing happened.

We need logs from where they fail to turn off… I suspect in those cases your setup was not in ‘home’ mode.


(Jack) #11

That’s not overly helpful! I have already done this…


(Jack) #12

I will try and find an example of when it doesn’t work.


#13

But you didn’t follow any of them obviously since you have the off function nested inside the on function which won’t work. You have to separate them.


(Robin) #14

actually @Ryan780 has a valid point there… i completely missed the missing ‘else’.

Try:

If
motion sensor 2 is active
then
(only when illuminance is less than or equal to 15lux)
with x y z
do
tun on
ELSE
with x y z
wait 30 seconds
turn off


(Jack) #15

If it was obvious, i wouldn’t be asking. I didn’t understand what you meant and changed it to what i thought you meant. So far you have just picked holes in things and when i ask for help you simply say “look at the examples” which i “obviously” have already done.


(Jack) #16

How is this different to what I started with?


(Robin) #17

It’s not… just pushing back on track.

Revert to the original piston, keep logging on full and post logs next time it fails to turn off.


(Jack) #18

Ah, I see. Ryan said originally that else statements were unreliable. Do you use them and if not, how do you configure it?


(Robin) #19

I use them all the time, perfectly reliable.

Mainly for lighting automatons like yours.

If motion turn on, else turn off

If door open turn on, else turn off

But I also go crazy complicated with nested elses within elses lol… to cover more complex multi scenario situations:


(Jack) #20

Great, thanks. I will revert to the original script and upload a log tomorrow when i have one of it misbehaving. Thanks again for your help.