Compare commits

..

1 Commits

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

View File

@@ -1,241 +0,0 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* SmartPower Outlet (CentraLite)
*
* Author: SmartThings
* Date: 2015-08-23
*/
metadata {
// Automatically generated. Make future change here.
definition (name: "Iris Smart Plug", namespace: "blebson", author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Power Meter"
capability "Energy Meter"
capability "Configuration"
capability "Refresh"
capability "Sensor"
// indicates that device keeps track of heartbeat (in state.heartbeat)
attribute "heartbeat", "string"
attribute "timerStart", "number"
attribute "energyDisplay", "string"
attribute "elapsedTimeDisplay", "string"
command "resetEnergyUsage"
fingerprint profileId: "0104", inClusters: "0000 0003 0004 0005 0006 0B04 0B05 FC03", outClusters: "0019", manufacturer: "CentraLite", model: "3210-L", deviceJoinName: "Outlet"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019"
}
// simulator metadata
simulator {
// status messages
status "on": "on/off: 1"
status "off": "on/off: 0"
// reply messages
reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0"
}
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS1.jpg",
"http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS2.jpg"
])
}
section("Reporting Intervals") {
input "intervalMin", "number", title: "Minimum interval between reports [s]", defaultValue: 5, range: "1..600"
input "intervalMax", "number", title: "Maximum interval between reports [s]", defaultValue: 600, range: "1..600"
}
}
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
}
tileAttribute ("power", key: "SECONDARY_CONTROL") {
attributeState "power", label:'${currentValue} W'
}
}
valueTile("energyDisplay", "device.energyDisplay", width: 5, height: 1, decoration: "flat") {
state "default", label:'Energy used: ${currentValue}', unit: "kWh"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("resetUsage", "command.resetEnergyUsage", decoration: "flat", width: 1, height: 1){
state "default", action: "resetEnergyUsage", label:'Reset kWh', icon:"st.Health & Wellness.health7"
}
valueTile("elapsedTimeDisplay", "device.elapsedTimeDisplay", decoration: "flat", width: 5, height: 1){
state "default", label: 'Time: ${currentValue}', unit: "h"
}
standardTile("configure", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
state "default", label:"", action:"configure", icon:"st.secondary.configure"
}
main "switch"
details(["switch","energyDisplay","resetUsage","power","elapsedTimeDisplay","refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
// save heartbeat (i.e. last time we got a message from device)
state.heartbeat = Calendar.getInstance().getTimeInMillis()
if (state.timerStart == null)
{
state.timerStart = Calendar.getInstance().getTimeInMillis()
state.energy = 0.0
}
def finalResult = zigbee.getKnownDescription(description)
//TODO: Remove this after getKnownDescription can parse it automatically
if (!finalResult && description!="updated")
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
if (finalResult) {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10
sendEvent(name: "power", value: powerValue, isStateChange: true, displayed: false) // note: stateChange = true added so the energy calculation can work properly
/*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with correct units needs to be handled in the device type
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
*/
calculateAndShowEnergy()
}
else {
sendEvent(name: finalResult.type, value: finalResult.value)
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
}
def calculateAndShowEnergy()
{
def recentEvents = device.statesSince("power", new Date()-1, [max: 2]).collect {[value: it.value as float, date: it.date]}
def deltaT = (recentEvents[0].date.getTime() - recentEvents[1].date.getTime()) // time since last "power" event in milliseconds
deltaT = deltaT / 3600000 // convert to hours
def energyValue = device.currentValue("energy")
if(energyValue != null) {
energyValue += (recentEvents[1].value * deltaT) / 1000 // energy used since last "power" event in kWh
}
sendEvent(name: "energy", value: energyValue, displayed: false)
sendEvent(name: "energyDisplay", value: String.format("%6.3f kWh",energyValue), displayed: false)
def currentTime = Calendar.getInstance().getTimeInMillis()
def timeDifference = ((long)currentTime - state.timerStart)/1000; // in seconds
int h = (int) (timeDifference / (3600));
int m = (int) ((timeDifference - (h * 3600)) / 60);
int s = (int) (timeDifference - (h * 3600) - m * 60);
int d = (int) h >= 24 ? h / 24 : 0
h = d > 0 ? h % 24 : h
sendEvent(name: "elapsedTimeDisplay", value: String.format("%dd %02d:%02d:%02d", d, h, m, s), displayed: false)
}
def off() {
zigbee.off()
}
def on() {
zigbee.on()
}
def resetEnergyUsage() {
sendEvent(name: "energy", value: 0.0, displayed: false)
state.timerStart = Calendar.getInstance().getTimeInMillis()
sendEvent(name: "energyDisplay", value: String.format("%6.3f kWh",0.0), displayed: false)
sendEvent(name: "elapsedTimeDisplay", value: String.format("%dd %02d:%02d:%02d", 0, 0, 0, 0), displayed: false)
}
def refresh() {
sendEvent(name: "heartbeat", value: "alive", displayed:false)
return zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
}
def configure() {
log.debug "Configuring..."
return zigbee.onOffConfig() + powerConfig() + refresh()
}
def updated() {
response(configure())
}
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
//min change in value is 01
def powerConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0B04 0x050B 0x29 ${intervalMin ?: 5} ${intervalMax ?: 600} {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
//TODO: Remove this after getKnownDescription can parse it automatically
def getPowerDescription(descMap) {
def powerValue = "undefined"
if (descMap.cluster == "0B04") {
if (descMap.attrId == "050b") {
if(descMap.value!="ffff")
powerValue = zigbee.convertHexToInt(descMap.value)
}
}
else if (descMap.clusterId == "0B04") {
if(descMap.command=="07"){
return [type: "update", value : "power (0B04) capability configured successfully"]
}
}
if (powerValue != "undefined"){
return [type: "power", value : powerValue]
}
else {
return [:]
}
}

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

@@ -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"
}