Compare commits

..

1 Commits

Author SHA1 Message Date
Postalboy
bbae724d9a MSA-1588: my home monitoring 2016-11-15 06:00:09 -08:00
16 changed files with 489 additions and 445 deletions

View File

@@ -0,0 +1,360 @@
/**
* LANnouncer Alerter (Formerly LANdroid - but Google didn't like that much.)
*
* Requires the LANnouncer android app; https://play.google.com/store/apps/details?id=com.keybounce.lannouncer
* See http://www.keybounce.com/LANdroidHowTo/LANdroid.html for full downloads and instructions.
* SmartThings thread: https://community.smartthings.com/t/android-as-a-speech-alarm-device-released/30282/12
*
* Note: Only Siren and Strobe from the U.I. or Alarm capabilities default to continuous.
*
* Version 1.25 22 July 2016
*
*
* Copyright 2015-2016 Tony McNamara
*
* 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.
*
* To Do: Add string return to Image Capture attribute
*
*/
metadata {
definition (name: "LANnouncer Alerter", namespace: "KeyBounce", author: "Tony McNamara") {
capability "Alarm"
capability "Speech Synthesis"
capability "Notification"
capability "Tone"
capability "Image Capture"
attribute "LANdroidSMS","string"
/* Per http://docs.smartthings.com/en/latest/device-type-developers-guide/overview.html#actuator-and-sensor */
capability "Sensor"
capability "Actuator"
// Custom Commands
/** Retrieve image, formatted for SmartThings, from camera by name. */
command "chime"
command "doorbell"
command "ipCamSequence", ["number"]
command "retrieveAndWait", ["string"]
command "retrieveFirstAndWait"
command "retrieveSecondAndWait"
}
preferences {
input("DeviceLocalLan", "string", title:"Android IP Address", description:"Please enter your tablet's I.P. address", defaultValue:"" , required: false, displayDuringSetup: true)
input("DevicePort", "string", title:"Android Port", description:"Port the Android device listens on", defaultValue:"1035", required: false, displayDuringSetup: true)
input("ReplyOnEmpty", "bool", title:"Say Nothing", description:"When no speech is found, announce LANdroid? (Needed for the speech and notify tiles to work)", defaultValue: true, displayDuringSetup: true)
input("AlarmContinuous", "bool", title:"Continuous Alarm (vs 10 sec.)", description: "When on, the alarm will sound until Stop is issued.", defaultValue: false, displayDuringSetup: true)
}
simulator {
// reply messages
["strobe","siren","both","off"].each
{
reply "$it": "alarm:$it"
}
}
tiles {
standardTile("alarm", "device.alarm", width: 2, height: 2) {
state "off", label:'off', action:'alarm.both', icon:"st.alarm.alarm.alarm", backgroundColor:"#ffffff"
state "strobe", label:'strobe!', action:'alarm.off', icon:"st.Lighting.light11", backgroundColor:"#e86d13"
state "siren", label:'siren!', action:'alarm.off', icon:"st.alarm.alarm.alarm", backgroundColor:"#e86d13"
state "both", label:'alarm!', action:'alarm.off', icon:"st.alarm.alarm.alarm", backgroundColor:"#e86d13"
}
standardTile("strobe", "device.alarm", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"alarm.strobe", icon:"st.secondary.strobe"
}
standardTile("siren", "device.alarm", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"alarm.siren", icon:"st.secondary.siren"
}
standardTile("off", "device.alarm", inactiveLabel: false, decoration: "flat") {
state "default", label:'Off', action:"alarm.off", icon:"st.quirky.spotter.quirky-spotter-sound-off"
}
/* Apparently can't show image attributes on tiles. */
standardTile("take", "device.image", width: 1, height: 1, canChangeIcon: false, canChangeBackground: false)
{
state "take", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking"
state "taking", label:'Taking', action: "", icon: "st.camera.take-photo", backgroundColor: "#53a7c0"
state "image", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF", nextState:"taking"
}
standardTile("speak", "device.speech", inactiveLabel: false, decoration: "flat")
{
state "default", label:'Speak', action:"Speech Synthesis.speak", icon:"st.Electronics.electronics13"
}
standardTile("toast", "device.notification", inactiveLabel: false, decoration: "flat") {
state "default", label:'Notify', action:"notification.deviceNotification", icon:"st.Kids.kids1"
}
standardTile("beep", "device.tone", inactiveLabel: false, decoration: "flat") {
state "default", label:'Tone', action:"tone.beep", icon:"st.Entertainment.entertainment2"
}
carouselTile("cameraDetails", "device.image", width: 3, height: 2) { }
main (["alarm", "take"]);
details(["alarm","strobe","siren","off","speak", "take","toast","beep", "cameraDetails"]);
}
}
/** Generally matches TTSServer/app/build.gradle */
String getVersion() {return "24 built July 2016";}
/** Alarm Capability, Off.
* Turns off both the strobe and the alarm.
* Necessary when in continuous mode.
*/
def off() {
log.debug "Executing 'off'"
sendEvent(name:"alarm", value:"off")
def command="&ALARM=STOP&FLASH=STOP&"+getDoneString();
return sendCommands(command)
}
/** Alarm Capability: Strobe
* Flashes the camera light, if any.
*/
def strobe() {
log.debug "Executing 'strobe'"
// For illustration, switch to siren and sendEvent after.
def command= (AlarmContinuous?"&FLASH=CONTINUOUS&":"&FLASH=STROBE&")+getDoneString();
// def command=(AlarmContinuous?"&ALARM=SIREN:CONTINUOUS&":"&ALARM=SIREN&")+getDoneString();
def hubAction = sendCommands(command)
sendEvent(name:"alarm", value:"strobe")
return hubAction;
}
/** Alarm Capability: Siren
* Sounds the siren, either continuous or for a brief period, depending on setting.
* If continuous, Stop should be called later.
*/
def siren() {
log.debug "Executing 'siren'"
sendEvent(name:"alarm", value:"siren")
def command=(AlarmContinuous?"&ALARM=SIREN:CONTINUOUS&":"&ALARM=SIREN&")+getDoneString();
return sendCommands(command);
}
/** Tone Capability: Beep
* Sounds a short beep
*/
def beep() {
log.debug "Executing 'beep'"
def command="&ALARM=CHIME&"+getDoneString()
return sendCommands(command);
}
def both() {
log.debug "Executing 'both'"
sendEvent(name:"alarm", value:"both")
def command="&ALARM=ON&FLASH=ON&"+getDoneString()
if (AlarmContinuous)
{
command="&ALARM=SIREN:CONTINUOUS&FLASH=CONTINUOUS&"+getDoneString()
}
return sendCommands(command);
}
/** speechSynthesis Capability: Speak
*/
def speak(toSay) {
log.debug "Executing 'speak'"
if (!toSay?.trim()) {
if (ReplyOnEmpty) {
toSay = "LANnouncer Version ${version}"
}
}
if (toSay?.trim()) {
def command="&SPEAK="+toSay+"&"+getDoneString()
return sendCommands(command)
}
}
/** Notification capability: deviceNotification
*/
def deviceNotification(toToast) {
log.debug "Executing notification with "+toToast
if (!toToast?.trim()) {
if (ReplyOnEmpty) {
toToast = "LANnouncer Version ${version}";
}
}
if (toToast?.trim()) {
def command="&TOAST="+toToast+"&"+getDoneString()
return sendCommands(command)
}
}
def chime() {
log.debug "Executing 'chime'"
// TODO: handle 'siren' command
def command="&ALARM=CHIME&"+getDoneString()
return sendCommands(command)
}
def doorbell() {
log.debug "Executing 'doorbell'"
// TODO: handle 'siren' command
def command="&ALARM=DOORBELL&"+getDoneString()
return sendCommands(command)
}
def ipCamSequence(cameraNumber) {
def camera = (cameraNumber==1?"FIRST":"SECOND");
def command="&RETRIEVESEQ="+cameraNumber+"&"+getDoneString()
return sendIPCommand(command, true)
}
def retrieveFirstAndWait() {
retrieveAndWait("FIRST");
}
def retrieveSecondAndWait() {
retrieveAndWait("SECOND");
}
def retrieveAndWait(cameraName) {
log.info("Requesting image from camera ${cameraName}");
def command="&RETRIEVE="+cameraName+"&STSHRINK=TRUE&"+getDoneString()
return sendIPCommand(command, true)
}
def take() {
// This won't result in received file. Can't handle large or binaries in hub.
log.debug "Executing 'take'"
def command="&PHOTO=BACK&STSHRINK=TRUE&"+getDoneString()
return sendIPCommand(command, true)
}
/** Send to IP and to SMS as appropriate
* The caller MUST return the value, which is the hubAction.
* As of version 1.25, does not "send" the command so much as
* request that the calling service send it.
*/
private sendCommands(command) {
log.info "Command request: "+command
sendSMSCommand(command)
return sendIPCommand(command)
}
/** Prepares the hubAction to be executed.
* Pre-V25, this was executed in-line.
* Now it is returned, not executed, and must be returned up the calling chain.
*/
private sendIPCommand(commandString, sendToS3 = false) {
log.info "Sending command "+ commandString+" to "+DeviceLocalLan+":"+DevicePort
if (DeviceLocalLan?.trim()) {
def hosthex = convertIPtoHex(DeviceLocalLan)
def porthex = convertPortToHex(DevicePort)
device.deviceNetworkId = "$hosthex:$porthex"
def headers = [:]
headers.put("HOST", "$DeviceLocalLan:$DevicePort")
def method = "GET"
def hubAction = new physicalgraph.device.HubAction(
method: method,
path: "/"+commandString,
headers: headers
);
if (sendToS3 == true)
{
hubAction.options = [outputMsgToS3:true];
}
log.debug hubAction
return hubAction;
}
}
private sendSMSCommand(commandString) {
def preface = "+@TTSSMS@+"
def smsValue = preface+"&"+commandString
state.lastsmscommand = smsValue
sendEvent(name: "LANdroidSMS", value: smsValue, isStateChange: true)
/*
if (SMSPhone?.trim()) {
sendSmsMessage(SMSPhone, preface+"&"+commandString)
}
*/
}
private String getDoneString() {
return "@DONE@"
}
def parse(String description) {
log.debug "Parsing '${description}'"
def map = parseLanMessage(description);
log.debug "As LAN: " + map;
if ((map.headers) && (map.headers.'Content-Type' != null) && (map.headers.'Content-Type'.contains("image/jpeg")) )
{ // Store the file
if(map.body)
{
storeImage(getPictureName(), map.body);
}
}
/* 'index:0F, mac:0073E023A13A, ip:C0A80114, port:040B, requestId:f9036fb2-9637-40b8-b2c5-71ba5a09fd3e, bucket:smartthings-device-conn-temp, key:fc8e3dfd-5035-40a2-8adc-a312926f9034' */
else if (map.bucket && map.key)
{ // S3 pointer; retrieve image from it to store.
try {
def s3ObjectContent; // Needed for scope of try-catch
def imageBytes = getS3Object(map.bucket, map.key + ".jpg")
if(imageBytes)
{
log.info ("Got image bytes; saving them.")
s3ObjectContent = imageBytes.getObjectContent()
def bytes = new ByteArrayInputStream(s3ObjectContent.bytes)
storeImage(getPictureName(), bytes)
}
}
catch(Exception e)
{
log.error e
}
finally {
//explicitly close the stream
if (s3ObjectContent) { s3ObjectContent.close() }
}
}
}
// Image Capture handling
/* Note that images are stored in https://graph.api.smartthings.com/api/s3/smartthings-smartsense-camera/[IMAGE-ID4],
* where [IMAGE-ID] is listed in the IDE under My Devices > [Camera] > Current States: Image. That page is updated as pictures are taken.
*/
private getPictureName() {
def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')
log.debug pictureUuid
def picName = device.deviceNetworkId.replaceAll(':', '') + "_$pictureUuid" + ".jpg"
return picName
}
private String convertIPtoHex(ipAddress) {
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02X', it.toInteger() ) }.join()
log.debug "IP address entered is $ipAddress and the converted hex code is $hex"
return hex
}
private String convertPortToHex(port) {
String hexport = port.toString().format( '%04X', port.toInteger() )
log.debug hexport
return hexport
}

