Compare commits

..

7 Commits

Author SHA1 Message Date
John Vanacore
a3b8dffadd MSA-1646: Gate/Garage opener 2016-12-10 07:49:27 -08:00
Vinay Rao
0a82077b24 Merge pull request #1519 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-12-07 14:15:16 -08:00
Vinay Rao
38ef9e5c77 Merge pull request #1517 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-12-07 12:10:01 -08:00
Vinay Rao
6a71615ca5 Merge pull request #1505 from varzac/sendevent-appengine-fix
DPROT-215 Don't use sendEvent for AppEngine parse events
2016-12-02 11:39:24 -08:00
Zach Varberg
9939591005 Don't use sendEvent for AppEngine parse events
With the changes made for
https://smartthings.atlassian.net/browse/DPROT-200 there were a few DTHs
that were using sendEvent to directly send events generated during
parse.  However, because using sendEvent didn't result in parse
returning an event AppEngine would send the description up to the cloud
DTH to be handled.  In some cases this resulted in multiple events for
the same device trigger.

This resolves: https://smartthings.atlassian.net/browse/DPROT-215
2016-12-01 14:52:24 -06:00
Vinay Rao
d7f2bc1d79 Merge pull request #1502 from bflorian/PROB-1426-life360-logging
PROB-1426 adding logging to Life360
2016-11-28 13:20:16 -08:00
bflorian
3c5d727d4c PROB-1426 adding logging to Life360 2016-11-28 16:00:42 -05:00
9 changed files with 551 additions and 342 deletions

View File

@@ -39,7 +39,7 @@ metadata {
}
def generatePresenceEvent(boolean present) {
log.debug "Here in generatePresenceEvent!"
log.info "Life360 generatePresenceEvent($present)"
def value = formatValue(present)
def linkText = getLinkText(device)
def descriptionText = formatDescriptionText(linkText, present)

View File

@@ -128,7 +128,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [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]}"

View File

@@ -132,7 +132,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [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]}"

View File

