X-Git-Url: http://plrg.eecs.uci.edu/git/?p=smartthings-infrastructure.git;a=blobdiff_plain;f=main.groovy;h=953f33f858966b54ff90e94eff5fe23214359b26;hp=a1474f1fcd60a238b457107787b61fcc76bf007f;hb=d3802bd96ca8963ae52b9740443f9be6896f0e18;hpb=fea13f011a64a64558ad697eee68e3927395cccd diff --git a/main.groovy b/main.groovy index a1474f1..953f33f 100644 --- a/main.groovy +++ b/main.groovy @@ -70,7 +70,6 @@ import Valve.Valves import MobilePresence.MobilePresence import MobilePresence.MobilePresences import Event.Event -import AtomicState.AtomicState import Timer.SimulatedTimer //JPF's Verify API @@ -92,14 +91,24 @@ def eventHandler(LinkedHashMap eventDataMap) { for (int i = 0;i < app2.eventList.size();i++) { if (app2.eventList[i] == name) { def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data) - app2.functionList[i](event) + if (app2.functionList[i] instanceof String) { + String toCall = app2.functionList[i] + app2."$toCall"(event) + } + else + app2.functionList[i](event) } } for (int i = 0;i < app1.eventList.size();i++) { if (app1.eventList[i] == name) { def event = new Event(value, name, deviceId, descriptionText, displayed, linkText, linkText, isStateChange, unit, data) - app1.functionList[i](event) + if (app1.functionList[i] instanceof String) { + String toCall = app1.functionList[i] + app1."$toCall"(event) + } + else + app1.functionList[i](event) } } } @@ -115,8 +124,6 @@ def eventHandler(LinkedHashMap eventDataMap) { @Field def appObject = new Touched(sendEvent, 0) //Create a global list for events //@Field def evt = [] -//Global Object for class AtomicState! -@Field def atomicState = new AtomicState() //Global Object for class Touch Sensor! @Field def touchSensorObject = new NfcTouch(sendEvent, 1) //Global Object for class switch! @@ -185,45 +192,51 @@ class App1 { def reference def location def app - def atomicState //Extracted objects for App1 - //Object for class lock! - def lock1 - //Object for class contactSensor! - def contact - //Global variable for number! - def minutesLater = 70 - //Global variable for number! - def secondsLater = 93 - //Global variable for contact! - def recipients = "AJ" - //Global variable for phone! - def phoneNumber = 9495379373 + //Object for class color control! + def master + //Object for class color control! + def slaves + //Global variable for boolean! + def randomYes = "1" //Extracted objects for functions for App1 //Global Object for functions in subscribe method! + def mainPage = this.&mainPage + //Global Object for functions in subscribe method! def installed = this.&installed //Global Object for functions in subscribe method! def updated = this.&updated //Global Object for functions in subscribe method! - def initialize = this.&initialize + def init = this.&init + //Global Object for functions in subscribe method! + def onOffHandler = this.&onOffHandler + //Global Object for functions in subscribe method! + def colorHandler = this.&colorHandler + //Global Object for functions in subscribe method! + def getRandomColorMaster = this.&getRandomColorMaster + //Global Object for functions in subscribe method! + def tempHandler = this.&tempHandler + //Global Object for functions in subscribe method! + def textAppName = this.&textAppName + //Global Object for functions in subscribe method! + def textVersion = this.&textVersion //Global Object for functions in subscribe method! - def lockDoor = this.&lockDoor + def textCopyright = this.&textCopyright //Global Object for functions in subscribe method! - def unlockDoor = this.&unlockDoor + def textLicense = this.&textLicense //Global Object for functions in subscribe method! - def doorHandler = this.&doorHandler + def textHelp = this.&textHelp App1(Object obj) { reference = obj location = obj.locationObject app = obj.appObject - atomicState = obj.atomicState - lock1 = obj.lockObject - contact = obj.contactObject + master = obj.colorControlObject + slaves = obj.colorControlObject //Global variable for settings! - settings = [app:app, lock1:lock1, contact:contact, minutesLater:minutesLater, secondsLater:secondsLater, recipients:recipients, phoneNumber:phoneNumber] + settings = [app: app, master: master, slaves: slaves, randomYes: randomYes, END: "END"] } //Global variables for each app //Global variable for state[mode] @@ -244,11 +257,16 @@ class App1 { def settings //Zip code def zipCode = 92617 + //atomicState variable + def atomicState = [version: "1.01"] //Methods ///////////////////////////////////////////////////////////////////// def setLocationMode(String mode) { - location.mode = mode + location.setValue([name: "Location", value: "$mode", deviceId: "locationID0", descriptionText: "", + displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) + location.setValue([name: "mode", value: "$mode", deviceId: "locationID0", descriptionText: "", + displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) } ///////////////////////////////////////////////////////////////////// @@ -270,6 +288,12 @@ class App1 { eventList.add(event) functionList.add(FunctionToCall) } + ////subscribe(obj, event, nameOfFunc) + def subscribe(Object obj, String event, String nameOfFunction) { + objectList.add(obj) + eventList.add(event) + functionList.add(nameOfFunction) + } ////subscribe(obj, event, func, data) def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) { objectList.add(obj) @@ -315,6 +339,17 @@ class App1 { } } + def unschedule(String nameOfFunctionToUnschedule) { + for (int i = 0;i < timersFuncList.size();i++) { + if (timersFuncList[i] instanceof String) { + if (timersFuncList[i] == nameOfFunctionToUnschedule) { + if (timersList != null) + timersList[i].cancel() + } + } + } + } + def unschedule() { for (int i = 0;i < timersFuncList.size();i++) { @@ -333,6 +368,16 @@ class App1 { } } } + + def sendNotificationToContacts(String text, String recipients, LinkedHashMap metaData) { + for (int i = 0;i < recipients.size();i++) { + for (int j = 0;j < location.contacts.size();j++) { + if (recipients[i] == location.contacts[j]) { + println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString()) + } + } + } + } ///////////////////////////////////////////////////////////////////// ////sendSms(phone, text) def sendSms(long phoneNumber, String text) { @@ -435,92 +480,161 @@ class App1 { def canSchedule() { return true } + ///////////////////////////////////////////////////////////////////// + def createAccessToken() { + state.accessToken = "accessToken" + return state.accessToken + } + ///////////////////////////////////////////////////////////////////// + def runOnce(Date date, Closure methodToCall) { + methodTocall() + } - def installed(){ - initialize() + def mainPage() { + section("Master Light") { + input "master", "capability.colorControl", title: "Colored Light", required: true + } + section("Lights that follow the master settings") { + input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: true, submitOnChange: true + } + section([mobileOnly:true], "Options") { + input "randomYes", "bool",title: "When Master Turned On, Randomize Color", defaultValue: false + href "pageAbout", title: "About ${textAppName()}", description: "Tap to get application version, license and instructions" + } + + dynamicPage(name: "mainPage", title: "", install: true, uninstall: false) { + def masterInList = slaves?.id?.find{it==master?.id} + if (masterInList) { + section ("**WARNING**"){ + paragraph "You have included the Master Light in the Slave Group. This will cause a loop in execution. Please remove this device from the Slave Group.", image: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/img/caution.png" + } + } + + page(name: "pageAbout", title: "About ${textAppName()}", uninstall: true) { + section { + paragraph "${textVersion()}\n${textCopyright()}\n\n${textLicense()}\n" + } + section("Instructions") { + paragraph textHelp() + } + section("Tap button below to remove application"){ + } + } + + } + } + + def installed() { + init() } def updated(){ - unsubscribe() - unschedule() - initialize() + unsubscribe() + init() + } + + def init() { + subscribe(master, "switch", onOffHandler) + subscribe(master, "level", colorHandler) + subscribe(master, "hue", colorHandler) + subscribe(master, "saturation", colorHandler) + subscribe(master, "colorTemperature", tempHandler) + } + //----------------------------------- + def onOffHandler(evt){ + if (slaves && master) { + if (!slaves?.id.find{it==master?.id}){ + if (master?.currentValue("switch") == "on"){ + if (randomYes) getRandomColorMaster() + else slaves?.on() + } + else { + slaves?.off() + } + } + } } - def initialize(){ - log.debug "Settings: ${settings}" - subscribe(lock1, "lock", doorHandler, [filterEvents: false]) - subscribe(lock1, "unlock", doorHandler, [filterEvents: false]) - subscribe(contact, "contact.open", doorHandler) - subscribe(contact, "contact.closed", doorHandler) + def colorHandler(evt) { + if (slaves && master) { + if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){ + log.debug "Changing Slave units H,S,L" + def dimLevel = master?.currentValue("level") + def hueLevel = master?.currentValue("hue") + def saturationLevel = master.currentValue("saturation") + def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer] + slaves?.setColor(newValue) + try { + log.debug "Changing Slave color temp" + def tempLevel = master?.currentValue("colorTemperature") + slaves?.setColorTemperature(tempLevel) + } + catch (e){ + log.debug "Color temp for master --" + } + } + } } - def lockDoor(){ - log.debug "Locking the door." - lock1.lock() - if(location.contactBookEnabled) { - if ( recipients ) { - log.debug ( "Sending Push Notification..." ) - sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients) - } - } - if (phoneNumber) { - log.debug("Sending text message...") - sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!") - } + def getRandomColorMaster(){ + def hueLevel = Math.floor(Math.random() *1000) + def saturationLevel = Math.floor(Math.random() * 100) + def dimLevel = master?.currentValue("level") + def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer] + log.debug hueLevel + log.debug saturationLevel + master.setColor(newValue) + slaves?.setColor(newValue) + } + + def tempHandler(evt){ + if (slaves && master) { + if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){ + if (evt.value != "--") { + log.debug "Changing Slave color temp based on Master change" + def tempLevel = master.currentValue("colorTemperature") + slaves?.setColorTemperature(tempLevel) + } + } + } } - def unlockDoor(){ - log.debug "Unlocking the door." - lock1.unlock() - if(location.contactBookEnabled) { - if ( recipients ) { - log.debug ( "Sending Push Notification..." ) - sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients) - } - } - if ( phoneNumber ) { - log.debug("Sending text message...") - sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!") - } + //Version/Copyright/Information/Help + + private def textAppName() { + def text = "Color Coordinator" + } + + private def textVersion() { + def text = "Version 1.1.1 (12/13/2016)" } - def doorHandler(evt){ - if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then... - //def delay = (secondsLater) // runIn uses seconds - runIn( secondsLater, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged. - } - else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then... - unschedule( unlockDoor ) // ...we don't need to unlock it later. - } - else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then... - unschedule( lockDoor ) // ...we don't need to lock it later. - } - else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then... - //def delay = (minutesLater * 60) // runIn uses seconds - runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock. - } - else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door... - unschedule( lockDoor ) // ...we don't need to lock it later. - } - else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door... - //def delay = (minutesLater * 60) // runIn uses seconds - runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock. - } - else { //Opening or Closing door when locked (in case you have a handle lock) - log.debug "Unlocking the door." - lock1.unlock() - if(location.contactBookEnabled) { - if ( recipients ) { - log.debug ( "Sending Push Notification..." ) - sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients) - } - } - if ( phoneNumber ) { - log.debug("Sending text message...") - sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!") - } - } + private def textCopyright() { + def text = "Copyright © 2016 Michael Struck" + } + + private def textLicense() { + def text = + "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"+ + "\n\n"+ + " http://www.apache.org/licenses/LICENSE-2.0"+ + "\n\n"+ + "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." + } + + private def textHelp() { + def text = + "This application will allow you to control the settings of multiple colored lights with one control. " + + "Simply choose a master control light, and then choose the lights that will follow the settings of the master, "+ + "including on/off conditions, hue, saturation, level and color temperature. Also includes a random color feature." } + } @@ -529,99 +643,45 @@ class App2 { def reference def location def app - def atomicState //Extracted objects for App2 + //Object for class contactSensor! + def deviceContactSensor //Global variable for time! - def starting = "15:00" - //Global variable for time! - def ending = "15:00" - //Object for class beacon sensor! - def beacons - //Object for class mobile presence! - def phones - //Global variable for enum! - def arrivalPhrase = "Good Night!" - //Object for class switch! - def arrivalOnSwitches - //Object for class switch! - def arrivalOffSwitches - //Object for class lock! - def arrivalLocks - //Global variable for enum! - def departPhrase = "Good Night!" - //Object for class switch! - def departOnSwitches - //Object for class switch! - def departOffSwitches - //Object for class lock! - def departLocks - //Global variable for boolean! - def pushNotification = "0" - //Global variable for phone! - def phone = 9495379373 - //Global variable for enum! - def days = "Monday" - //Global variable for mode! - def modes = "night" + def reminderTime = "15:00" + //Object for class color control! + def deviceLight //Extracted objects for functions for App2 //Global Object for functions in subscribe method! - def mainPage = this.&mainPage - //Global Object for functions in subscribe method! def installed = this.&installed //Global Object for functions in subscribe method! def updated = this.&updated //Global Object for functions in subscribe method! def initialize = this.&initialize //Global Object for functions in subscribe method! - def beaconHandler = this.&beaconHandler - //Global Object for functions in subscribe method! - def arriveActions = this.&arriveActions - //Global Object for functions in subscribe method! - def departActions = this.&departActions - //Global Object for functions in subscribe method! - def prefix = this.&prefix - //Global Object for functions in subscribe method! - def listPhrases = this.&listPhrases - //Global Object for functions in subscribe method! - def executePhrase = this.&executePhrase + def contactHandler = this.&contactHandler //Global Object for functions in subscribe method! - def getBeaconName = this.&getBeaconName + def checkOpenDrawInPast = this.&checkOpenDrawInPast //Global Object for functions in subscribe method! - def getPhoneName = this.&getPhoneName + def checkOpenDrawAfterReminder = this.&checkOpenDrawAfterReminder //Global Object for functions in subscribe method! - def hideOptionsSection = this.&hideOptionsSection + def sendNotification = this.&sendNotification //Global Object for functions in subscribe method! - def getAllOk = this.&getAllOk + def isOpened = this.&isOpened //Global Object for functions in subscribe method! - def getModeOk = this.&getModeOk + def setLEDNotification = this.&setLEDNotification //Global Object for functions in subscribe method! - def getDaysOk = this.&getDaysOk - //Global Object for functions in subscribe method! - def getTimeOk = this.&getTimeOk - //Global Object for functions in subscribe method! - def hhmm = this.&hhmm - //Global Object for functions in subscribe method! - def timeIntervalLabel = this.&timeIntervalLabel - //Global Object for functions in subscribe method! - def list = this.&list + def resetLEDNotification = this.&resetLEDNotification App2(Object obj) { reference = obj location = obj.locationObject app = obj.appObject - atomicState = obj.atomicState - beacons = obj.beaconSensorObject - phones = obj.mobilePresenceObject - arrivalOnSwitches = obj.switchObject - arrivalOffSwitches = obj.switchObject - arrivalLocks = obj.lockObject - departOnSwitches = obj.switchObject - departOffSwitches = obj.switchObject - departLocks = obj.lockObject + deviceContactSensor = obj.contactObject + deviceLight = obj.colorControlObject //Global variable for settings! - settings = [app:app, starting:starting, ending:ending, beacons:beacons, phones:phones, arrivalPhrase:arrivalPhrase, arrivalOnSwitches:arrivalOnSwitches, arrivalOffSwitches:arrivalOffSwitches, arrivalLocks:arrivalLocks, departPhrase:departPhrase, departOnSwitches:departOnSwitches, departOffSwitches:departOffSwitches, departLocks:departLocks, pushNotification:pushNotification, phone:phone, days:days, modes:modes] + settings = [app: app, deviceContactSensor: deviceContactSensor, reminderTime: reminderTime, deviceLight: deviceLight, END: "END"] } //Global variables for each app //Global variable for state[mode] @@ -642,11 +702,16 @@ class App2 { def settings //Zip code def zipCode = 92617 + //atomicState variable + def atomicState = [version: "1.01"] //Methods ///////////////////////////////////////////////////////////////////// def setLocationMode(String mode) { - location.mode = mode + location.setValue([name: "Location", value: "$mode", deviceId: "locationID0", descriptionText: "", + displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) + location.setValue([name: "mode", value: "$mode", deviceId: "locationID0", descriptionText: "", + displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) } ///////////////////////////////////////////////////////////////////// @@ -668,6 +733,12 @@ class App2 { eventList.add(event) functionList.add(FunctionToCall) } + ////subscribe(obj, event, nameOfFunc) + def subscribe(Object obj, String event, String nameOfFunction) { + objectList.add(obj) + eventList.add(event) + functionList.add(nameOfFunction) + } ////subscribe(obj, event, func, data) def subscribe(Object obj, String event, Closure FunctionToCall, LinkedHashMap metaData) { objectList.add(obj) @@ -713,6 +784,17 @@ class App2 { } } + def unschedule(String nameOfFunctionToUnschedule) { + for (int i = 0;i < timersFuncList.size();i++) { + if (timersFuncList[i] instanceof String) { + if (timersFuncList[i] == nameOfFunctionToUnschedule) { + if (timersList != null) + timersList[i].cancel() + } + } + } + } + def unschedule() { for (int i = 0;i < timersFuncList.size();i++) { @@ -731,6 +813,16 @@ class App2 { } } } + + def sendNotificationToContacts(String text, String recipients, LinkedHashMap metaData) { + for (int i = 0;i < recipients.size();i++) { + for (int j = 0;j < location.contacts.size();j++) { + if (recipients[i] == location.contacts[j]) { + println("Sending \""+text+"\" to "+location.phoneNumbers[j].toString()) + } + } + } + } ///////////////////////////////////////////////////////////////////// ////sendSms(phone, text) def sendSms(long phoneNumber, String text) { @@ -833,329 +925,183 @@ class App2 { def canSchedule() { return true } - - def mainPage() { - dynamicPage(name: "mainPage", install: true, uninstall: true) { - - section("Where do you want to watch?") { - input name: "beacons", type: "capability.beacon", title: "Select your beacon(s)", - multiple: true, required: true - } - - section("Who do you want to watch for?") { - input name: "phones", type: "device.mobilePresence", title: "Select your phone(s)", - multiple: true, required: true - } - - section("What do you want to do on arrival?") { - input name: "arrivalPhrase", type: "enum", title: "Execute a phrase", - options: listPhrases(), required: false - input "arrivalOnSwitches", "capability.switch", title: "Turn on some switches", - multiple: true, required: false - input "arrivalOffSwitches", "capability.switch", title: "Turn off some switches", - multiple: true, required: false - input "arrivalLocks", "capability.lock", title: "Unlock the door", - multiple: true, required: false - } - - section("What do you want to do on departure?") { - input name: "departPhrase", type: "enum", title: "Execute a phrase", - options: listPhrases(), required: false - input "departOnSwitches", "capability.switch", title: "Turn on some switches", - multiple: true, required: false - input "departOffSwitches", "capability.switch", title: "Turn off some switches", - multiple: true, required: false - input "departLocks", "capability.lock", title: "Lock the door", - multiple: true, required: false - } - - section("Do you want to be notified?") { - input "pushNotification", "bool", title: "Send a push notification" - input "phone", "phone", title: "Send a text message", description: "Tap to enter phone number", - required: false - } - - section { - label title: "Give your automation a name", description: "e.g. Goodnight Home, Wake Up" - } - - def timeLabel = timeIntervalLabel() - section(title: "More options", hidden: hideOptionsSection(), hideable: true) { - href "timeIntervalInput", title: "Only during a certain time", - description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete" - - input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false, - options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] - - input "modes", "mode", title: "Only when mode is", multiple: true, required: false - } - } + ///////////////////////////////////////////////////////////////////// + def createAccessToken() { + state.accessToken = "accessToken" + return state.accessToken } - - // Lifecycle management + ///////////////////////////////////////////////////////////////////// + def runOnce(Date date, Closure methodToCall) { + methodTocall() + } + def installed() { - log.debug " Installed with settings: ${settings}" + log.debug "Installed with settings: ${settings}" + initialize() } def updated() { - log.debug " Updated with settings: ${settings}" + log.debug "Updated with settings: ${settings}" + unsubscribe() + initialize() } def initialize() { - subscribe(beacons, "presence", beaconHandler) - } - - // Event handlers - def beaconHandler(evt) { - log.debug " beaconHandler: $evt" - - if (allOk) { - def data = new groovy.json.JsonSlurper().parseText(evt.data) - // removed logging of device names. can be added back for debugging - //log.debug " data: $data - phones: " + phones*.deviceNetworkId - - def beaconName = getBeaconName(evt) - // removed logging of device names. can be added back for debugging - //log.debug " beaconName: $beaconName" - - def phoneName = getPhoneName(data) - // removed logging of device names. can be added back for debugging - //log.debug " phoneName: $phoneName" - if (phoneName != null) { - def action = data.presence == "1" ? "arrived" : "left" - def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName" - - if (action == "arrived") { - msg = arriveActions(msg) - } - else if (action == "left") { - msg = departActions(msg) - } - log.debug " msg: $msg" - if (pushNotification || phone) { - def options = [ - method: (pushNotification && phone) ? "both" : (pushNotification ? "push" : "sms"), - phone: phone - ] - sendNotification(msg, options) - } + // will stop LED notification incase it was set by med reminder + subscribe(deviceContactSensor, "contact", contactHandler) + + // how many minutes to look in the past from the reminder time, for an open draw + state.minutesToCheckOpenDraw = 60 + + // is true when LED notification is set after exceeding 10 minutes past reminder time + state.ledNotificationTriggered = false + + // Set a timer to run once a day to notify if draw wasn't opened yet + schedule(reminderTime, checkOpenDrawInPast) + + } + + // Should turn off any LED notification on OPEN state + def contactHandler(evt){ + if (evt.value == "open") { + // if LED notification triggered, reset it. + log.debug "Cabinet opened" + if (state.ledNotificationTriggered) { + resetLEDNotification() } } } - // Helpers - private arriveActions(msg) { - if (arrivalPhrase || arrivalOnSwitches || arrivalOffSwitches || arrivalLocks) msg += ", so" - - if (arrivalPhrase) { - log.debug " executing: $arrivalPhrase" - executePhrase(arrivalPhrase) - msg += " ${prefix('executed')} $arrivalPhrase." - } - if (arrivalOnSwitches) { - log.debug " turning on: $arrivalOnSwitches" - arrivalOnSwitches.on() - msg += " ${prefix('turned')} ${list(arrivalOnSwitches)} on." - } - if (arrivalOffSwitches) { - log.debug " turning off: $arrivalOffSwitches" - arrivalOffSwitches.off() - msg += " ${prefix('turned')} ${list(arrivalOffSwitches)} off." - } - if (arrivalLocks) { - log.debug " unlocking: $arrivalLocks" - arrivalLocks.unlock() - msg += " ${prefix('unlocked')} ${list(arrivalLocks)}." - } - msg - } - - private departActions(msg) { - if (departPhrase || departOnSwitches || departOffSwitches || departLocks) msg += ", so" - - if (departPhrase) { - log.debug " executing: $departPhrase" - executePhrase(departPhrase) - msg += " ${prefix('executed')} $departPhrase." - } - if (departOnSwitches) { - log.debug " turning on: $departOnSwitches" - departOnSwitches.on() - msg += " ${prefix('turned')} ${list(departOnSwitches)} on." - } - if (departOffSwitches) { - log.debug " turning off: $departOffSwitches" - departOffSwitches.off() - msg += " ${prefix('turned')} ${list(departOffSwitches)} off." - } - if (departLocks) { - log.debug " unlocking: $departLocks" - departLocks.lock() - msg += " ${prefix('locked')} ${list(departLocks)}." - } - msg - } - - private prefix(word) { - def result - def index = settings.prefixIndex == null ? 0 : settings.prefixIndex + 1 - switch (index) { - case 0: - result = "I $word" - break - case 1: - result = "I also $word" - break - case 2: - result = "And I $word" - break - default: - result = "And $word" - break - } - - settings.prefixIndex = index - log.trace "prefix($word'): $result" - result - } - - private listPhrases() { - location.helloHome.getPhrases().label - } - - private executePhrase(phraseName) { - if (phraseName) { - location.helloHome.execute(phraseName) - log.debug " executed phrase: $phraseName" - } - } - - private getBeaconName(evt) { - def beaconName = beacons.find { b -> b.id == evt.deviceId } - return beaconName + // If the draw was NOT opened within 60 minutes of the timer send notification out. + def checkOpenDrawInPast(){ + log.debug "Checking past 60 minutes of activity from $reminderTime" + + // check activity of sensor for past 60 minutes for any OPENED status + def cabinetOpened = isOpened(state.minutesToCheckOpenDraw) + log.debug "Cabinet found opened: $cabinetOpened" + + // if it's opened, then do nothing and assume they took their meds + if (!cabinetOpened) { + sendNotification("Hi, please remember to take your meds in the cabinet") + + // if no open activity, send out notification and set new reminder + def reminderTimePlus10 = new Date(now() + (10 * 60000)) + + // needs to be scheduled if draw wasn't already opened + runOnce(reminderTimePlus10, checkOpenDrawAfterReminder) + } } - private getPhoneName(data) { - def phoneName = phones.find { phone -> - // Work around DNI bug in data - def pParts = phone.deviceNetworkId.split('\\|') - def dParts = data.dni.split('\\|') - pParts[0] == dParts[0] + // If the draw was NOT opened after 10 minutes past reminder, use LED notification + def checkOpenDrawAfterReminder(){ + log.debug "Checking additional 10 minutes of activity from $reminderTime" + + // check activity of sensor for past 10 minutes for any OPENED status + def cabinetOpened = isOpened(10) + + log.debug "Cabinet found opened: $cabinetOpened" + + // if no open activity, blink lights + if (!cabinetOpened) { + log.debug "Set LED to Notification color" + setLEDNotification() + } + + } + + // Helper function for sending out an app notification + def sendNotification(msg){ + log.debug "Message Sent: $msg" + sendPush(msg) + } + + // Check if the sensor has been opened since the minutes entered + // Return true if opened found, else false. + def isOpened(minutes){ + // query last X minutes of activity log + def previousDateTime = new Date(now() - (minutes * 60000)) + + // capture all events recorded + def evts = deviceContactSensor.eventsSince(previousDateTime) + def cabinetOpened = false + if (evts.size() > 0) { + evts.each{ + if(it.value == "open") { + cabinetOpened = true + } + } } - return phoneName + + return cabinetOpened } - private hideOptionsSection() { - (starting || ending || days || modes) ? false : true - } - - private getAllOk() { - modeOk && daysOk && timeOk - } + // Saves current color and sets the light to RED + def setLEDNotification(){ - private getModeOk() { - def result = !modes || modes.contains(location.mode) - log.trace " modeOk = $result" - result - } + state.ledNotificationTriggered = true + + // turn light back off when reset is called if it was originally off + state.ledState = deviceLight.currentValue("switch") - private getDaysOk() { - def result = true - if (days) { - def df = new java.text.SimpleDateFormat("EEEE") - if (location.timeZone) { - df.setTimeZone(location.timeZone) - } - else { - df.setTimeZone(TimeZone.getTimeZone("America/New_York")) - } - def day = df.format(new Date()) - result = days.contains(day) - } - log.trace " daysOk = $result" - result - } + // set light to RED and store original color until stopped + state.origColor = deviceLight.currentValue("hue") + deviceLight.on() + deviceLight.setHue(100) + + log.debug "LED set to RED. Original color stored: $state.origColor" - private getTimeOk() { - def result = true - if (starting && ending) { - def currTime = now() - def start = timeToday(starting, location?.timeZone).time - def stop = timeToday(ending, location?.timeZone).time - result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start - } - log.trace " timeOk = $result" - result } - private hhmm(time, fmt = "h:mm a") { - def t = timeToday(time, location.timeZone) - def f = new java.text.SimpleDateFormat(fmt) - f.setTimeZone(location.timeZone ?: timeZone(time)) - f.format(t) - } + // Sets the color back to the original saved color + def resetLEDNotification(){ - private timeIntervalLabel() { - (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : "" - } + state.ledNotificationTriggered = false + + // return color to original + log.debug "Reset LED color to: $state.origColor" + if (state.origColor != null) { + deviceLight.setHue(state.origColor) + } + + // if the light was turned on just for the notification, turn it back off now + if (state.ledState == "off") { + deviceLight.off() + } - private list(Object names) { - return names[0] } } @Field def app1 @Field def app2 -def initOrder = Verify.getBoolean() -if (initOrder) { - app1 = new App1(this) - app2 = new App2(this) -} else { - app2 = new App2(this) - app1 = new App1(this) -} -def installOrder = Verify.getBoolean() -if (installOrder) { - app1.installed() - app2.installed() -} else { - app2.installed() - app1.installed() -} +app1 = new App1(this) +app2 = new App2(this) -while(true) { - def eventNumber = Verify.getInt(0,4) - switch(eventNumber) { - case 0: - lockObject.setValue([name: "lock", value: "locked", deviceId: "lockID0", descriptionText: "", - displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) - break - case 1: - lockObject.setValue([name: "unlock", value: "unlocked ", deviceId: "lockID0", descriptionText: "", - displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) - break - case 2: - contactObject.setValue([name: "contact.open", value: "open", deviceId: "contactSensorID0", descriptionText: "", - displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) - break - case 3: - contactObject.setValue([name: "contact.closed", value: "closed", deviceId: "contactSensorID0", descriptionText: "", - displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) - break - case 4: - def event = Verify.getInt(0,1) - if (event == 0) { - presenceSensorObject.setValue([name: "presence", value: "present", deviceId: "presenceSensorID0", descriptionText: "", - displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"presence":"1","dni":"mobile0"}']) - } else { - presenceSensorObject.setValue([name: "presence", value: "not present", deviceId: "presenceSensorID0", descriptionText: "", - displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"presence":"0","dni":"mobile0"}']) - } - break - } -} +app1.installed() +app2.installed() + +contactObject.setValue([name: "contact", value: "open", deviceId: "contactSensorID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) +contactObject.setValue([name: "contact", value: "closed", deviceId: "contactSensorID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) +contactObject.setValue([name: "contact", value: "open", deviceId: "contactSensorID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) + +colorControlObject.setValue([name: "hue", value: "32", deviceId: "colorControlID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) +colorControlObject.setValue([name: "saturation", value: "32", deviceId: "colorControlID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) +colorControlObject.setValue([name: "level", value: "32", deviceId: "colorControlID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) +colorControlObject.setValue([name: "switch", value: "on", deviceId: "colorControlID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) +colorControlObject.setValue([name: "switch", value: "off", deviceId: "colorControlID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) +colorControlObject.setValue([name: "switch", value: "on", deviceId: "colorControlID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}']) +colorControlObject.setValue([name: "colorTemperature", value: "32", deviceId: "colorControlID0", descriptionText: "", +displayed: true, linkText: "", isStateChange: false, unit: "", data: '{"info": "info"}'])