View File

@@ -120,15 +120,6 @@ def configure() {
return cmd
}
def installed() {
initialize()
}
def updated() {
initialize()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 4)
}

View File

@@ -109,15 +109,6 @@ def configure() {
return cmds
}
def installed() {
initialize()
}
def updated() {
initialize()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 4)
}

View File

@@ -27,7 +27,7 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Music Player"
capability "Health Check"
capability "Polling"
/**
* Define all commands, ie, if you have a custom action not
@@ -236,33 +236,7 @@ def parse(String event) {
* @return action(s) to take or null
*/
def installed() {
// Notify health check about this device with timeout interval 12 minutes
sendEvent(name: "checkInterval", value: 12 * 60, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
startPoll()
}
/**
* Called by health check if no events been generated in the last 12 minutes
* If device doesn't respond it will be marked offline (not available)
*/
def ping() {
TRACE("ping")
boseSendGetNowPlaying()
}
/**
* Schedule a 2 minute poll of the device to refresh the
* tiles so the user gets the correct information.
*/
def startPoll() {
TRACE("startPoll")
unschedule()
// Schedule 2 minute polling of speaker status (song average length is 3-4 minutes)
def sec = Math.round(Math.floor(Math.random() * 60))
//def cron = "$sec 0/5 * * * ?" // every 5 min
def cron = "$sec 0/2 * * * ?" // every 2 min
log.debug "schedule('$cron', boseSendGetNowPlaying)"
schedule(cron, boseSendGetNowPlaying)
onAction("refresh")
}
/**
@@ -342,6 +316,14 @@ def onAction(String user, data=null) {
return actions
}
/**
* Called every so often (every 5 minutes actually) to refresh the
* tiles so the user gets the correct information.
*/
def poll() {
return boseRefreshNowPlaying()
}
/**
* Joins this speaker into the everywhere zone
*/
@@ -855,10 +837,6 @@ def boseRefreshNowPlaying(delay=0) {
return boseGET("/now_playing")
}
def boseSendGetNowPlaying() {
sendHubCommand(boseGET("/now_playing"))
}
/**
* Requests the list of presets
*
@@ -1036,8 +1014,4 @@ def boseGetDeviceID() {
*/
def getDeviceIP() {
return parent.resolveDNI2Address(device.deviceNetworkId)
}
def TRACE(text) {
log.trace "${text}"
}

View File

@@ -183,15 +183,6 @@ def updateState(String name, String value) {
device.updateDataValue(name, value)
}
def installed() {
initialize()
}
def updated() {
initialize()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 3)
}