@@ -161,7 +161,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07) {
if(cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [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]}"
@@ -339,7 +339,7 @@ private Map getContactResult(value) {
log.debug "Contact: ${device.displayName} value = ${value}"
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
return [name: 'status', value: value, descriptionText: descriptionText, translatable: true]
}
private getAccelerationResult(numValue) {

View File

@@ -119,7 +119,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07){
if (cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [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]}"

View File

@@ -103,7 +103,7 @@ private Map parseCatchAllMessage(String description) {
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
resultMap = [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]}"

View File

@@ -0,0 +1,536 @@
/**
* MyQ Lite
*
* Copyright 2015 Jason Mok/Brian Beaird/Barry Burke
*
* 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.
*
* Last Updated : 06/14/2016
*
*/
definition(
name: "MyQ Lite",
namespace: "brbeaird",
author: "Jason Mok/Brian Beaird/Barry Burke",
description: "Integrate MyQ with Smartthings",
category: "SmartThings Labs",
iconUrl: "http://smartthings.copyninja.net/icons/MyQ@1x.png",
iconX2Url: "http://smartthings.copyninja.net/icons/MyQ@2x.png",
iconX3Url: "http://smartthings.copyninja.net/icons/MyQ@3x.png"
)
preferences {
page(name: "prefLogIn", title: "MyQ")
page(name: "prefListDevices", title: "MyQ")
page(name: "prefSensor1", title: "MyQ")
page(name: "prefSensor2", title: "MyQ")
page(name: "prefSensor3", title: "MyQ")
page(name: "prefSensor4", title: "MyQ")
}
/* Preferences */
def prefLogIn() {
def showUninstall = username != null && password != null
return dynamicPage(name: "prefLogIn", title: "Connect to MyQ", nextPage:"prefListDevices", uninstall:showUninstall, install: false) {
section("Login Credentials"){
input("username", "email", title: "Username", description: "MyQ Username (email address)")
input("password", "password", title: "Password", description: "MyQ password")
}
section("Gateway Brand"){
input(name: "brand", title: "Gateway Brand", type: "enum", metadata:[values:["Liftmaster","Chamberlain","Craftsman"]] )
}
}
}
def prefListDevices() {
if (forceLogin()) {
def doorList = getDoorList()
if ((doorList)) {
return dynamicPage(name: "prefListDevices", title: "Devices", nextPage:"prefSensor1", install:false, uninstall:true) {
if (doorList) {
section("Select which garage door/gate to use"){
input(name: "doors", type: "enum", required:false, multiple:true, metadata:[values:doorList])
}
}
}
} else {
def devList = getDeviceList()
return dynamicPage(name: "prefListDevices", title: "Error!", install:false, uninstall:true) {
section(""){
paragraph "Could not find any supported device(s). Please report to author about these devices: " + devList
}
}
}
} else {
return dynamicPage(name: "prefListDevices", title: "Error!", install:false, uninstall:true) {
section(""){
paragraph "The username or password you entered is incorrect. Try again. "
}
}
}
}
def prefSensor1() {
log.debug "Doors chosen: " + doors
//Set defaults
def nextPage = ""
def showInstall = true
def titleText = ""
//Determine if we have multiple doors and need to send to another page
if (doors instanceof String){ //simulator seems to just make a single door a string. For that reason we have this weird check.
log.debug "Single door detected (string)."
titleText = "Select Sensors for Door 1 (" + state.data[doors].name + ")"
}
else if (doors.size() == 1){
log.debug "Single door detected (array)."
titleText = "Select Sensors for Door 1 (" + state.data[doors[0]].name + ")"
}
else{
log.debug "Multiple doors detected."
nextPage = "prefSensor2"
titleText = "Select Sensors for Door 1 (" + state.data[doors[0]].name + ")"
showInstall = false;
}
return dynamicPage(name: "prefSensor1", title: "Sensors", nextPage:nextPage, install:showInstall, uninstall:true) {
section(titleText){
input(name: "door1Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: true, multiple: false)
input(name: "door1Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
}
}
}
def prefSensor2() {
def nextPage = ""
def showInstall = true
def titleText = "Sensors for Door 2 (" + state.data[doors[1]].name + ")"
if (doors.size() > 2){
nextPage = "prefSensor3"
showInstall = false;
}
return dynamicPage(name: "prefSensor2", title: "Sensors", nextPage:nextPage, install:showInstall, uninstall:true) {
section(titleText){
input(name: "door2Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: true, multiple: false)
input(name: "door2Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
}
}
}
def prefSensor3() {
def nextPage = ""
def showInstall = true
def titleText = "Sensors for Door 3 (" + state.data[doors[2]].name + ")"
if (doors.size() > 3){
nextPage = "prefSensor4"
showInstall = false;
}
return dynamicPage(name: "prefSensor3", title: "Sensors", nextPage:nextPage, install:showInstall, uninstall:true) {
section(titleText){
input(name: "door3Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: true, multiple: false)
input(name: "door3Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
}
}
}
def prefSensor4() {
def titleText = "Contact Sensor for Door 4 (" + state.data[doors[3]].name + ")"
return dynamicPage(name: "prefSensor4", title: "Sensors", install:true, uninstall:true) {
section(titleText){
input(name: "door4Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: "true", multiple: "false")
input(name: "door4Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
}
}
}
/* Initialization */
def installed() { initialize() }
def updated() {
unsubscribe()
initialize()
}
def uninstalled() {}
def initialize() {
login()
state.sensorMap = [:]
// Get initial device status in state.data
state.polling = [ last: 0, rescheduler: now() ]
state.data = [:]
// Create selected devices
def doorsList = getDoorList()
//def lightsList = getLightList()
def selectedDevices = [] + getSelectedDevices("doors")
selectedDevices.each {
log.debug "Creating child device: " + it
if (!getChildDevice(it)) {
if (it.contains("GarageDoorOpener")) { addChildDevice("brbeaird", "MyQ Garage Door Opener", it, null, ["name": "MyQLite: " + doorsList[it]]) }
}
}
// Remove unselected devices
def deleteDevices = (selectedDevices) ? (getChildDevices().findAll { !selectedDevices.contains(it.deviceNetworkId) }) : getAllChildDevices()
deleteDevices.each { deleteChildDevice(it.deviceNetworkId) }
//Create subscriptions
if (door1Sensor)
subscribe(door1Sensor, "contact", sensorHandler)
if (door2Sensor)
subscribe(door2Sensor, "contact", sensorHandler)
if (door3Sensor)
subscribe(door3Sensor, "contact", sensorHandler)
if (door4Sensor)
subscribe(door4Sensor, "contact", sensorHandler)
if (door1Acceleration)
subscribe(door1Acceleration, "acceleration", sensorHandler)
if (door2Acceleration)
subscribe(door2Acceleration, "acceleration", sensorHandler)
if (door3Acceleration)
subscribe(door3Acceleration, "acceleration", sensorHandler)
if (door4Acceleration)
subscribe(door4Acceleration, "acceleration", sensorHandler)
//Set initial values
syncDoorsWithSensors()
}
def syncDoorsWithSensors(child){
def firstDoor = doors[0]
//Handle single door (sometimes it's just a dumb string thanks to the simulator)
if (doors instanceof String)
firstDoor = doors
def doorDNI = null
if (child) { // refresh only the requesting door (makes things a bit more efficient if you have more than 1 door
doorDNI = child.device.deviceNetworkId
switch (doorDNI) {
case firstDoor:
updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, child)
break
case doors[1]:
updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, child)
break
case doors[2]:
updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, child)
break
case doors[3]:
updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, child)
}
} else { // refresh ALL the doors
if (firstDoor) updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, null)
if (doors[1]) updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, null)
if (doors[2]) updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, null)
if (doors[3]) updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, null)
}
}
def updateDoorStatus(doorDNI, sensor, acceleration, threeAxis, child){
//Get door to update and set the new value
def doorToUpdate = getChildDevice(doorDNI)
def doorName = state.data[doorDNI].name
def value = "unknown"
def moving = "unknown"
def door = doorToUpdate.latestValue("door")
if (acceleration) moving = acceleration.latestValue("acceleration")
if (sensor) value = sensor.latestValue("contact")
if (moving == "active") {
if (value == "open") {
if (door != "opening") value = "closing" else value = "opening" // if door is "open" or "waiting" change to "closing", else it must be "opening"
} else if (value == "closed") {
if (door != "closing") value = "opening" else value = "closed"
}
} else if (moving == "inactive") {
if (door == "closing") {
if (value == "open") { // just stopped but door is still open
value = "stopped"
}
}
}
doorToUpdate.updateDeviceStatus(value)
doorToUpdate.updateDeviceSensor("${sensor} is ${sensor.currentContact}")
log.debug "Door: " + doorName + ": Updating with status - " + value + " - from sensor " + sensor
//Write to child log if this was initiated from one of the doors
if (child)
child.log("Door: " + doorName + ": Updating with status - " + value + " - from sensor " + sensor)
if (acceleration) {
doorToUpdate.updateDeviceMoving("${acceleration} is ${moving}")
log.debug "Door: " + doorName + ": Updating with status - " + moving + " - from sensor " + acceleration
if (child)
child.log("Door: " + doorName + ": Updating with status - " + moving + " - from sensor " + acceleration)
}
//Get latest activity timestamp for the sensor (data saved for up to a week)
def eventsSinceYesterday = sensor.eventsSince(new Date() - 7)
def latestEvent = eventsSinceYesterday[0]?.date
def timeStampLogText = "Door: " + doorName + ": Updating timestamp to: " + latestEvent + " - from sensor " + sensor
if (!latestEvent) //If the door has been inactive for more than a week, timestamp data will be null. Keep current value in that case.
timeStampLogText = "Door: " + doorName + ": Null timestamp detected " + " - from sensor " + sensor + " . Keeping current value."
else
doorToUpdate.updateDeviceLastActivity(latestEvent)
log.debug timeStampLogText
//Write to child log if this was initiated from one of the doors
if (child)
child.log(timeStampLogText)
}
def refresh(child){
def door = child.device.deviceNetworkId
def doorName = state.data[door].name
child.log("refresh called from " + doorName + ' (' + door + ')')
syncDoorsWithSensors(child)
}
def sensorHandler(evt) {
log.debug "Sensor change detected: Event name " + evt.name + " value: " + evt.value + " deviceID: " + evt.deviceId
switch (evt.deviceId) {
case door1Sensor.id:
case door1Acceleration?.id:
def firstDoor = doors[0]
if (doors instanceof String) firstDoor = doors
updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, null)
break
case door2Sensor?.id:
case door2Acceleration?.id:
updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, null)
break
case door3Sensor?.id:
case door3Acceleration?.id:
updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, null)
break
case door4Sensor?.id:
case door4Acceleration?.id:
updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, null)
break
default:
syncDoorsWithSensors()
}
}
def getSelectedDevices( settingsName ) {
def selectedDevices = []
(!settings.get(settingsName))?:((settings.get(settingsName)?.getAt(0)?.size() > 1) ? settings.get(settingsName)?.each { selectedDevices.add(it) } : selectedDevices.add(settings.get(settingsName)))
return selectedDevices
}
/* Access Management */
private forceLogin() {
//Reset token and expiry
state.session = [ brandID: 0, brandName: settings.brand, securityToken: null, expiration: 0 ]
state.polling = [ last: 0, rescheduler: now() ]
state.data = [:]
return doLogin()
}
private login() { return (!(state.session.expiration > now())) ? doLogin() : true }
private doLogin() {
apiGet("/api/user/validate", [username: settings.username, password: settings.password] ) { response ->
log.debug "got login response: " + response
if (response.status == 200) {
if (response.data.SecurityToken != null) {
state.session.brandID = response.data.BrandId
state.session.brandName = response.data.BrandName
state.session.securityToken = response.data.SecurityToken
state.session.expiration = now() + 150000
return true
} else {
return false
}
} else {
return false
}
}
}
// Listing all the garage doors you have in MyQ
private getDoorList() {
def deviceList = [:]
apiGet("/api/v4/userdevicedetails/get", []) { response ->
if (response.status == 200) {
//log.debug "response data: " + response.data.Devices
//sendAlert("response data: " + response.data.Devices)
response.data.Devices.each { device ->
// 2 = garage door, 5 = gate, 7 = MyQGarage(no gateway), 17 = Garage Door Opener WGDO
if (device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7||device.MyQDeviceTypeId == 17) {
log.debug "Found door: " + device.MyQDeviceId
def dni = [ app.id, "GarageDoorOpener", device.MyQDeviceId ].join('|')
def description = ''
def doorState = ''
def updatedTime = ''
device.Attributes.each {
if (it.AttributeDisplayName=="desc") //deviceList[dni] = it.Value
{
description = it.Value
}
if (it.AttributeDisplayName=="doorstate") {
doorState = it.Value
updatedTime = it.UpdatedTime
}
}
//Ignore any doors with blank descriptions
if (description != ''){
log.debug "adding door: " + description + "type: " + device.MyQDeviceTypeId + " status: " + doorState
deviceList[dni] = description
state.data[dni] = [ status: doorState, lastAction: updatedTime, name: description ]
}
}
}
}
}
return deviceList
}
private getDeviceList() {
def deviceList = []
apiGet("/api/v4/userdevicedetails/get", []) { response ->
if (response.status == 200) {
response.data.Devices.each { device ->
log.debug "MyQDeviceTypeId : " + device.MyQDeviceTypeId.toString()
if (!(device.MyQDeviceTypeId == 1||device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 3||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7)) {
device.Attributes.each {
def description = ''
def doorState = ''
def updatedTime = ''
if (it.AttributeDisplayName=="desc") //deviceList[dni] = it.Value
description = it.Value
//Ignore any doors with blank descriptions
if (description != ''){
log.debug "found device: " + description
deviceList.add( device.MyQDeviceTypeId.toString() + "|" + device.TypeID )
}
}
}
}
}
}
return deviceList
}
/* api connection */
// get URL
private getApiURL() {
if (settings.brand == "Craftsman") {
return "https://craftexternal.myqdevice.com"
} else {
return "https://myqexternal.myqdevice.com"
}
}
private getApiAppID() {
if (settings.brand == "Craftsman") {
return "QH5AzY8MurrilYsbcG1f6eMTffMCm3cIEyZaSdK/TD/8SvlKAWUAmodIqa5VqVAs"
} else {
return "JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu"
}
}
// HTTP GET call
private apiGet(apiPath, apiQuery = [], callback = {}) {
// set up query
apiQuery = [ appId: getApiAppID() ] + apiQuery
if (state.session.securityToken) { apiQuery = apiQuery + [SecurityToken: state.session.securityToken ] }
try {
httpGet([ uri: getApiURL(), path: apiPath, query: apiQuery ]) { response -> callback(response) }
} catch (SocketException e) {
//sendAlert("API Error: $e")
log.debug "API Error: $e"
}
}
// HTTP PUT call
private apiPut(apiPath, apiBody = [], callback = {}) {
// set up body
apiBody = [ ApplicationId: getApiAppID() ] + apiBody
if (state.session.securityToken) { apiBody = apiBody + [SecurityToken: state.session.securityToken ] }
// set up query
def apiQuery = [ appId: getApiAppID() ]
if (state.session.securityToken) { apiQuery = apiQuery + [SecurityToken: state.session.securityToken ] }
try {
httpPut([ uri: getApiURL(), path: apiPath, contentType: "application/json; charset=utf-8", body: apiBody, query: apiQuery ]) { response -> callback(response) }
} catch (SocketException e) {
log.debug "API Error: $e"
}
}
// Get Device ID
def getChildDeviceID(child) {
return child.device.deviceNetworkId.split("\\|")[2]
}
// Get single device status
def getDeviceStatus(child) {
return state.data[child.device.deviceNetworkId].status
}
// Get single device last activity
def getDeviceLastActivity(child) {
return state.data[child.device.deviceNetworkId].lastAction.toLong()
}
// Send command to start or stop
def sendCommand(child, attributeName, attributeValue) {
if (login()) {
//Send command
apiPut("/api/v4/deviceattribute/putdeviceattribute", [ MyQDeviceId: getChildDeviceID(child), AttributeName: attributeName, AttributeValue: attributeValue ])
if ((attributeName == "desireddoorstate") && (attributeValue == 0)) { // if we are closing, check if we have an Acceleration sensor, if so, "waiting" until it moves
def firstDoor = doors[0]
if (doors instanceof String) firstDoor = doors
def doorDNI = child.device.deviceNetworkId
switch (doorDNI) {
case firstDoor:
if (door1Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("opening")
break
case doors[1]:
if (door2Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("opening")
break
case doors[2]:
if (door3Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("opening")
break
case doors[3]:
if (door4Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("opening")
break
}
}
return true
}
}

View File

@@ -1,327 +0,0 @@
/**
* Cubic Home
* sdfssdfsfsdfsdfdsf
* Copyright 2016 Nikolay Zenovkin
*
* 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: "Cubic Butler for Smart Home",
namespace: "cubicrobotics",
author: "Cubic Robotics",
description: "Hey, Im Cubic, AI butler for smart home! \n" +
"I am the one and only app you need to control an entire home. \n" +
"Right now I can help you to control your SmartThings devices via natural speech and dashboard.\n" +
"Speak naturally, dont learn robot! My A.I. is designed for a smart home control.\n" +
"- You dont have to remember exact commands and phrases.\n" +
"- I have memory, and every new request will be processed in the context of previous ones.\n" +
"- I can ask clarifying questions, if Im not sure what you mean.",
category: "Convenience",
iconUrl: "https://lh3.googleusercontent.com/GX5BRhaFq22HpAEU6tD4JXvizlxWFuB9zjyZE39-pLpZvQvvUmVpWXa0v4-oaxz4tg=w300-rw",
iconX2Url: "https://lh3.googleusercontent.com/GX5BRhaFq22HpAEU6tD4JXvizlxWFuB9zjyZE39-pLpZvQvvUmVpWXa0v4-oaxz4tg=w300-rw",
iconX3Url: "https://lh3.googleusercontent.com/GX5BRhaFq22HpAEU6tD4JXvizlxWFuB9zjyZE39-pLpZvQvvUmVpWXa0v4-oaxz4tg=w300-rw",
oauth: [displayName: "Cubic Home", displayLink: "http://cubic.ai/"])
preferences {
section("Welcome to Cubic") {
// TODO: put inputs here
}
section("Allow Cubic to control these switches") {
input "switches", "capability.switch", multiple: true, required: false
}
section("Allow Cubic to control these motion sensors") {
input "motionSensors", "capability.motionSensor", multiple: true, required: false
}
section("Allow Cubic to control these bulbs") {
input "lamps", "capability.colorControl", multiple: true, required: false
}
section("Allow Cubic to control these thermostats") {
input "thermostats", "capability.thermostat", multiple: true, required: false
}
section("Allow Cubic to control these water sensors") {
input "waterSensors", "capability.waterSensor", multiple: true, required: false
}
section("Allow Cubic to control these CO sensors") {
input "smokeSensors", "capability.carbonMonoxideDetector", multiple: true, required: false
}
section("Allow Cubic to control these door controls") {
input "doorControls", "capability.doorControl", multiple: true, required: false
}
}
mappings {
path("/devices") {
action:
[
GET: "getDevices"
]
}
path("/devices/:id") {
action:
[
PUT: "updateDevice",
GET: "getDevice"
]
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
subscribe(motionSensors, "motion", motionHandler)
subscribe(switches, "switch", switchHandler)
subscribe(thermostats, "thermostat.temperature", thermostatHandler)
subscribe(waterSensors, "waterSensor", waterHandler)
subscribe(smokeSensors, "carbonMonoxideDetector", smokeHandler)
subscribe(doorControls, "doorControl", doorControlHandler)
}
def doorControlHandler(evt) {
if (evt.isStateChanged()) {
sendPushEvent(evt.deviceId,
"DOOR_CONTROL_SMART_THINGS",
evt.value
)
}
}
def smokeHandler(evt) {
if (evt.isStateChanged()) {
sendPushEvent(evt.deviceId,
"SMOKE_CO_SENSOR_SMART_THINGS",
evt.value
)
}
}
def thermostatHandler(evt) {
if (evt.isStateChanged()) {
sendPushEvent(evt.deviceId,
"THERMOSTAT_SMART_THINGS",
evt.value
)
}
}
def waterHandler(evt) {
if (evt.isStateChanged()) {
sendPushEvent(evt.deviceId,
"WATER_SENSOR_SMART_THINGS",
evt.value
)
}
}
def motionHandler(evt) {
if ("active" == evt.value) {
sendPushEvent(evt.deviceId,
"MOTION_SENSOR_SMART_THINGS",
evt.value)
}
}
def switchHandler(evt) {
log.debug "$evt.deviceId switch changed state to $evt.value"
}
def getDevices() {
def resp = []
switches.each {
resp << extractSwitch(it)
}
motionSensors.each {
resp << extractMotionSensor(it)
}
lamps.each {
resp << extractLamp(it)
}
thermostats.each {
resp << extractThermostat(it)
}
return resp
}
def getDevice() {
def id = params.id
log.info "Getting device by id " + id
def device = findDeviceById(id);
if (device == null) {
httpError(400, "$id is not a valid id for switch specified")
} else if (device.type == "SWITCH_SMART_THINGS") {
return extractSwitch(device.device);
} else if (device.type == "THERMOSTAT_SMART_THINGS") {
return extractThermostat(device.device);
} else if (device.type == "LAMP_SMART_THINGS") {
return extractLamp(device.device);
} else if (device.type == "MOTION_SENSOR_SMART_THINGS") {
return extractMotionSensor(device.device);
}
return nil
}
void updateDevice() {
def id = params.id
def device = findDeviceById(id)
if (device == null) {
httpError(400, "$id is not a valid switch id")
} else if (device.type == "SWITCH_SMART_THINGS") {
changeSwitchState(device, request.JSON?.powered)
} else if (device.type == "THERMOSTAT_SMART_THINGS") {
def heating_setpoint = request.JSON?.heating_setpoint
def cooling_setpoint = request.JSON?.cooling_setpoint
def thermostat_setpoint = request.JSON?.thermostat_setpoint
if (heating_setpoint != null) {
device.device.setHeatingSetpoint(heating_setpoint)
}
if (cooling_setpoint != null) {
device.device.setCoolingSetpoint(cooling_setpoint)
}
//if (thermostat_setpoint != null) {
// device.device.setThermostatSetpoint(thermostat_setpoint)
//}
} else if (device.type == "LAMP_SMART_THINGS") {
def powered = request.JSON?.powered
def color_h = request.JSON?.color_h
def color_s = request.JSON?.color_s
def color_b = request.JSON?.color_b
def color_temperature = request.JSON?.color_temperature
if (powered != null) {
changeSwitchState(device, request.JSON?.powered)
}
if (color_h != null) {
device.device.setHue(color_h)
}
if (color_b != null) {
device.device.setLevel(color_b)
}
if (color_s != null) {
device.device.setSaturation(color_s)
}
if (color_temperature != null) {
device.device.setColorTemperature(color_temperature)
}
} else if (device.type == "MOTION_SENSOR_SMART_THINGS") {
httpError(400, "Unable to control motion sensor")
}
}
def findDeviceById(id) {
def device = switches.find { it.id == id }
def type = "SWITCH_SMART_THINGS"
if (device == null) {
device = thermostats.find { it.id == id }
type = "THERMOSTAT_SMART_THINGS"
}
if (device == null) {
device = lamps.find { it.id == id }
type = "LAMP_SMART_THINGS"
}
if (device == null) {
device = motionSensors.find { it.id == id }
type = "MOTION_SENSOR_SMART_THINGS"
}
if (device == null) {
return null;
}
return [device: device, type: type];
}
def extractSwitch(it) {
return [id: it.id, type: "SWITCH_SMART_THINGS", state: [name: it.displayName, powered: it.currentValue("switch") == "on" ? true : false]]
}
def extractMotionSensor(it) {
return [id: it.id, type: "MOTION_SENSOR_SMART_THINGS", state: [name: it.displayName, state: it.currentValue("motion")]]
}
def extractLamp(it) {
return [id: it.id, type: "LAMP_SMART_THINGS", state: [name : it.displayName,
color_h : it.currentValue("hue"), color_s: it.currentValue("saturation"), color_b: it.currentValue("level"),
color_temperature: it.currentValue("colorTemperature"), powered: it.currentValue("switch") == "on" ? true : false]]
}
def extractThermostat(it) {
return [id: it.id, type: "THERMOSTAT_SMART_THINGS", state: [name : it.displayName,
temperature : it.currentValue("temperature"),
heating_setpoint : it.currentValue("heatingSetpoint"),
cooling_setpoint : it.currentValue("coolingSetpoint"),
thermostat_setpoint : it.currentValue("thermostatSetpoint"),
thermostat_mode : it.currentValue("thermostatMode"),
thermostat_fan_mode : it.currentValue("thermostatFanMode"),
thermostat_operating_state: it.currentValue("thermostatOperatingState")]]
}
def changeSwitchState(device, powered) {
log.info "Updating device " + device.device.displayName + " to state $powered"
switch (powered) {
case "true":
device.device.on()
break
case "false":
device.device.off()
break
default:
httpError(400, "$powered is not a valid power state for switch specified")
}
}
def sendPushEvent(pushEvent) {
def params = [
uri : "https://intent-processor-stage.cubic.ai/api/v1/pushEvent",
headers : [
Authorization: "Bearer -gm-IOuQR3W2Gim8Tjwsuw",
Accept : "/"
],
body : pushEvent,
requestContentType: "application/json"
]
try {
httpPost(params) { resp ->
log.debug "response data: ${resp.data}"
log.debug "response contentType: ${resp.contentType}"
}
} catch (e) {
log.debug "something went wrong: $e"
}
}
def sendPushEvent(id, type, state) {
sendPushEvent(
[device_id : id,
device_type: type,
state : state]
)
}

View File

@@ -289,12 +289,12 @@ def initializeLife360Connection() {
state.life360AccessToken = result.data.access_token
return true;
}
log.debug "Response=${result.data}"
log.info "Life360 initializeLife360Connection, response=${result.data}"
return false;
}
catch (e) {
log.debug e
log.error "Life360 initializeLife360Connection, error: $e"
return false;
}
@@ -656,7 +656,7 @@ def generateInitialEvent (member, childDevice) {
try { // we are going to just ignore any errors
log.debug "Generate Initial Event for New Device for Member = ${member.id}"
log.info "Life360 generateInitialEvent($member, $childDevice)"
def place = state.places.find{it.id==settings.place}
@@ -677,6 +677,8 @@ def generateInitialEvent (member, childDevice) {
// log.debug "Distance Away = ${distanceAway}"
boolean isPresent = (distanceAway <= placeRadius)
log.info "Life360 generateInitialEvent, member: ($memberLatitude, $memberLongitude), place: ($placeLatitude, $placeLongitude), radius: $placeRadius, dist: $distanceAway, present: $isPresent"
// log.debug "External Id=${app.id}:${member.id}"
@@ -718,7 +720,7 @@ def haversine(lat1, lon1, lat2, lon2) {
def placeEventHandler() {
log.debug "In placeEventHandler method."
log.info "Life360 placeEventHandler: params=$params, settings.place=$settings.place"
// the POST to this end-point will look like:
// POST http://test.com/webhook?circleId=XXXX&placeId=XXXX&userId=XXXX&direction=arrive
@@ -729,8 +731,6 @@ def placeEventHandler() {
def direction = params?.direction
def timestamp = params?.timestamp
log.debug "Life360 Event: Circle: ${circleId}, Place: ${placeId}, User: ${userId}, Direction: ${direction}"
if (placeId == settings.place) {
def presenceState = (direction=="in")
@@ -745,10 +745,10 @@ def placeEventHandler() {
if (deviceWrapper) {
deviceWrapper.generatePresenceEvent(presenceState)
log.debug "Event raised on child device: ${externalId}"
log.debug "Life360 event raised on child device: ${externalId}"
}
else {
log.debug "Couldn't find child device associated with inbound Life360 event."
log.warn "Life360 couldn't find child device associated with inbound Life360 event."
}
}