Compare commits

..

3 Commits

2 changed files with 367 additions and 152 deletions

View File

@@ -0,0 +1,273 @@
/**
* Lloyds Banking Group Connect & Protect
*
* Copyright 2016 Domotz
*
* 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.
*
*/
definition(
name: "Lloyds Banking Group Connect & Protect",
namespace: "domotz.dev",
author: "Domotz",
description: "The Lloyds Connect & Protect SmartApp is a bridge between SmartThings Cloud and Lloyds Banking Group to enable advanced connected device monitoring and alerting features to be included in the offering to their customers for the connected home service",
category: "Convenience",
singleInstance: true,
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
oauth: [displayName: "Lloyds Banking Group Connect & Protect", displayLink: ""]) {
appSetting "endpointRetrievalUrl"
appSetting "xApiKey"
}
def getSupportedTypes() {
return [
[obj: switches, name: "switches", attribute: "switch", capability: "switch", title: "Switches"],
[obj: motions, name: "motions", attribute: "motion", capability: "motionSensor", title: "Motion Sensors"],
[obj: temperature, name: "temperature", attribute: "temperature", capability: "temperatureMeasurement", title: "Temperature Sensors"],
[obj: contact, name: "contact", attribute: "contact", capability: "contactSensor", title: "Contact Sensors"],
[obj: presence, name: "presence", attribute: "presence", capability: "presenceSensor", title: "Presence Sensors"],
[obj: water, name: "water", attribute: "water", capability: "waterSensor", title: "Water Sensors"],
[obj: smoke, name: "smoke", attribute: "smoke", capability: "smokeDetector", title: "Smoke Sensors"],
[obj: battery, name: "battery", attribute: "battery", capability: "battery", title: "Batteries"]
]
}
preferences {
section("Allow Lloyds Banking Group Connect & Protect service to monitor these devices") {
for (type in getSupportedTypes()) {
input type.get("name"), "capability.${type.get('capability')}", title: type.get('title'), multiple: true
}
}
}
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
hubUpdateHandler()
}
def getRequestHeaders() {
return ['Accept': '*/*', 'X-API-KEY': appSettings.xApiKey]
}
def subscribeToDeviceEvents() {
for (type in getSupportedTypes()) {
subscribe(type.get("obj"), "${type.get("attribute")}", genericDeviceEventHandler)
}
}
def initialize() {
if (atomicState.endpoint != null) {
log.debug "Detected endpoint: ${atomicState.endpoint}"
subscribeToDeviceEvents()
subscribe(location, "routineExecuted", modeChangeHandler)
subscribe(location, "mode", modeChangeHandler)
} else {
log.debug "There is no endpoint, requesting domotz for a new one"
requestNewEndpoint()
}
}
def getHubLocation() {
def location_info = [:]
location_info['uid'] = location.id
//location_info['hubs'] = location.hubs
location_info['latitude'] = location.latitude
location_info['longitude'] = location.longitude
location_info['current_mode'] = location.mode
//location_info['modes'] = location.modes
location_info['name'] = location.name
location_info['temperature_scale'] = location.temperatureScale
location_info['version'] = location.version
location_info['channel_name'] = location.channelName
location_info['zip_code'] = location.zipCode
log.debug "Triggered getHubLocation with properties: ${location_info}"
return location_info
}
def modeChangeHandler(evt) {
log.debug "mode changed to ${evt.value}"
def url = null
if (atomicState.endpoint != null) {
url = atomicState.endpoint + '/hub-change'
httpPutJson(
uri: url,
body: getHubLocation(),
headers: getRequestHeaders()
)
} else {
log.debug "There is no endpoint, requesting domotz for a new one"
requestNewEndpoint()
}
}
def genericDeviceEventHandler(event) {
log.debug "Device Event Handler, event properties: ${event.getProperties().toString()}"
log.debug "Device Event Handler, value: ${event.value}"
def resp = [:]
def url = null
def device = null
device = getDevice(event.device, resp)
url = atomicState.endpoint + "/device/" + device.provider_uid + "/${event.name}"
log.debug "Device Event Handler, put url: ${url}"
log.debug "Device Event Handler, put body: value: ${event.value}\ndate: ${event.isoDate}"
httpPutJson(
uri: url,
body: [
"value": event.value,
"time" : event.isoDate
],
headers: getRequestHeaders()
)
}
def hubUpdateHandler() {
log.debug "Hub Update Handler, with settings: ${settings}"
def url = null
def deviceList = [:]
if (atomicState.endpoint != null) {
url = atomicState.endpoint + '/device-list'
log.debug "Hub Update Event Handler, put url: ${url}"
deviceList = getDeviceList()
httpPutJson(
uri: url,
body: deviceList,
headers: getRequestHeaders()
)
} else {
log.debug "There is no endpoint, requesting domotz for a new one"
requestNewEndpoint()
}
}
def getDeviceList() {
try {
def resp = [:]
def attribute = null
for (type in getSupportedTypes()) {
type.get("obj").each {
device = getDevice(it, resp)
attribute = type.get("attribute")
if (it.currentState(attribute)) {
device['attributes'][attribute] = [
"value": it.currentState(attribute).value,
"time" : it.currentState(attribute).getIsoDate(),
"unit" : it.currentState(attribute).unit
]
}
}
}
return resp
} catch (e) {
log.debug("caught exception", e)
return [:]
}
}
def getDevice(it, resp) {
if (resp[it.id]) {
return resp[it.id]
}
resp[it.id] = [name: it.name, display_name: it.displayName, provider_uid: it.id, type: it.typeName, label: it.label, manufacturer_name: it.manufacturerName, model: it.modelName, attributes: [:]]
}
def activateMonitoring(resp) {
unsubscribe()
log.debug "Event monitoring activated for endpoint: ${request.JSON.endpoint}"
atomicState.endpoint = request.JSON.endpoint
log.debug "Event monitoring activated for endpoint: ${atomicState.endpoint}"
initialize()
}
def deactivateMonitoring() {
log.debug "Event monitoring deactivated."
atomicState.endpoint = null
unsubscribe()
}
def requestNewEndpoint() {
log.debug "Requesting a new endpoint."
def hubId = location.id
def params = [
uri : "${appSettings.endpointRetrievalUrl}/${hubId}/endpoint",
headers: getRequestHeaders()
]
try {
httpGet(params) { response ->
log.debug "Request was successful, received endpoint: ${response.data.endpoint}"
atomicState.endpoint = response.data.endpoint
subscribeToDeviceEvents()
}
}
catch (e) {
log.debug "Unable to retrieve the endpoint"
}
}
def handleClientUninstall() {
log.info("Deactivated from client")
try {
app.delete()
} catch (e) {
unschedule()
unsubscribe()
httpError(500, "An error occurred during deleting SmartApp: ${e}")
}
}
mappings {
path("/device") {
action:
[
GET: getDeviceList
]
}
path("/location") {
action:
[
GET: getHubLocation
]
}
path("/monitoring") {
action:
[
POST : activateMonitoring,
DELETE: deactivateMonitoring
]
}
path("/uninstall") {
action
[GET: handleClientUninstall]
}
}