View File

@@ -125,7 +125,7 @@ metadata {
void installed() {
// The device refreshes every 5 minutes by default so if we miss 2 refreshes we can consider it offline
// Using 12 minutes because in testing, device health team found that there could be "jitter"
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "cloud"], displayed: false)
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "cloud", hubHardwareId: device.hub.hardwareID], displayed: false)
}
// Device Watch will ping the device to proactively determine if the device has gone offline

View File

@@ -71,7 +71,7 @@ def parse(String description) {
def event = [:]
def finalResult = isKnownDescription(description)
if (finalResult) {
if (finalResult != "false") {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
@@ -212,16 +212,13 @@ def isKnownDescription(description) {
else if (descMap.cluster == "0B04" || descMap.clusterId == "0B04"){
isDescriptionPower(descMap)
}
else {
return [:]
}
}
else if(description?.startsWith("on/off:")) {
def switchValue = description?.endsWith("1") ? "on" : "off"
return [type: "switch", value : switchValue]
}
else {
return [:]
return "false"
}
}
@@ -255,7 +252,7 @@ def isDescriptionOnOff(descMap) {
return [type: "switch", value : switchValue]
}
else {
return [:]
return "false"
}
}
@@ -282,9 +279,10 @@ def isDescriptionLevel(descMap) {
if (dimmerValue != -1){
return [type: "level", value : dimmerValue]
}
else {
return [:]
return "false"
}
}
@@ -306,7 +304,7 @@ def isDescriptionPower(descMap) {
return [type: "power", value : powerValue]
}
else {
return [:]
return "false"
}
}

