Update hue-minimote.groovy
[smartapps.git] / official / opent2t-smartapp-test.groovy
1 import javax.crypto.Mac;
2 import javax.crypto.spec.SecretKeySpec;
3 import java.security.InvalidKeyException;
4
5 /**
6  *  OpenT2T SmartApp Test
7  *
8  *  Copyright 2016 OpenT2T
9  *
10  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
11  *  in compliance with the License. You may obtain a copy of the License at:
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
16  *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
17  *  for the specific language governing permissions and limitations under the License.
18  *
19  */
20 definition(
21                 name: "OpenT2T SmartApp Test",
22                 namespace: "opent2t",
23                 author: "OpenT2T",
24                 description: "Test app to test end to end SmartThings scenarios via OpenT2T",
25                 category: "SmartThings Labs",
26                 iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
27                 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
28                 iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
29
30 /** --------------------+---------------+-----------------------+------------------------------------
31  *  Device Type         | Attribute Name| Commands              | Attribute Values
32  *  --------------------+---------------+-----------------------+------------------------------------
33  *  switches            | switch        | on, off               | on, off
34  *  motionSensors       | motion        |                       | active, inactive
35  *  contactSensors      | contact       |                       | open, closed
36  *  presenceSensors     | presence      |                       | present, 'not present'
37  *  temperatureSensors  | temperature   |                       | <numeric, F or C according to unit>
38  *  accelerationSensors | acceleration  |                       | active, inactive
39  *  waterSensors        | water         |                       | wet, dry
40  *  lightSensors        | illuminance   |                       | <numeric, lux>
41  *  humiditySensors     | humidity      |                       | <numeric, percent>
42  *  locks               | lock          | lock, unlock          | locked, unlocked
43  *  garageDoors         | door          | open, close           | unknown, closed, open, closing, opening
44  *  cameras             | image         | take                  | <String>
45  *  thermostats         | thermostat    | setHeatingSetpoint,   | temperature, heatingSetpoint, coolingSetpoint,
46  *                          |                           | setCoolingSetpoint,   | thermostatSetpoint, thermostatMode,
47  *                      |                               | off, heat, cool, auto,| thermostatFanMode, thermostatOperatingState
48  *                      |                               | emergencyHeat,        |
49  *                      |                               | setThermostatMode,    |
50  *                      |                               | fanOn, fanAuto,       |
51  *                      |                               | fanCirculate,         |
52  *                      |                               | setThermostatFanMode  |
53  *  --------------------+---------------+-----------------------+------------------------------------
54  */
55
56 //Device Inputs
57 preferences {
58         section("Allow OpenT2T to control these things...") {
59                 input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true
60                 input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true
61                 input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true
62                 input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true
63                 input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true
64                 input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true
65                 input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false, hideWhenEmpty: true
66                 input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false, hideWhenEmpty: true
67                 input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false, hideWhenEmpty: true
68         }
69 }
70
71 def getInputs() {
72         def inputList = []
73         inputList += contactSensors ?: []
74         inputList += garageDoors ?: []
75         inputList += locks ?: []
76         inputList += cameras ?: []
77         inputList += motionSensors ?: []
78         inputList += presenceSensors ?: []
79         inputList += switches ?: []
80         inputList += thermostats ?: []
81         inputList += waterSensors ?: []
82         return inputList
83 }
84
85 //API external Endpoints
86 mappings {
87         path("/devices") {
88                 action:
89                 [
90                                 GET: "getDevices"
91                 ]
92         }
93         path("/devices/:id") {
94                 action:
95                 [
96                                 GET: "getDevice"
97                 ]
98         }
99         path("/update/:id") {
100                 action:
101                 [
102                                 PUT: "updateDevice"
103                 ]
104         }
105         path("/deviceSubscription") {
106                 action:
107                 [
108                                 POST  : "registerDeviceChange",
109                                 DELETE: "unregisterDeviceChange"
110                 ]
111         }
112         path("/locationSubscription") {
113                 action:
114                 [
115                                 POST  : "registerDeviceGraph",
116                                 DELETE: "unregisterDeviceGraph"
117                 ]
118         }
119 }
120
121 def installed() {
122         log.debug "Installing with settings: ${settings}"
123         initialize()
124 }
125
126 def updated() {
127         log.debug "Updating with settings: ${settings}"
128
129         //Initialize state variables if didn't exist.
130         if (state.deviceSubscriptionMap == null) {
131                 state.deviceSubscriptionMap = [:]
132                 log.debug "deviceSubscriptionMap created."
133         }
134         if (state.locationSubscriptionMap == null) {
135                 state.locationSubscriptionMap = [:]
136                 log.debug "locationSubscriptionMap created."
137         }
138         if (state.verificationKeyMap == null) {
139                 state.verificationKeyMap = [:]
140                 log.debug "verificationKeyMap created."
141         }
142
143         unsubscribe()
144         registerAllDeviceSubscriptions()
145 }
146
147 def initialize() {
148         log.debug "Initializing with settings: ${settings}"
149         state.deviceSubscriptionMap = [:]
150         log.debug "deviceSubscriptionMap created."
151         state.locationSubscriptionMap = [:]
152         log.debug "locationSubscriptionMap created."
153         state.verificationKeyMap = [:]
154         log.debug "verificationKeyMap created."
155         registerAllDeviceSubscriptions()
156 }
157
158 /*** Subscription Functions  ***/
159
160 //Subscribe events for all devices
161 def registerAllDeviceSubscriptions() {
162         registerChangeHandler(inputs)
163 }
164
165 //Endpoints function: Subscribe to events from a specific device
166 def registerDeviceChange() {
167         def subscriptionEndpt = params.subscriptionURL
168         def deviceId = params.deviceId
169         def myDevice = findDevice(deviceId)
170
171         if (myDevice == null) {
172                 httpError(404, "Cannot find device with device ID ${deviceId}.")
173         }
174
175         def theAtts = myDevice.supportedAttributes
176         try {
177                 theAtts.each { att ->
178                         subscribe(myDevice, att.name, deviceEventHandler)
179                 }
180                 log.info "Subscribing for ${myDevice.displayName}"
181
182                 if (subscriptionEndpt != null) {
183                         if (state.deviceSubscriptionMap[deviceId] == null) {
184                                 state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
185                                 log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
186                         } else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
187                                 state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
188                                 log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
189                         }
190
191                         if (params.key != null) {
192                                 state.verificationKeyMap[subscriptionEndpt] = params.key
193                                 log.info "Added verification key: ${params.key} for ${subscriptionEndpt}"
194                         }
195                 }
196         } catch (e) {
197                 httpError(500, "something went wrong: $e")
198         }
199
200         log.info "Current subscription map is ${state.deviceSubscriptionMap}"
201         log.info "Current verification key map is ${state.verificationKeyMap}"
202         return ["succeed"]
203 }
204
205 //Endpoints function: Unsubscribe to events from a specific device
206 def unregisterDeviceChange() {
207         def subscriptionEndpt = params.subscriptionURL
208         def deviceId = params.deviceId
209         def myDevice = findDevice(deviceId)
210
211         if (myDevice == null) {
212                 httpError(404, "Cannot find device with device ID ${deviceId}.")
213         }
214
215         try {
216                 if (subscriptionEndpt != null && subscriptionEndpt != "undefined") {
217                         if (state.deviceSubscriptionMap[deviceId]?.contains(subscriptionEndpt)) {
218                                 if (state.deviceSubscriptionMap[deviceId].size() == 1) {
219                                         state.deviceSubscriptionMap.remove(deviceId)
220                                 } else {
221                                         state.deviceSubscriptionMap[deviceId].remove(subscriptionEndpt)
222                                 }
223                                 state.verificationKeyMap.remove(subscriptionEndpt)
224                                 log.info "Removed subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
225                         }
226                 } else {
227                         state.deviceSubscriptionMap.remove(deviceId)
228                         log.info "Unsubscriping for ${myDevice.displayName}"
229                 }
230         } catch (e) {
231                 httpError(500, "something went wrong: $e")
232         }
233
234         log.info "Current subscription map is ${state.deviceSubscriptionMap}"
235         log.info "Current verification key map is ${state.verificationKeyMap}"
236 }
237
238 //Endpoints function: Subscribe to device additiona/removal updated in a location
239 def registerDeviceGraph() {
240         def subscriptionEndpt = params.subscriptionURL
241
242         if (subscriptionEndpt != null && subscriptionEndpt != "undefined") {
243                 subscribe(location, "DeviceCreated", locationEventHandler, [filterEvents: false])
244                 subscribe(location, "DeviceUpdated", locationEventHandler, [filterEvents: false])
245                 subscribe(location, "DeviceDeleted", locationEventHandler, [filterEvents: false])
246
247                 if (state.locationSubscriptionMap[location.id] == null) {
248                         state.locationSubscriptionMap.put(location.id, [subscriptionEndpt])
249                         log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
250                 } else if (!state.locationSubscriptionMap[location.id].contains(subscriptionEndpt)) {
251                         state.locationSubscriptionMap[location.id] << subscriptionEndpt
252                         log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
253                 }
254
255                 if (params.key != null) {
256                         state.verificationKeyMap[subscriptionEndpt] = params.key
257                         log.info "Added verification key: ${params.key} for ${subscriptionEndpt}"
258                 }
259
260                 log.info "Current location subscription map is ${state.locationSubscriptionMap}"
261                 log.info "Current verification key map is ${state.verificationKeyMap}"
262                 return ["succeed"]
263         } else {
264                 httpError(400, "missing input parameter: subscriptionURL")
265         }
266 }
267
268 //Endpoints function: Unsubscribe to events from a specific device
269 def unregisterDeviceGraph() {
270         def subscriptionEndpt = params.subscriptionURL
271
272         try {
273                 if (subscriptionEndpt != null && subscriptionEndpt != "undefined") {
274                         if (state.locationSubscriptionMap[location.id]?.contains(subscriptionEndpt)) {
275                                 if (state.locationSubscriptionMap[location.id].size() == 1) {
276                                         state.locationSubscriptionMap.remove(location.id)
277                                 } else {
278                                         state.locationSubscriptionMap[location.id].remove(subscriptionEndpt)
279                                 }
280                                 state.verificationKeyMap.remove(subscriptionEndpt)
281                                 log.info "Removed subscription URL: ${subscriptionEndpt} for Location ${location.name}"
282                         }
283                 } else {
284                         httpError(400, "missing input parameter: subscriptionURL")
285                 }
286         } catch (e) {
287                 httpError(500, "something went wrong: $e")
288         }
289
290         log.info "Current location subscription map is ${state.locationSubscriptionMap}"
291         log.info "Current verification key map is ${state.verificationKeyMap}"
292 }
293
294 //When events are triggered, send HTTP post to web socket servers
295 def deviceEventHandler(evt) {
296         def evtDevice = evt.device
297         def evtDeviceType = getDeviceType(evtDevice)
298         def deviceData = [];
299
300         if (evt.data != null) {
301                 def evtData = parseJson(evt.data)
302                 log.info "Received event for ${evtDevice.displayName}, data: ${evtData},  description: ${evt.descriptionText}"
303         }
304
305         if (evtDeviceType == "thermostat") {
306                 deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationMode: getLocationModeInfo(), locationId: location.id]
307         } else {
308                 deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationId: location.id]
309         }
310
311         def params = [body: deviceData]
312
313         //send event to all subscriptions urls
314         log.debug "Current subscription urls for ${evtDevice.displayName} is ${state.deviceSubscriptionMap[evtDevice.id]}"
315         state.deviceSubscriptionMap[evtDevice.id].each {
316                 params.uri = "${it}"
317                 if (state.verificationKeyMap[it] != null) {
318                         def key = state.verificationKeyMap[it]
319                         params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
320                 }
321                 log.trace "POST URI: ${params.uri}"
322                 log.trace "Header: ${params.header}"
323                 log.trace "Payload: ${params.body}"
324                 try {
325                         httpPostJson(params) { resp ->
326                                 log.trace "response status code: ${resp.status}"
327                                 log.trace "response data: ${resp.data}"
328                         }
329                 } catch (e) {
330                         log.error "something went wrong: $e"
331                 }
332         }
333 }
334
335 def locationEventHandler(evt) {
336         log.info "Received event for location ${location.name}/${location.id}, Event: ${evt.name}, description: ${evt.descriptionText}, apiServerUrl: ${apiServerUrl("")}"
337         switch (evt.name) {
338                 case "DeviceCreated":
339                 case "DeviceDeleted":
340                         def evtDevice = evt.device
341                         def evtDeviceType = getDeviceType(evtDevice)
342                         def params = [body: [eventType: evt.name, deviceId: evtDevice.id, locationId: location.id]]
343
344                         if (evt.name == "DeviceDeleted" && state.deviceSubscriptionMap[deviceId] != null) {
345                                 state.deviceSubscriptionMap.remove(evtDevice.id)
346                         }
347
348                         state.locationSubscriptionMap[location.id].each {
349                                 params.uri = "${it}"
350                                 if (state.verificationKeyMap[it] != null) {
351                                         def key = state.verificationKeyMap[it]
352                                         params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
353                                 }
354                                 log.trace "POST URI: ${params.uri}"
355                                 log.trace "Header: ${params.header}"
356                                 log.trace "Payload: ${params.body}"
357                                 try {
358                                         httpPostJson(params) { resp ->
359                                                 log.trace "response status code: ${resp.status}"
360                                                 log.trace "response data: ${resp.data}"
361                                         }
362                                 } catch (e) {
363                                         log.error "something went wrong: $e"
364                                 }
365                         }
366                 case "DeviceUpdated":
367                 default:
368                         break
369         }
370 }
371
372 private ComputHMACValue(key, data) {
373         try {
374                 SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1")
375                 Mac mac = Mac.getInstance("HmacSHA1")
376                 mac.init(secretKeySpec)
377                 byte[] digest = mac.doFinal(data.getBytes("UTF-8"))
378                 return byteArrayToString(digest)
379         } catch (InvalidKeyException e) {
380                 log.error "Invalid key exception while converting to HMac SHA1"
381         }
382 }
383
384 private def byteArrayToString(byte[] data) {
385         BigInteger bigInteger = new BigInteger(1, data)
386         String hash = bigInteger.toString(16)
387         return hash
388 }
389
390 /*** Device Query/Update Functions  ***/
391
392 //Endpoints function: return all device data in json format
393 def getDevices() {
394         def deviceData = []
395         inputs?.each {
396                 def deviceType = getDeviceType(it)
397                 if (deviceType == "thermostat") {
398                         deviceData << [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()]
399                 } else {
400                         deviceData << [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType)]
401                 }
402         }
403
404         log.debug "getDevices, return: ${deviceData}"
405         return deviceData
406 }
407
408 //Endpoints function: get device data
409 def getDevice() {
410         def it = findDevice(params.id)
411         def deviceType = getDeviceType(it)
412         def device
413         if (deviceType == "thermostat") {
414                 device = [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()]
415         } else {
416                 device = [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType)]
417         }
418
419         log.debug "getDevice, return: ${device}"
420         return device
421 }
422
423 //Endpoints function: update device data
424 void updateDevice() {
425         def device = findDevice(params.id)
426         request.JSON.each {
427                 def command = it.key
428                 def value = it.value
429                 if (command) {
430                         def commandList = mapDeviceCommands(command, value)
431                         command = commandList[0]
432                         value = commandList[1]
433
434                         if (command == "setAwayMode") {
435                                 log.info "Setting away mode to ${value}"
436                                 if (location.modes?.find { it.name == value }) {
437                                         location.setMode(value)
438                                 }
439                         } else if (command == "thermostatSetpoint") {
440                                 switch (device.currentThermostatMode) {
441                                         case "cool":
442                                                 log.info "Update: ${device.displayName}, [${command}, ${value}]"
443                                                 device.setCoolingSetpoint(value)
444                                                 break
445                                         case "heat":
446                                         case "emergency heat":
447                                                 log.info "Update: ${device.displayName}, [${command}, ${value}]"
448                                                 device.setHeatingSetpoint(value)
449                                                 break
450                                         default:
451                                                 httpError(501, "this mode: ${device.currentThermostatMode} does not allow changing thermostat setpoint.")
452                                                 break
453                                 }
454                         } else if (!device) {
455                                 log.error "updateDevice, Device not found"
456                                 httpError(404, "Device not found")
457                         } else if (!device.hasCommand(command)) {
458                                 log.error "updateDevice, Device does not have the command"
459                                 httpError(404, "Device does not have such command")
460                         } else {
461                                 if (command == "setColor") {
462                                         log.info "Update: ${device.displayName}, [${command}, ${value}]"
463                                         device."$command"(hex: value)
464                                 } else if (value.isNumber()) {
465                                         def intValue = value as Integer
466                                         log.info "Update: ${device.displayName}, [${command}, ${intValue}(int)]"
467                                         device."$command"(intValue)
468                                 } else if (value) {
469                                         log.info "Update: ${device.displayName}, [${command}, ${value}]"
470                                         device."$command"(value)
471                                 } else {
472                                         log.info "Update: ${device.displayName}, [${command}]"
473                                         device."$command"()
474                                 }
475                         }
476                 }
477         }
478 }
479
480 /*** Private Functions ***/
481
482 //Return current location mode info
483 private getLocationModeInfo() {
484         return [mode: location.mode, supported: location.modes.name]
485 }
486
487 //Map each device to a type given it's capabilities
488 private getDeviceType(device) {
489         def deviceType
490         def capabilities = device.capabilities
491         log.debug "capabilities: [${device}, ${capabilities}]"
492         log.debug "supported commands: [${device}, ${device.supportedCommands}]"
493
494         //Loop through the device capability list to determine the device type.
495         capabilities.each { capability ->
496                 switch (capability.name.toLowerCase()) {
497                         case "switch":
498                                 deviceType = "switch"
499
500                                 //If the device also contains "Switch Level" capability, identify it as a "light" device.
501                                 if (capabilities.any { it.name.toLowerCase() == "switch level" }) {
502
503                                         //If the device also contains "Power Meter" capability, identify it as a "dimmerSwitch" device.
504                                         if (capabilities.any { it.name.toLowerCase() == "power meter" }) {
505                                                 deviceType = "dimmerSwitch"
506                                                 return deviceType
507                                         } else {
508                                                 deviceType = "light"
509                                                 return deviceType
510                                         }
511                                 }
512                                 break
513                         case "garageDoorControl":
514                                 deviceType = "garageDoor"
515                                 return deviceType
516                         case "lock":
517                                 deviceType = "lock"
518                                 return deviceType
519                         case "video camera":
520                                 deviceType = "camera"
521                                 return deviceType
522                         case "thermostat":
523                                 deviceType = "thermostat"
524                                 return deviceType
525                         case "acceleration sensor":
526                         case "contact sensor":
527                         case "motion sensor":
528                         case "presence sensor":
529                         case "water sensor":
530                                 deviceType = "genericSensor"
531                                 return deviceType
532                         default:
533                                 break
534                 }
535         }
536         return deviceType
537 }
538
539 //Return a specific device give the device ID.
540 private findDevice(deviceId) {
541         return inputs?.find { it.id == deviceId }
542 }
543
544 //Return a list of device attributes
545 private deviceAttributeList(device, deviceType) {
546         def attributeList = [:]
547         def allAttributes = device.supportedAttributes
548         allAttributes.each { attribute ->
549                 try {
550                         def currentState = device.currentState(attribute.name)
551                         if (currentState != null) {
552                                 switch (attribute.name) {
553                                         case 'temperature':
554                                                 attributeList.putAll([(attribute.name): currentState.value, 'temperatureScale': location.temperatureScale])
555                                                 break;
556                                         default:
557                                                 attributeList.putAll([(attribute.name): currentState.value])
558                                                 break;
559                                 }
560                                 if (deviceType == "genericSensor") {
561                                         def key = attribute.name + "_lastUpdated"
562                                         attributeList.putAll([(key): currentState.isoDate])
563                                 }
564                         } else {
565                                 attributeList.putAll([(attribute.name): null]);
566                         }
567                 } catch (e) {
568                         attributeList.putAll([(attribute.name): null]);
569                 }
570         }
571         return attributeList
572 }
573
574 //Map device command and value. 
575 //input command and value are from UWP,
576 //returns resultCommand and resultValue that corresponds with function and value in SmartApps
577 private mapDeviceCommands(command, value) {
578         log.debug "mapDeviceCommands: [${command}, ${value}]"
579         def resultCommand = command
580         def resultValue = value
581         switch (command) {
582                 case "switch":
583                         if (value == 1 || value == "1" || value == "on") {
584                                 resultCommand = "on"
585                                 resultValue = ""
586                         } else if (value == 0 || value == "0" || value == "off") {
587                                 resultCommand = "off"
588                                 resultValue = ""
589                         }
590                         break
591         // light attributes
592                 case "level":
593                         resultCommand = "setLevel"
594                         resultValue = value
595                         break
596                 case "hue":
597                         resultCommand = "setHue"
598                         resultValue = value
599                         break
600                 case "saturation":
601                         resultCommand = "setSaturation"
602                         resultValue = value
603                         break
604                 case "colorTemperature":
605                         resultCommand = "setColorTemperature"
606                         resultValue = value
607                         break
608                 case "color":
609                         resultCommand = "setColor"
610                         resultValue = value
611         // thermostat attributes
612                 case "hvacMode":
613                         resultCommand = "setThermostatMode"
614                         resultValue = value
615                         break
616                 case "fanMode":
617                         resultCommand = "setThermostatFanMode"
618                         resultValue = value
619                         break
620                 case "awayMode":
621                         resultCommand = "setAwayMode"
622                         resultValue = value
623                         break
624                 case "coolingSetpoint":
625                         resultCommand = "setCoolingSetpoint"
626                         resultValue = value
627                         break
628                 case "heatingSetpoint":
629                         resultCommand = "setHeatingSetpoint"
630                         resultValue = value
631                         break
632                 case "thermostatSetpoint":
633                         resultCommand = "thermostatSetpoint"
634                         resultValue = value
635                         break
636         // lock attributes
637                 case "locked":
638                         if (value == 1 || value == "1" || value == "lock") {
639                                 resultCommand = "lock"
640                                 resultValue = ""
641                         } else if (value == 0 || value == "0" || value == "unlock") {
642                                 resultCommand = "unlock"
643                                 resultValue = ""
644                         }
645                         break
646                 default:
647                         break
648         }
649
650         return [resultCommand, resultValue]
651 }