View File

@@ -1,7 +1,3 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
/** /**
* OpenT2T SmartApp Test * OpenT2T SmartApp Test
* *
@@ -43,7 +39,7 @@ definition(
* garageDoors | door | open, close | unknown, closed, open, closing, opening * garageDoors | door | open, close | unknown, closed, open, closing, opening
* cameras | image | take | <String> * cameras | image | take | <String>
* thermostats | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint, coolingSetpoint, * thermostats | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint, coolingSetpoint,
* | | setCoolingSetpoint, | thermostatSetpoint, thermostatMode, * | | setCoolingSetpoint, | thermostatSetpoint, thermostatMode,
* | | off, heat, cool, auto,| thermostatFanMode, thermostatOperatingState * | | off, heat, cool, auto,| thermostatFanMode, thermostatOperatingState
* | | emergencyHeat, | * | | emergencyHeat, |
* | | setThermostatMode, | * | | setThermostatMode, |
@@ -59,7 +55,7 @@ preferences {
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false, hideWhenEmpty: true input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false, hideWhenEmpty: true
@@ -70,49 +66,44 @@ preferences {
def getInputs() { def getInputs() {
def inputList = [] def inputList = []
inputList += contactSensors ?: [] inputList += contactSensors?: []
inputList += garageDoors ?: [] inputList += garageDoors?: []
inputList += locks ?: [] inputList += locks?: []
inputList += cameras ?: [] inputList += cameras?: []
inputList += motionSensors ?: [] inputList += motionSensors?: []
inputList += presenceSensors ?: [] inputList += presenceSensors?: []
inputList += switches ?: [] inputList += switches?: []
inputList += thermostats ?: [] inputList += thermostats?: []
inputList += waterSensors ?: [] inputList += waterSensors?: []
return inputList return inputList
} }
//API external Endpoints //API external Endpoints
mappings { mappings {
path("/devices") { path("/devices") {
action: action: [
[
GET: "getDevices" GET: "getDevices"
] ]
} }
path("/devices/:id") { path("/devices/:id") {
action: action: [
[
GET: "getDevice" GET: "getDevice"
] ]
} }
path("/update/:id") { path("/update/:id") {
action: action: [
[
PUT: "updateDevice" PUT: "updateDevice"
] ]
} }
path("/deviceSubscription") { path("/deviceSubscription") {
action: action: [
[ POST: "registerDeviceChange",
POST : "registerDeviceChange",
DELETE: "unregisterDeviceChange" DELETE: "unregisterDeviceChange"
] ]
} }
path("/locationSubscription") { path("/locationSubscription") {
action: action: [
[ POST: "registerDeviceGraph",
POST : "registerDeviceGraph",
DELETE: "unregisterDeviceGraph" DELETE: "unregisterDeviceGraph"
] ]
} }
@@ -125,21 +116,14 @@ def installed() {
def updated() { def updated() {
log.debug "Updating with settings: ${settings}" log.debug "Updating with settings: ${settings}"
if(state.deviceSubscriptionMap == null){
//Initialize state variables if didn't exist.
if (state.deviceSubscriptionMap == null) {
state.deviceSubscriptionMap = [:] state.deviceSubscriptionMap = [:]
log.debug "deviceSubscriptionMap created." log.debug "deviceSubscriptionMap created."
} }
if (state.locationSubscriptionMap == null) { if( state.locationSubscriptionMap == null){
state.locationSubscriptionMap = [:] state.locationSubscriptionMap = [:]
log.debug "locationSubscriptionMap created." log.debug "locationSubscriptionMap created."
} }
if (state.verificationKeyMap == null) {
state.verificationKeyMap = [:]
log.debug "verificationKeyMap created."
}
unsubscribe() unsubscribe()
registerAllDeviceSubscriptions() registerAllDeviceSubscriptions()
} }
@@ -148,11 +132,9 @@ def initialize() {
log.debug "Initializing with settings: ${settings}" log.debug "Initializing with settings: ${settings}"
state.deviceSubscriptionMap = [:] state.deviceSubscriptionMap = [:]
log.debug "deviceSubscriptionMap created." log.debug "deviceSubscriptionMap created."
registerAllDeviceSubscriptions()
state.locationSubscriptionMap = [:] state.locationSubscriptionMap = [:]
log.debug "locationSubscriptionMap created." log.debug "locationSubscriptionMap created."
state.verificationKeyMap = [:]
log.debug "verificationKeyMap created."
registerAllDeviceSubscriptions()
} }
/*** Subscription Functions ***/ /*** Subscription Functions ***/
@@ -162,43 +144,47 @@ def registerAllDeviceSubscriptions() {
registerChangeHandler(inputs) registerChangeHandler(inputs)
} }
//Subscribe to events from a list of devices
def registerChangeHandler(myList) {
myList.each { myDevice ->
def theAtts = myDevice.supportedAttributes
theAtts.each {att ->
subscribe(myDevice, att.name, deviceEventHandler)
log.info "Registering for ${myDevice.displayName}.${att.name}"
}
}
}
//Endpoints function: Subscribe to events from a specific device //Endpoints function: Subscribe to events from a specific device
def registerDeviceChange() { def registerDeviceChange() {
def subscriptionEndpt = params.subscriptionURL def subscriptionEndpt = params.subscriptionURL
def deviceId = params.deviceId def deviceId = params.deviceId
def myDevice = findDevice(deviceId) def myDevice = findDevice(deviceId)
if( myDevice == null ){
if (myDevice == null) {
httpError(404, "Cannot find device with device ID ${deviceId}.") httpError(404, "Cannot find device with device ID ${deviceId}.")
} }
def theAtts = myDevice.supportedAttributes def theAtts = myDevice.supportedAttributes
try { try {
theAtts.each { att -> theAtts.each {att ->
subscribe(myDevice, att.name, deviceEventHandler) subscribe(myDevice, att.name, deviceEventHandler)
} }
log.info "Subscribing for ${myDevice.displayName}" log.info "Subscribing for ${myDevice.displayName}"
if (subscriptionEndpt != null) { if(subscriptionEndpt != null){
if (state.deviceSubscriptionMap[deviceId] == null) { if(state.deviceSubscriptionMap[deviceId] == null){
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt]) state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}" log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) { } else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)){
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}" log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
} }
if (params.key != null) {
state.verificationKeyMap[subscriptionEndpt] = params.key
log.info "Added verification key: ${params.key} for ${subscriptionEndpt}"
}
} }
} catch (e) { } catch (e) {
httpError(500, "something went wrong: $e") httpError(500, "something went wrong: $e")
} }
log.info "Current subscription map is ${state.deviceSubscriptionMap}" log.info "Current subscription map is ${state.deviceSubscriptionMap}"
log.info "Current verification key map is ${state.verificationKeyMap}"
return ["succeed"] return ["succeed"]
} }
@@ -208,19 +194,18 @@ def unregisterDeviceChange() {
def deviceId = params.deviceId def deviceId = params.deviceId
def myDevice = findDevice(deviceId) def myDevice = findDevice(deviceId)
if (myDevice == null) { if( myDevice == null ){
httpError(404, "Cannot find device with device ID ${deviceId}.") httpError(404, "Cannot find device with device ID ${deviceId}.")
} }
try { try {
if (subscriptionEndpt != null && subscriptionEndpt != "undefined") { if(subscriptionEndpt != null && subscriptionEndpt != "undefined"){
if (state.deviceSubscriptionMap[deviceId]?.contains(subscriptionEndpt)) { if (state.deviceSubscriptionMap[deviceId]?.contains(subscriptionEndpt)){
if (state.deviceSubscriptionMap[deviceId].size() == 1) { if(state.deviceSubscriptionMap[deviceId].size() == 1){
state.deviceSubscriptionMap.remove(deviceId) state.deviceSubscriptionMap.remove(deviceId)
} else { } else {
state.deviceSubscriptionMap[deviceId].remove(subscriptionEndpt) state.deviceSubscriptionMap[deviceId].remove(subscriptionEndpt)
} }
state.verificationKeyMap.remove(subscriptionEndpt)
log.info "Removed subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}" log.info "Removed subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
} }
} else { } else {
@@ -232,33 +217,25 @@ def unregisterDeviceChange() {
} }
log.info "Current subscription map is ${state.deviceSubscriptionMap}" log.info "Current subscription map is ${state.deviceSubscriptionMap}"
log.info "Current verification key map is ${state.verificationKeyMap}"
} }
//Endpoints function: Subscribe to device additiona/removal updated in a location //Endpoints function: Subscribe to device additiona/removal updated in a location
def registerDeviceGraph() { def registerDeviceGraph() {
def subscriptionEndpt = params.subscriptionURL def subscriptionEndpt = params.subscriptionURL
if (subscriptionEndpt != null && subscriptionEndpt != "undefined") { if (subscriptionEndpt != null && subscriptionEndpt != "undefined"){
subscribe(location, "DeviceCreated", locationEventHandler, [filterEvents: false]) subscribe(location, "DeviceCreated", locationEventHandler, [filterEvents: false])
subscribe(location, "DeviceUpdated", locationEventHandler, [filterEvents: false]) subscribe(location, "DeviceUpdated", locationEventHandler, [filterEvents: false])
subscribe(location, "DeviceDeleted", locationEventHandler, [filterEvents: false]) subscribe(location, "DeviceDeleted", locationEventHandler, [filterEvents: false])
if (state.locationSubscriptionMap[location.id] == null) { if(state.locationSubscriptionMap[location.id] == null){
state.locationSubscriptionMap.put(location.id, [subscriptionEndpt]) state.locationSubscriptionMap.put(location.id, [subscriptionEndpt])
log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}" log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
} else if (!state.locationSubscriptionMap[location.id].contains(subscriptionEndpt)) { } else if (!state.locationSubscriptionMap[location.id].contains(subscriptionEndpt)){
state.locationSubscriptionMap[location.id] << subscriptionEndpt state.locationSubscriptionMap[location.id] << subscriptionEndpt
log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}" log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
} }
if (params.key != null) {
state.verificationKeyMap[subscriptionEndpt] = params.key
log.info "Added verification key: ${params.key} for ${subscriptionEndpt}"
}
log.info "Current location subscription map is ${state.locationSubscriptionMap}" log.info "Current location subscription map is ${state.locationSubscriptionMap}"
log.info "Current verification key map is ${state.verificationKeyMap}"
return ["succeed"] return ["succeed"]
} else { } else {
httpError(400, "missing input parameter: subscriptionURL") httpError(400, "missing input parameter: subscriptionURL")
@@ -270,17 +247,16 @@ def unregisterDeviceGraph() {
def subscriptionEndpt = params.subscriptionURL def subscriptionEndpt = params.subscriptionURL
try { try {
if (subscriptionEndpt != null && subscriptionEndpt != "undefined") { if(subscriptionEndpt != null && subscriptionEndpt != "undefined"){
if (state.locationSubscriptionMap[location.id]?.contains(subscriptionEndpt)) { if (state.locationSubscriptionMap[location.id]?.contains(subscriptionEndpt)){
if (state.locationSubscriptionMap[location.id].size() == 1) { if(state.locationSubscriptionMap[location.id].size() == 1){
state.locationSubscriptionMap.remove(location.id) state.locationSubscriptionMap.remove(location.id)
} else { } else {
state.locationSubscriptionMap[location.id].remove(subscriptionEndpt) state.locationSubscriptionMap[location.id].remove(subscriptionEndpt)
} }
state.verificationKeyMap.remove(subscriptionEndpt)
log.info "Removed subscription URL: ${subscriptionEndpt} for Location ${location.name}" log.info "Removed subscription URL: ${subscriptionEndpt} for Location ${location.name}"
} }
} else { }else{
httpError(400, "missing input parameter: subscriptionURL") httpError(400, "missing input parameter: subscriptionURL")
} }
} catch (e) { } catch (e) {
@@ -288,40 +264,28 @@ def unregisterDeviceGraph() {
} }
log.info "Current location subscription map is ${state.locationSubscriptionMap}" log.info "Current location subscription map is ${state.locationSubscriptionMap}"
log.info "Current verification key map is ${state.verificationKeyMap}"
} }
//When events are triggered, send HTTP post to web socket servers //When events are triggered, send HTTP post to web socket servers
def deviceEventHandler(evt) { def deviceEventHandler(evt) {
def evtDevice = evt.device def evt_device = evt.device
def evtDeviceType = getDeviceType(evtDevice) def evt_deviceType = getDeviceType(evt_device)
def deviceData = []; def deviceInfo
if (evt.data != null) { def params = [ body: [deviceName: evt_device.displayName, deviceId: evt_device.id, locationId: location.id] ]
if(evt.data != null){
def evtData = parseJson(evt.data) def evtData = parseJson(evt.data)
log.info "Received event for ${evtDevice.displayName}, data: ${evtData}, description: ${evt.descriptionText}" log.info "Received event for ${evt_device.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
} }
if (evtDeviceType == "thermostat") {
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]
} else {
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]
}
def params = [body: deviceData]
//send event to all subscriptions urls //send event to all subscriptions urls
log.debug "Current subscription urls for ${evtDevice.displayName} is ${state.deviceSubscriptionMap[evtDevice.id]}" log.debug "Current subscription urls for ${evt_device.displayName} is ${state.deviceSubscriptionMap[evt_device.id]}"
state.deviceSubscriptionMap[evtDevice.id].each { state.deviceSubscriptionMap[evt_device.id].each {
params.uri = "${it}" params.uri = "${it}"
if (state.verificationKeyMap[it] != null) {
def key = state.verificationKeyMap[it]
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
}
log.trace "POST URI: ${params.uri}" log.trace "POST URI: ${params.uri}"
log.trace "Header: ${params.header}"
log.trace "Payload: ${params.body}" log.trace "Payload: ${params.body}"
try { try{
httpPostJson(params) { resp -> httpPostJson(params) { resp ->
log.trace "response status code: ${resp.status}" log.trace "response status code: ${resp.status}"
log.trace "response data: ${resp.data}" log.trace "response data: ${resp.data}"
@@ -334,27 +298,20 @@ def deviceEventHandler(evt) {
def locationEventHandler(evt) { def locationEventHandler(evt) {
log.info "Received event for location ${location.name}/${location.id}, Event: ${evt.name}, description: ${evt.descriptionText}, apiServerUrl: ${apiServerUrl("")}" log.info "Received event for location ${location.name}/${location.id}, Event: ${evt.name}, description: ${evt.descriptionText}, apiServerUrl: ${apiServerUrl("")}"
switch (evt.name) { switch(evt.name){
case "DeviceCreated": case "DeviceCreated":
case "DeviceDeleted": case "DeviceDeleted":
def evtDevice = evt.device def evt_device = evt.device
def evtDeviceType = getDeviceType(evtDevice) def evt_deviceType = getDeviceType(evt_device)
def params = [body: [eventType: evt.name, deviceId: evtDevice.id, locationId: location.id]] log.info "DeviceName: ${evt_device.displayName}, DeviceID: ${evt_device.id}, deviceType: ${evt_deviceType}"
if (evt.name == "DeviceDeleted" && state.deviceSubscriptionMap[deviceId] != null) { def params = [ body: [ eventType:evt.name, deviceId: evt_device.id, locationId: location.id ] ]
state.deviceSubscriptionMap.remove(evtDevice.id)
}
state.locationSubscriptionMap[location.id].each { state.locationSubscriptionMap[location.id].each {
params.uri = "${it}" params.uri = "${it}"
if (state.verificationKeyMap[it] != null) {
def key = state.verificationKeyMap[it]
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
}
log.trace "POST URI: ${params.uri}" log.trace "POST URI: ${params.uri}"
log.trace "Header: ${params.header}"
log.trace "Payload: ${params.body}" log.trace "Payload: ${params.body}"
try { try{
httpPostJson(params) { resp -> httpPostJson(params) { resp ->
log.trace "response status code: ${resp.status}" log.trace "response status code: ${resp.status}"
log.trace "response data: ${resp.data}" log.trace "response data: ${resp.data}"
@@ -369,23 +326,6 @@ def locationEventHandler(evt) {
} }
} }
private ComputHMACValue(key, data) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1")
Mac mac = Mac.getInstance("HmacSHA1")
mac.init(secretKeySpec)
byte[] digest = mac.doFinal(data.getBytes("UTF-8"))
return byteArrayToString(digest)
} catch (InvalidKeyException e) {
log.error "Invalid key exception while converting to HMac SHA1"
}
}
private def byteArrayToString(byte[] data) {
BigInteger bigInteger = new BigInteger(1, data)
String hash = bigInteger.toString(16)
return hash
}
/*** Device Query/Update Functions ***/ /*** Device Query/Update Functions ***/
@@ -394,10 +334,10 @@ def getDevices() {
def deviceData = [] def deviceData = []
inputs?.each { inputs?.each {
def deviceType = getDeviceType(it) def deviceType = getDeviceType(it)
if (deviceType == "thermostat") { if(deviceType == "thermostat") {
deviceData << [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()] deviceData << [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()]
} else { } else {
deviceData << [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType)] deviceData << [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType)]
} }
} }
@@ -410,10 +350,10 @@ def getDevice() {
def it = findDevice(params.id) def it = findDevice(params.id)
def deviceType = getDeviceType(it) def deviceType = getDeviceType(it)
def device def device
if (deviceType == "thermostat") { if(deviceType == "thermostat") {
device = [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()] device = [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it,deviceType), locationMode: getLocationModeInfo()]
} else { } else {
device = [name: it.displayName, id: it.id, status: it.status, deviceType: deviceType, manufacturer: it.manufacturerName, model: it.modelName, attributes: deviceAttributeList(it, deviceType)] device = [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType)]
} }
log.debug "getDevice, return: ${device}" log.debug "getDevice, return: ${device}"
@@ -426,18 +366,18 @@ void updateDevice() {
request.JSON.each { request.JSON.each {
def command = it.key def command = it.key
def value = it.value def value = it.value
if (command) { if (command){
def commandList = mapDeviceCommands(command, value) def commandList = mapDeviceCommands(command, value)
command = commandList[0] command = commandList[0]
value = commandList[1] value = commandList[1]
if (command == "setAwayMode") { if (command == "setAwayMode") {
log.info "Setting away mode to ${value}" log.info "Setting away mode to ${value}"
if (location.modes?.find { it.name == value }) { if (location.modes?.find {it.name == value}) {
location.setMode(value) location.setMode(value)
} }
} else if (command == "thermostatSetpoint") { }else if (command == "thermostatSetpoint"){
switch (device.currentThermostatMode) { switch(device.currentThermostatMode){
case "cool": case "cool":
log.info "Update: ${device.displayName}, [${command}, ${value}]" log.info "Update: ${device.displayName}, [${command}, ${value}]"
device.setCoolingSetpoint(value) device.setCoolingSetpoint(value)
@@ -451,7 +391,7 @@ void updateDevice() {
httpError(501, "this mode: ${device.currentThermostatMode} does not allow changing thermostat setpoint.") httpError(501, "this mode: ${device.currentThermostatMode} does not allow changing thermostat setpoint.")
break break
} }
} else if (!device) { }else if (!device) {
log.error "updateDevice, Device not found" log.error "updateDevice, Device not found"
httpError(404, "Device not found") httpError(404, "Device not found")
} else if (!device.hasCommand(command)) { } else if (!device.hasCommand(command)) {
@@ -461,11 +401,11 @@ void updateDevice() {
if (command == "setColor") { if (command == "setColor") {
log.info "Update: ${device.displayName}, [${command}, ${value}]" log.info "Update: ${device.displayName}, [${command}, ${value}]"
device."$command"(hex: value) device."$command"(hex: value)
} else if (value.isNumber()) { } else if(value.isNumber()) {
def intValue = value as Integer def intValue = value as Integer
log.info "Update: ${device.displayName}, [${command}, ${intValue}(int)]" log.info "Update: ${device.displayName}, [${command}, ${intValue}(int)]"
device."$command"(intValue) device."$command"(intValue)
} else if (value) { } else if (value){
log.info "Update: ${device.displayName}, [${command}, ${value}]" log.info "Update: ${device.displayName}, [${command}, ${value}]"
device."$command"(value) device."$command"(value)
} else { } else {
@@ -492,16 +432,17 @@ private getDeviceType(device) {
log.debug "supported commands: [${device}, ${device.supportedCommands}]" log.debug "supported commands: [${device}, ${device.supportedCommands}]"
//Loop through the device capability list to determine the device type. //Loop through the device capability list to determine the device type.
capabilities.each { capability -> capabilities.each {capability ->
switch (capability.name.toLowerCase()) { switch(capability.name.toLowerCase())
{
case "switch": case "switch":
deviceType = "switch" deviceType = "switch"
//If the device also contains "Switch Level" capability, identify it as a "light" device. //If the device also contains "Switch Level" capability, identify it as a "light" device.
if (capabilities.any { it.name.toLowerCase() == "switch level" }) { if (capabilities.any{it.name.toLowerCase() == "switch level"}){
//If the device also contains "Power Meter" capability, identify it as a "dimmerSwitch" device. //If the device also contains "Power Meter" capability, identify it as a "dimmerSwitch" device.
if (capabilities.any { it.name.toLowerCase() == "power meter" }) { if (capabilities.any{it.name.toLowerCase() == "power meter"}){
deviceType = "dimmerSwitch" deviceType = "dimmerSwitch"
return deviceType return deviceType
} else { } else {
@@ -548,24 +489,24 @@ private deviceAttributeList(device, deviceType) {
allAttributes.each { attribute -> allAttributes.each { attribute ->
try { try {
def currentState = device.currentState(attribute.name) def currentState = device.currentState(attribute.name)
if (currentState != null) { if(currentState != null ){
switch (attribute.name) { switch(attribute.name){
case 'temperature': case 'temperature':
attributeList.putAll([(attribute.name): currentState.value, 'temperatureScale': location.temperatureScale]) attributeList.putAll([ (attribute.name): currentState.value, 'temperatureScale':location.temperatureScale ])
break; break;
default: default:
attributeList.putAll([(attribute.name): currentState.value]) attributeList.putAll([(attribute.name): currentState.value ])
break; break;
} }
if (deviceType == "genericSensor") { if( deviceType == "genericSensor" ){
def key = attribute.name + "_lastUpdated" def key = attribute.name + "_lastUpdated"
attributeList.putAll([(key): currentState.isoDate]) attributeList.putAll([ (key): currentState.isoDate ])
} }
} else { } else {
attributeList.putAll([(attribute.name): null]); attributeList.putAll([ (attribute.name): null ]);
} }
} catch (e) { } catch(e) {
attributeList.putAll([(attribute.name): null]); attributeList.putAll([ (attribute.name): null ]);
} }
} }
return attributeList return attributeList
@@ -638,7 +579,8 @@ private mapDeviceCommands(command, value) {
if (value == 1 || value == "1" || value == "lock") { if (value == 1 || value == "1" || value == "lock") {
resultCommand = "lock" resultCommand = "lock"
resultValue = "" resultValue = ""
} else if (value == 0 || value == "0" || value == "unlock") { }
else if (value == 0 || value == "0" || value == "unlock") {
resultCommand = "unlock" resultCommand = "unlock"
resultValue = "" resultValue = ""
} }
@@ -647,5 +589,5 @@ private mapDeviceCommands(command, value) {
break break
} }
return [resultCommand, resultValue] return [resultCommand,resultValue]
} }