View File

@@ -57,7 +57,6 @@ def parse(String description) {
private Map parseBasicMessage(description) {
def name = parseName(description)
def results = [:]
if (name != null) {
def value = parseValue(description)
def linkText = getLinkText(device)
@@ -65,7 +64,7 @@ private Map parseBasicMessage(description) {
def handlerName = value
def isStateChange = isStateChange(device, name, value)
results = [
def results = [
name : name,
value : value,
linkText : linkText,
@@ -74,6 +73,8 @@ private Map parseBasicMessage(description) {
isStateChange : isStateChange,
displayed : displayed(description, isStateChange)
]
} else {
results = [:]
}
log.debug "Parse returned $results.descriptionText"
return results

View File

@@ -126,15 +126,6 @@ private hold(button) {
sendEvent(name: "button", value: "held", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was held", isStateChange: true)
}
def installed() {
initialize()
}
def updated() {
initialize()
}
def initialize() {
sendEvent(name: "numberOfButtons", value: 4)
}

View File

@@ -23,11 +23,9 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart A19 Soft White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart BR30 Soft White"
}
tiles(scale: 2) {

View File

@@ -28,8 +28,8 @@ metadata {
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB", deviceJoinName: "SYLVANIA Smart Gardenspot mini RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB", deviceJoinName: "SYLVANIA Smart Gardenspot mini RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB", deviceJoinName: "OSRAM LIGHTIFY Gardenspot mini RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB", deviceJoinName: "OSRAM LIGHTIFY Gardenspot mini RGB"
}
// UI tile definitions

View File

@@ -32,12 +32,11 @@ metadata {
attribute "colorName", "string"
command "setGenericName"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "SYLVANIA Smart Flex RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Flex RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "SYLVANIA Smart A19 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "SYLVANIA Smart BR30 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "SYLVANIA Smart RT5/6 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY FLEX OUTDOOR RGBW", deviceJoinName: "SYLVANIA Smart Outdoor RGBW Flex"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED A19 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "OSRAM LIGHTIFY LED BR30 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "OSRAM LIGHTIFY LED RT 5/6 RGBW"
}
// UI tile definitions

View File

@@ -32,12 +32,11 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "SYLVANIA Smart BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "SYLVANIA Smart RT5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "SYLVANIA Smart A19 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY RT5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A19NAE26", deviceJoinName: "Sengled Element plus"
}
// UI tile definitions
@@ -84,24 +83,105 @@ def parse(String description) {
}
}
else {
def cluster = zigbee.parse(description)
Map bindingTable = parseBindingTableResponse(description)
if (bindingTable) {
List<String> cmds = []
bindingTable.table_entries.inject(cmds) { acc, entry ->
// The binding entry is not for our hub and should be deleted
if (entry["dstAddr"] != zigbeeEui) {
acc.addAll(removeBinding(entry.clusterId, entry.srcAddr, entry.srcEndpoint, entry.dstAddr, entry.dstEndpoint))
}
acc
}
// There are more entries that we haven't examined yet
if (bindingTable.numTableEntries > bindingTable.startIndex + bindingTable.numEntriesReturned) {
def startPos
if (cmds) {
log.warn "Removing binding entries for other devices: $cmds"
// Since we are removing some entries, we should start in the same spot as we just read since values
// will fill in the newly vacated spots
startPos = bindingTable.startIndex
} else {
// Since we aren't removing anything we move forward to the next set of table entries
startPos = bindingTable.startIndex + bindingTable.numEntriesReturned
}
cmds.addAll(requestBindingTable(startPos))
}
sendHubCommand(cmds.collect { it ->
new physicalgraph.device.HubAction(it)
}, 2000)
} else {
def cluster = zigbee.parse(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
}
}
def parseBindingTableResponse(description) {
Map descMap = zigbee.parseDescriptionAsMap(description)
if (descMap["clusterInt"] == 0x8033) {
def header_field_lengths = ["transactionSeqNo": 1, "status": 1, "numTableEntries": 1, "startIndex": 1, "numEntriesReturned": 1]
def field_values = [:]
def data = descMap["data"]
header_field_lengths.each { k, v ->
field_values[k] = Integer.parseInt(data.take(v).join(""), 16);
data = data.drop(v);
}
List<Map> table = []
if (field_values.numEntriesReturned) {
def table_entry_lengths = ["srcAddr": 8, "srcEndpoint": 1, "clusterId": 2, "dstAddrMode": 1]
for (def i : 0..(field_values.numEntriesReturned - 1)) {
def entryMap = [:]
table_entry_lengths.each { k, v ->
def val = data.take(v).reverse().join("")
entryMap[k] = val.length() < 8 ? Integer.parseInt(val, 16) : val
data = data.drop(v)
}
switch (entryMap.dstAddrMode) {
case 0x01:
entryMap["dstAddr"] = data.take(2).reverse().join("")
data = data.drop(2)
break
case 0x03:
entryMap["dstAddr"] = data.take(8).reverse().join("")
data = data.drop(8)
entryMap["dstEndpoint"] = Integer.parseInt(data.take(1).join(""), 16)
data = data.drop(1)
break
}
table << entryMap
}
}
field_values["table_entries"] = table
return field_values
}
return [:]
}
def requestBindingTable(startPos=0) {
return ["zdo mgmt-bind 0x${zigbee.deviceNetworkId} $startPos"]
}
def removeBinding(cluster, srcAddr, srcEndpoint, destAddr, destEndpoint) {
return ["zdo unbind unicast 0x${zigbee.deviceNetworkId} {${srcAddr}} $srcEndpoint $cluster {${destAddr}} $destEndpoint"]
}
def off() {
zigbee.off()
}
@@ -131,8 +211,7 @@ def configure() {
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh()
refresh() + requestBindingTable(0) + ["delay 2000"]
}
def setColorTemperature(value) {

View File

@@ -127,8 +127,8 @@ def configureHealthCheck() {
def configure() {
log.debug "configure()"
configureHealthCheck()
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
configureHealthCheck()
}
def updated() {

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

@@ -19,9 +19,9 @@
author: "SmartThings",
description: "Control your Bose SoundTouch speakers",
category: "SmartThings Labs",
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png",
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",
singleInstance: true
)
@@ -104,7 +104,7 @@ def deviceDiscovery()
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your ${getDeviceName()}. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices, submitOnChange: true
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices
}
}
}
@@ -196,8 +196,6 @@ def addDevice(){
d = addChildDevice(getNameSpace(), getDeviceName(), dni, newDevice?.value.hub, [label:"${deviceName}"])
d.boseSetDeviceID(newDevice.value.deviceID)
log.trace "Created ${d.displayName} with id $dni"
// sync DTH with device, done here as it currently don't work from the DTH's installed() method
d.refresh()
} else {
log.trace "${d.displayName} with id $dni already exists"
}