Odd the token is not present on line 109 when I look at the app code, it must remove it from the hub once the app is running??????
Try this:
/**
* Hive (Connect)
*
* Copyright 2015,2016 Alex Lee Yuk Cheung
* Hive Contact Sensor code portions contributed by Simon Green
* Hive Active Bulb code portions contributed by Tom Beech
*
* 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.
*
* VERSION HISTORY
*
* 24.02.2016
* v2.0 BETA - New Hive Connect App
* v2.0.1 BETA - Fix bug for accounts that do not have capabilities attribute against thermostat.
* v2.1 - Improved authentication process and overhaul to UI. Added notification capability.
* v2.1.1 - Bug fix when initially selecting devices for the first time.
* v2.1.2 - Move external icon references into Github\
*
* 17.08.2016
* v2.1.3 - Fix null pointer on state variable corruption
* v2.1.3b - Fix device failure on API timeout
*
* 01.09.2016
* v2.2 - Integrate auto mode functionality from Auto Mode for Thermostat smart app
*
* 04.09.2016
* v2.3 - Added support for Hive Contact Sensor - Author: Simon Green
*
* 06.09.2016
* v2.3.1 - Improve device detection
*
* 10.09.2016
* v2.3.2 - Added notification option for maximum temperature threshold breach for Hive heating devices.
*
* 23.1.2016
* v2.4 - Added support for Hive Active Warm White and Hive Active Tunable Lights - Author: Tom Beech
* v2.4b - Minor UI fixes for bulb devices.
*
* 28.11.2016
* v2.5 - Added support for Hive Active Plugs - Author: Tom Beech
* - Refactor of device selecting string - Author: Tom Beech
* - Review device naming and text consistency.
*
* v2.5b - Shortern some device names.
*
* 02.12.2016
* v2.6 - Added support for Hive Active Colour Bulb - Author: Tom Beech
*
* 28.05.2017
* v2.7 - Support for new Hive Beekeeper API - Authors: Tom Beech, Alex Lee Yuk Cheung
* - Removed support for Hive Contact Sensor. Zigbee integration by Simon Green is preferred option.
* v2.7b - Bug fix. Refresh bug prevents installation of Hive devices.
*
* 30.10.2017
* v3.0 - Support for Hive Active Light Colour Tuneable device.
*
* 4.10.2019
* v3.1 - Support for Hive Radiator TRV - Authors: Ben Lee
*
* 13.10.2020
* v3.1.1b - Fix device suffix being set within deviceId
*
* 05.12.2020
* v3.2 - Change authentication method to use Tokens generated from amazon-user-pool-srp-client
*
* 06.12.2020
* v3.2a - Reduce token refresh frequency
* v3.2b - Add Bearer token on API call
*
* 07.12.2020
* v3.2c - Remove username and password references that are now redundant
*/
definition(
name: "Hive (Connect)",
namespace: "alyc100",
author: "Alex Lee Yuk Cheung",
description: "Connect your Hive devices to SmartThings.",
iconUrl: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png",
iconX2Url: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png",
singleInstance: true
)
preferences {
//startPage
page(name: "startPage")
//Connect Pages
page(name:"mainPage", title:"Hive Device Setup", content:"mainPage", install: true)
page(name: "loginPAGE")
page(name: "selectDevicePAGE")
page(name: "preferencesPAGE")
page(name: "tmaPAGE")
//Thermostat Mode Automation Pages
page(name: "tmaConfigurePAGE")
}
def apiBeekeeperUKURL(path = '/') { return "https://beekeeper-uk.hivehome.com:443/1.0${path}" }
def apiBeekeeperURL(path = '/') { return "https://beekeeper.hivehome.com:443/1.0${path}" }
def initTokens() {
def hiveTokenString = 'PASTE HiveToken.json content into here'
return new groovy.json.JsonSlurper().parseText(hiveTokenString)
}
def startPage() {
if (parent) {
atomicState?.isParent = false
tmaConfigurePAGE()
} else {
atomicState?.isParent = true
mainPage()
}
}
//Hive Connect App Pages
def mainPage() {
log.debug "mainPage"
if (!state.beekeeperToken || state.beekeeperToken == '') {
return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) {
section {
headerSECTION()
href("loginPAGE", title: null, description: authenticated() ? "AUTHENTICATED. Tap to refresh authentication" : "Tap to refresh authentication", state: authenticated())
}
}
} else {
log.debug "next phase"
return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) {
section {
headerSECTION()
href("loginPAGE", title: "Authentication", description: authenticated() ? "AUTHENTICATED. Tap to refresh authentication" : "Tap to refresh authentication", state: authenticated())
}
if (stateTokenPresent()) {
section ("Choose your devices:") {
href("selectDevicePAGE", title: "Devices", description: devicesSelected() ? getDevicesSelectedString() : "Tap to select devices", state: devicesSelected())
}
section("Hive Mode Automations:") {
href "tmaPAGE", title: "Hive Mode Automations...", description: (tmaDescription() ? tmaDescription() : "Tap to Configure..."), state: (tmaDescription() ? "complete" : null)
}
section ("Notifications:") {
href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure notifications", state: preferencesSelected())
}
} else {
section {
paragraph "NOT AUTHENTICATED. There was a problem connecting to Hive. Check your generated token and error logs in SmartThings web console.\n\n${state.loginerrors}"
}
}
}
}
}
def headerSECTION() {
return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png",
"Hive (Connect)\nVersion: v3.2c\nDate: 07122020(1500)")
}
def stateTokenPresent() {
return state.beekeeperToken != null && state.beekeeperToken != ''
}
def authenticated() {
return (state.beekeeperToken != null && state.beekeeperToken != '') ? "complete" : null
}
def devicesSelected() {
return (selectedHeating || selectedHotWater || selectedBulb || selectedTunableBulb || selectedActivePlug || selectedColourBulb) ? "complete" : null
}
def preferencesSelected() {
return (sendPush || sendSMS != null) && (maxtemp != null || mintemp != null || sendBoost || sendOff || sendManual || sendSchedule || sendMaxThresholdBreach) ? "complete" : null
}
def tmaDescription() {
// def tmaApp = findChildAppByName( appName() )
// if(tmaApp) {
if(childApps) {
def str = ""
str += "Thermostat Automations:"
childApps?.each { a ->
def name = a?.getLabel()
str += "\n• $name"
}
return str
}
return null
}
def getDevicesSelectedString() {
if (state.hiveHeatingDevices == null ||
state.hiveHotWaterDevices == null ||
state.hiveTunableBulbDevices == null ||
state.hiveBulbDevices == null ||
state.hiveActivePlugDevices == null ||
state.hiveColourBulb == null) {
updateDevices()
}
def listString = ""
selectedHeating.each { childDevice ->
if (null != state.hiveHeatingDevices)
listString += "${state.hiveHeatingDevices[childDevice]}\n"
}
selectedHotWater.each { childDevice ->
if (null != state.hiveHotWaterDevices)
listString += "${state.hiveHotWaterDevices[childDevice]}\n"
}
selectedBulb.each { childDevice ->
if (null != state.hiveBulbDevices)
listString += "${state.hiveBulbDevices[childDevice]}\n"
}
selectedTunableBulb.each { childDevice ->
if (null != state.hiveTunableBulbDevices)
listString += "${state.hiveTunableBulbDevices[childDevice]}\n"
}
selectedActivePlug.each { childDevice ->
if (null != state.hiveActivePlugDevices)
listString += "${state.hiveActivePlugDevices[childDevice]}\n"
}
selectedColourBulb.each { childDevice ->
if (null != state.selectedColourBulb)
listString += "${state.selectedColourBulb[childDevice]}\n"
}
// Returns the completed list, and trims the last carrige return
return listString.trim()
}
def getPreferencesString() {
def listString = ""
if (sendPush) listString += "Send Push, "
if (sendSMS != null) listString += "Send SMS, "
if (maxtemp != null) listString += "Max Temp: ${maxtemp}, "
if (mintemp != null) listString += "Min Temp: ${mintemp}, "
if (sendBoost) listString += "Boost, "
if (sendOff) listString += "Off, "
if (sendManual) listString += "Manual, "
if (sendSchedule) listString += "Schedule, "
if (sendMaxThresholdBreach) listString += "Max Temp Threshold Breach, "
if (listString != "") listString = listString.substring(0, listString.length() - 2)
return listString
}
def loginPAGE() {
getBeekeeperAccessToken()
if (!state.beekeeperToken || state.beekeeperToken == '') {
return dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) {
section { headerSECTION() }
section { paragraph "NOT AUTHENTICATED. There was a problem connecting to Hive. Check your generated token and error logs in SmartThings web console.\n\n${state.loginerrors}" }
}
} else {
dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) {
section { headerSECTION() }
if (stateTokenPresent()) {
section {
paragraph "AUTHENTICATED. You have successfully connected to Hive. Click 'Next' to go back to the main screen and choose your Hive devices."
}
} else {
section {
paragraph "NOT AUTHENTICATED. There was a problem connecting to Hive. Check your generated token and error logs in SmartThings web console.\n\n${state.loginerrors}"
}
}
}
}
}
def selectDevicePAGE() {
updateDevices()
dynamicPage(name: "selectDevicePAGE", title: "Devices", uninstall: false, install: false) {
section { headerSECTION() }
section("Select your devices:") {
input "selectedHeating", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/thermostat-frame-6c75d5394d102f52cb8cf73704855446.png", required:false, title:"Select Hive Heating Devices \n(${state.hiveHeatingDevices.size() ?: 0} found)", multiple:true, options:state.hiveHeatingDevices
input "selectedHotWater", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/thermostat-frame-6c75d5394d102f52cb8cf73704855446.png", required:false, title:"Select Hive Hot Water Devices \n(${state.hiveHotWaterDevices.size() ?: 0} found)", multiple:true, options:state.hiveHotWaterDevices
input "selectedBulb", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/hive-bulb.jpg", required:false, title:"Select Hive Light Dimmable Devices \n(${state.hiveBulbDevices.size() ?: 0} found)", multiple:true, options:state.hiveBulbDevices
input "selectedTunableBulb", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/hive-tunablebulb.jpg", required:false, title:"Select Hive Light Tuneable Devices \n(${state.hiveTunableBulbDevices.size() ?: 0} found)", multiple:true, options:state.hiveTunableBulbDevices
input "selectedColourBulb", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/hive-colouredbulb.jpg", required:false, title:"Select Hive Light Colour Devices \n(${state.hiveColourBulb.size() ?: 0} found)", multiple:true, options:state.hiveColourBulb
input "selectedActivePlug", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/hive-activeplug.jpg", required:false, title:"Select Hive Plug Devices \n(${state.hiveActivePlugDevices.size() ?: 0} found)", multiple:true, options:state.hiveActivePlugDevices
}
}
}
def preferencesPAGE() {
dynamicPage(name: "preferencesPAGE", title: "Preferences", uninstall: false, install: false) {
section {
input "sendPush", "bool", title: "Send as Push?", required: false, defaultValue: false
input "sendSMS", "phone", title: "Send as SMS?", required: false, defaultValue: null
}
section("Thermostat Notifications:") {
input "sendBoost", "bool", title: "Notify when mode is Boosting?", required: false, defaultValue: false
input "sendOff", "bool", title: "Notify when mode is Off?", required: false, defaultValue: false
input "sendManual", "bool", title: "Notify when mode is Manual?", required: false, defaultValue: false
input "sendSchedule", "bool", title: "Notify when mode is Schedule?", required: false, defaultValue: false
}
section("Thermostat Max Temperature") {
input ("maxtemp", "number", title: "Alert when temperature is above this value", required: false, defaultValue: 25)
}
section("Thermostat Min Temperature") {
input ("mintemp", "number", title: "Alert when temperature is below this value", required: false, defaultValue: 10)
}
section("Thermostat Max Threshold Breach") {
input "sendMaxThresholdBreach", "bool", title: "Notify when max temp threshold has been breached?", required: false, defaultValue: false
}
}
}
def tmaPAGE() {
dynamicPage(name: "tmaPAGE", title: "", nextPage: !parent ? "startPage" : "tmaPAGE", install: false) {
def tmaApp = findChildAppByName( appName() )
// if(tmaApp) {
if(childApps) {
section("Configured Hive Mode Automations...") { }
} else {
section("") {
paragraph "Create New Hive Mode Automation to get Started..."
}
}
section("Add a new Automation:") {
app(name: "tmaApp", appName: appName(), namespace: "alyc100", multiple: true, title: "Create New Mode Automation...")
def rText = "NOTICE:\nIntegrated Hive Mode Automations is in BETA\n"
paragraph "${rText}"//, required: true, state: null
}
}
}
//Auto Thermostat Mode Pages
def tmaConfigurePAGE() {
dynamicPage(name: "tmaConfigurePAGE", title: "Hive Mode Automation", install: true, uninstall: true) {
section {
input ("thermostats", "capability.thermostat", title: "For these thermostats", multiple: true, required: true)
}
section {
input(name: "modeTrigger", title: "Set the trigger to",
description: null, multiple: false, required: true, submitOnChange: true, type: "enum",
options: ["true": "Mode Change", "false": "Switches"])
}
if (modeTrigger == "true") {
// Do something here like update a message on the screen,
// or introduce more inputs. submitOnChange will refresh
// the page and allow the user to see the changes immediately.
// For example, you could prompt for the level of the dimmers
// if dimmers have been selected:
section {
input ("modes", "mode", title:"When SmartThings enters these modes", multiple: true, required: true)
}
} else if (modeTrigger == "false") {
section {
input ("theSwitch", "capability.switch", title:"When this switch is activated", multiple: false, required: true)
}
}
section {
input ("alteredThermostatMode", "enum", multiple: false, title: "Set thermostats to this mode",
options: ["Set To Schedule", "Boost", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Turn Off')
}
section {
input ("resetThermostats", "enum", title: "Reset thermostats after trigger turns off?",
options: ["true": "Yes","false": "No"], required: true, submitOnChange: true)
}
if (resetThermostats == "true") {
section {
input ("resumedThermostatMode", "enum", multiple: false, title: "Reset thermostats back to this mode", submitOnChange: true,
options: ["Set To Schedule", "Boost", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule')
}
if (resumedThermostatMode == "Boost") {
section {
input ("thermostatModeAfterBoost", "enum", multiple: false, title: "What to do when Boost has finished",
options: ["Set To Schedule", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule')
}
}
}
section( "Additional configuration" ) {
input ("days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
input ("temp", "decimal", title: "If setting to Manual, set the temperature to this", required: false, defaultValue: 21)
}
section( "Notifications" ) {
input ("sendPushMessage", "enum", title: "Send a push notification?",
options: ["Yes", "No"], required: true)
input ("phone", "phone", title: "Send a Text Message?", required: false)
}
section {
label title: "Assign a name", required: true
}
}
}
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
// App lifecycle hooks
def installed() {
if(parent) { installedChild() } // This will handle all of the install functions when the child app is installed
else { installedParent() } // This will handle all of the install functions when the parent app is installed
}
def updated() {
if(parent) { updatedChild() } // This will handle all of the install functions when the child app is updated
else { updatedParent() } // This will handle all of the install functions when the parent app is updated
}
def uninstalled() {
if(parent) { } // This will handle all of the install functions when the child app is uninstalled
else { uninstalledParent() } // This will handle all of the install functions when the parent app is uninstalled
}
def installedParent() {
log.debug "installed"
initialize()
// Check for new devices every 3 hours
runEvery3Hours('updateDevices')
// execute handlerMethod every 10 minutes.
runEvery10Minutes('refreshDevices')
}
// called after settings are changed
def updatedParent() {
log.debug "updated"
unsubscribe()
initialize()
unschedule('refreshDevices')
runEvery10Minutes('refreshDevices')
}
def uninstalledParent() {
log.info("Uninstalling, removing child devices...")
unschedule()
removeChildDevices(getChildDevices())
}
private removeChildDevices(devices) {
devices.each {
deleteChildDevice(it.deviceNetworkId) // 'it' is default
}
}
def installedChild() {
log.debug "Installed with settings: ${settings}"
//set up initial thermostat state and force thermostat into correct mode
state.thermostatAltered = false
state.boostingReset = false
//Flags to stop possible infinite loop scenarios when handlers create events
state.internalThermostatEvent = false
state.internalSwitchEvent = false
subscribe(thermostats, "thermostatMode", thermostateventHandlerForTMA, [filterEvents: false])
//Check if mode or switch is the trigger and run initialisation
if (modeTrigger == "true") {
def currentMode = location.mode
log.debug "currentMode = $currentMode"
if (currentMode in modes) {
takeActionForMode(currentMode)
}
subscribe(location, "mode", modeeventHandlerForTMA, [filterEvents: false])
}
else {
if (theSwitch.currentSwitch == "on") {
takeActionForSwitch(theSwitch.currentSwitch)
}
subscribe(theSwitch, "switch", switcheventHandlerForTMA, [filterEvents: false])
}
}
def updatedChild() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
//set up initial thermostat state and force thermostat into correct mode
state.thermostatAltered = false
state.boostingReset = false
state.internalThermostatEvent = false
state.internalSwitchEvent = false
subscribe(thermostats, "thermostatMode", thermostateventHandlerForTMA, [filterEvents: false])
//Check if mode or switch is the trigger and run initialisation
if (modeTrigger == "true") {
def currentMode = location.mode
log.debug "currentMode = $currentMode"
if (currentMode in modes) {
takeActionForMode(currentMode)
}
subscribe(location, "mode", modeeventHandlerForTMA, [filterEvents: false])
} else {
if (theSwitch.currentSwitch == "on") {
takeActionForSwitch(theSwitch.currentSwitch)
}
subscribe(theSwitch, "switch", switcheventHandlerForTMA, [filterEvents: false])
}
}
// called after Done is hit after selecting a Location
def initialize() {
if (parent) { }
else {
log.debug "initialize"
if (selectedHeating) {
addHeating()
}
if (selectedHotWater) {
addHotWater()
}
if(selectedBulb) {
addBulb()
}
if(selectedTunableBulb) {
addTunableBulb()
}
if(selectedActivePlug) {
addActivePlug()
}
if(selectedColourBulb) {
addColourBulb()
}
runIn(10, 'refreshDevices') // Asynchronously refresh devices so we don't block
//subscribe to events for notifications if activated
if (preferencesSelected() == "complete") {
getChildDevices().each { childDevice ->
if (childDevice.typeName == "Hive Heating V2.0" || childDevice.typeName == "Hive Hot Water V2.0") {
subscribe(childDevice, "thermostatMode", modeHandler, [filterEvents: false])
}
if (childDevice.typeName == "Hive Heating V2.0") {
subscribe(childDevice, "temperature", tempHandler, [filterEvents: false])
subscribe(childDevice, "maxtempthresholdbreach", evtHandler, [filterEvents: false])
}
}
}
state.maxNotificationSent = false
state.minNotificationSent = false
}
}
//Event Handler for Connect App
def evtHandler(evt) {
def msg
if (evt.name == "maxtempthresholdbreach") {
msg = "Auto adjusting set temperature of ${evt.displayName} as current set temperature of ${evt.value}°C is above maximum threshold."
if (settings.sendMaxThresholdBreach) generateNotification(msg)
}
}
def tempHandler(evt) {
def msg
log.trace "temperature: $evt.value, $evt"
if (settings.maxtemp != null) {
def maxTemp = settings.maxtemp
if (evt.doubleValue >= maxTemp) {
msg = "${evt.displayName} temperature reading is very hot."
if (state.maxNotificationSent == null || state.maxNotificationSent == false) {
generateNotification(msg)
//Avoid constant messages
state.maxNotificationSent = true
}
}
else {
//Reset if temperature falls back to normal levels
state.maxNotificationSent = false
}
}
else if (settings.mintemp != null) {
def minTemp = settings.mintemp
if (evt.doubleValue <= minTemp) {
msg = "${evt.displayName} temperature reading is very cold."
if (state.minNotificationSent == null || state.minNotificationSent == false) {
generateNotification(msg)
//Avoid constant messages
state.minNotificationSent = true
}
}
else {
//Reset if temperature falls back to normal levels
state.minNotificationSent = false
}
}
}
def modeHandler(evt) {
def msg
if (evt.value == "heat") {
msg = "${evt.displayName} is set to Manual"
if (settings.sendSchedule) generateNotification(msg)
}
else if (evt.value == "off") {
msg = "${evt.displayName} is turned Off"
if (settings.sendOff) generateNotification(msg)
}
else if (evt.value == "auto") {
msg = "${evt.displayName} is set to Schedule"
if (settings.sendManual) generateNotification(msg)
}
else if (evt.value == "emergency heat") {
msg = "${evt.displayName} is in Boost mode"
if (settings.sendBoost) generateNotification(msg)
}
}
//Event Handlers for Thermostat Mode Automation
def modeeventHandlerForTMA(evt) {
if(allOk) {
log.debug "evt.value: $evt.value"
takeActionForMode(evt.value)
}
}
//Handler and action for switch detection
def switcheventHandlerForTMA(evt) {
if(allOk) {
log.debug "evt.value: $evt.value"
log.debug "state.internalSwitchEvent: $state.internalSwitchEvent"
if (state.internalSwitchEvent == false) {
takeActionForSwitch(evt.value)
}
state.internalSwitchEvent = false
}
}
def thermostateventHandlerForTMA(evt) {
log.debug "evt.name: $evt.value"
log.debug "state.thermostatAltered: $state.thermostatAltered"
log.debug "alteredThermostatMode: $alteredThermostatMode"
log.debug "state.boostingReset: $state.boostingReset"
//If boost mode is selected as the trigger, turn switch off if boost mode finishes...
if (state.internalThermostatEvent == false) {
if (modeTrigger == "false") {
//if the switch is currently on, check the new mode of the thermostat and set switch to off if necessary
if (alteredThermostatMode == "Boost") {
state.internalSwitchEvent = true
if (evt.value != "emergency heat") {
//Switching the switch to off should trigger an event that resets app state
theSwitch.off()
}
else {
//Switching the switch to on so it can't be boost again
theSwitch.on()
}
}
}
//If boost mode is selected as resumed state, need to set thermostat mode as per preference
if (state.boostingReset) {
if (evt.value != "emergency heat") {
state.internalThermostatEvent = true
changeAllThermostatsModes(thermostats, thermostatModeAfterBoost, "Boost has now finished")
//Reset boosting reset flag
state.boostingReset = false
}
}
}
state.internalThermostatEvent = false
}
// Thermostat Auto Mode Methods
def takeActionForSwitch(switchState) {
// Is incoming switch is on
if (switchState == "on")
{
//Check thermostat is not already altered
if (!state.thermostatAltered)
{
//Turn selected thermostats into selected mode
//Add detail to push message if set to Manual is specified
log.debug "$theSwitch.label is on, turning thermostats to $alteredThermostatMode"
state.internalThermostatEvent = true
changeAllThermostatsModes(thermostats, alteredThermostatMode, "$theSwitch.label has turned on")
//Only if reset action is specified, set the thermostatAltered state.
if (resetThermostats == "true")
{
state.thermostatAltered = true
}
}
}
else {
log.debug "$theSwitch.label is off"
//Check if thermostats have previously been altered
if (state.thermostatAltered)
{
//Check if user wants to reset thermostats
if (resetThermostats == "true")
{
log.debug "Thermostats have been altered, turning back to $resumedThermostatMode"
//Turn selected thermostats into selected mode
state.internalThermostatEvent = true
changeAllThermostatsModes(thermostats, resumedThermostatMode, "$theSwitch.label has turned off")
//Set flag if boost mode is selected as reset state so it can be set back to desired mode in 'thermostatModeAfterBoost'
if (resumedThermostatMode == "Boost") {
state.boostingReset = true
}
}
//Reset app state
state.thermostatAltered = false
}
else
{
log.debug "Thermostats were not altered. No action taken."
}
}
}
def takeActionForMode(mode) {
// Is incoming mode in the event input enumeration
if (mode in modes)
{
//Check thermostat is not already altered
if (!state.thermostatAltered)
{
//Turn selected thermostats into selected mode
//Add detail to push message if set to Manual is specified
log.debug "$mode in selected modes, turning thermostats to $alteredThermostatMode"
state.internalThermostatEvent = true
changeAllThermostatsModes(thermostats, alteredThermostatMode, "mode has changed to $mode")
//Only if reset action is specified, set the thermostatAltered state.
if (resetThermostats == "true")
{
state.thermostatAltered = true
}
}
}
else {
log.debug "$mode is not in select modes"
//Check if thermostats have previously been altered
if (state.thermostatAltered)
{
//Check if user wants to reset thermostats
if (resetThermostats == "true")
{
log.debug "Thermostats have been altered, turning back to $resumedThermostatMode"
//Turn each thermostat to selected mode
state.internalThermostatEvent = true
changeAllThermostatsModes(thermostats, resumedThermostatMode, "mode has changed to $mode")
//Set flag if boost mode is selected as reset state so it can be set back to desired mode in 'thermostatModeAfterBoost'
if (resumedThermostatMode == "Boost") {
state.boostingReset = true
}
}
//Reset app state
state.thermostatAltered = false
}
else
{
log.debug "Thermostats were not altered. No action taken."
}
}
}
//Helper method for thermostat mode change
private changeAllThermostatsModes(thermostats, newThermostatMode, reason) {
//Add detail to push message if set to Manual is specified
def thermostatModeDetail = newThermostatMode
if (newThermostatMode == "Set to Manual") {
thermostatModeDetail = thermostatModeDetail + " at $temp°C"
}
for (thermostat in thermostats) {
def message = ''
message = "SmartThings has reset $thermostat.label to $thermostatModeDetail because $reason."
log.info message
send(message)
log.debug "Setting $thermostat.label to $thermostatModeDetail"
if (newThermostatMode == "Set to Manual") {
thermostat.heat()
thermostat.setHeatingSetpoint(temp)
}
else if (newThermostatMode == "Turn Off") {
thermostat.off()
}
else if (newThermostatMode == "Boost") {
thermostat.emergencyHeat()
}
else {
thermostat.auto()
}
}
}
private send(msg) {
if ( sendPushMessage != "No" ) {
log.debug( "sending push message" )
sendPush( msg )
}
if ( phone ) {
log.debug( "sending text message" )
sendSms( phone, msg )
}
log.debug msg
}
private getAllOk() {
daysOk && timeOk
}
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("Europe/London"))
}
def day = df.format(new Date())
result = days.contains(day)
}
log.trace "daysOk = $result"
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting).time
def stop = timeToday(ending).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)
}
def getTimeLabel(starting, ending){
def timeLabel = "Tap to set"
if(starting && ending){
timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
}
else if (starting) {
timeLabel = "Start at" + " " + hhmm(starting)
}
else if(ending){
timeLabel = "End at" + hhmm(ending)
}
timeLabel
}
private hideOptionsSection() {
(starting || ending || days || modes) ? false : true
}
def greyedOutSettings(){
def result = ""
if (starting || ending || days || falseAlarmThreshold) {
result = "complete"
}
result
}
def greyedOutTime(starting, ending){
def result = ""
if (starting || ending) {
result = "complete"
}
result
}
def generateNotification(msg) {
if (settings.sendSMS != null) {
sendSms(sendSMS, msg)
}
if (settings.sendPush == true) {
sendPush(msg)
}
}
def updateDevices() {
if (!state.devices) {
state.devices = [:]
}
def devices = devicesList()
state.hiveHeatingDevices = [:]
state.hiveHotWaterDevices = [:]
state.hiveBulbDevices = [:]
state.hiveTunableBulbDevices = [:]
state.hiveActivePlugDevices = [:]
state.hiveColourBulb = [:]
def selectors = []
devices.each { device ->
selectors.add("${device.id}")
//Heating
if (device.type == "heating" || device.type == "trvcontrol") {
def suffix = device.type == "heating" ? "Hive Heating" : "Hive TRV"
//Heating Control
log.debug "Identified: ${device.state.name} ${suffix}"
def value = "${device.state.name} ${suffix}"
def key = device.id
selectors.add("${key}")
state.hiveHeatingDevices["${key}"] = value
//Update names of devices with Hive
def childDevice = getChildDevice("${key}")
if (childDevice) {
//Update name of device if different.
if(childDevice.name != device.state.name + " ${suffix}") {
childDevice.name = device.state.name + " ${suffix}"
log.debug "Device's name has changed."
}
}
// Water Control
} else if (device.type == "hotwater") {
log.debug "Identified: ${device.state.name} Hive Hot Water"
def value = "${device.state.name} Hive Hot Water"
def key = device.id
state.hiveHotWaterDevices["${key}"] = value
//Update names of devices
def childDevice = getChildDevice("${device.id}")
if (childDevice) {
//Update name of device if different.
if(childDevice.name != device.state.name + " Hive Hot Water") {
childDevice.name = device.state.name + " Hive Hot Water"
log.debug "Device's name has changed."
}
}
//Dimmable Bulb
} else if (device.type == "tuneablelight") {
log.debug "Identified: ${device.state.name} Hive Light Tunable"
def value = "${device.state.name} Hive Light Tunable"
def key = device.id
state.hiveTunableBulbDevices["${key}"] = value
//Update names of devices
def childDevice = getChildDevice("${device.id}")
if (childDevice) {
//Update name of device if different.
if(childDevice.name != device.state.name) {
childDevice.name = device.state.name
log.debug "Device's name has changed."
}
}
//Colour Bulb
} else if (device.type == "colourtuneablelight") {
log.debug "Identified: ${device.state.name} Hive Colour Bulb"
def value = "${device.state.name} Hive Colour Bulb"
def key = device.id
state.hiveColourBulb["${key}"] = value
//Update names of devices
def childDevice = getChildDevice("${device.id}")
if (childDevice) {
//Update name of device if different.
if(childDevice.name != device.state.name) {
childDevice.name = device.state.name
log.debug "Device's name has changed."
}
}
//White Active Light Bulb
} else if (device.type == "warmwhitelight") {
log.debug "Identified: ${device.state.name} Hive Light Dimmable"
def value = "${device.state.name} Hive Light Dimmable"
def key = device.id
state.hiveBulbDevices["${key}"] = value
//Update names of devices
def childDevice = getChildDevice("${device.id}")
if (childDevice) {
//Update name of device if different.
if(childDevice.name != device.state.name) {
childDevice.name = device.state.name
log.debug "Device's name has changed."
}
}
// Active Plug
} else if (device.type == "activeplug") {
log.debug "Identified: ${device.state.name} Hive Plug"
def value = "${device.state.name} Hive Plug"
def key = device.id
state.hiveActivePlugDevices["${key}"] = value
//Update names of devices
def childDevice = getChildDevice("${device.id}")
if (childDevice) {
//Update name of device if different.
if(childDevice.name != device.state.name) {
childDevice.name = device.state.name
log.debug "Device's name has changed."
}
}
}
}
//Remove devices if does not exist on the Hive platform
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
log.info("Deleting ${it.deviceNetworkId}")
try {
deleteChildDevice(it.deviceNetworkId)
} catch (hubitat.exception.NotFoundException e) {
log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.")
} catch (hubitat.exception.ConflictException ce) {
log.info("Device ${it.deviceNetworkId} in use. Please manually delete.")
}
}
}
def addHeating() {
updateDevices()
selectedHeating.each { device ->
def childDevice = getChildDevice("${device}")
if (!childDevice) {
log.info("Adding Hive Heating device ${device}: ${state.hiveHeatingDevices[device]}")
def data = [
name: state.hiveHeatingDevices[device],
label: state.hiveHeatingDevices[device],
]
childDevice = addChildDevice("alyc100", "Hive Heating", "$device", null, data)
childDevice.refresh()
log.debug "Created ${state.hiveHeatingDevices[device]} with id: ${device}"
} else {
log.debug "found ${state.hiveHeatingDevices[device]} with id ${device} already exists"
}
}
}
def addHotWater() {
updateDevices()
selectedHotWater.each { device ->
def childDevice = getChildDevice("${device}")
if (!childDevice) {
log.info("Adding Hive Hot Water device ${device}: ${state.hiveHotWaterDevices[device]}")
def data = [
name: state.hiveHotWaterDevices[device],
label: state.hiveHotWaterDevices[device],
]
childDevice = addChildDevice("alyc100", "Hive Hot Water", "$device", null, data)
childDevice.refresh()
log.debug "Created ${state.hiveHotWaterDevices[device]} with id: ${device}"
} else {
log.debug "found ${state.hiveHotWaterDevices[device]} with id ${device} already exists"
}
}
}
def addBulb() {
updateDevices()
selectedBulb.each { device ->
def childDevice = getChildDevice("${device}")
if (!childDevice) {
log.debug "Adding Hive Light Dimmable device ${device}: ${state.hiveBulbDevices[device]}"
def data = [
name: state.hiveBulbDevices[device],
label: state.hiveBulbDevices[device],
]
log.debug data
childDevice = addChildDevice("alyc100", "Hive Active Light", "$device", null, data)
childDevice.refresh()
log.debug "Created ${state.hiveBulbDevices[device]} with id: ${device}"
} else {
log.debug "found ${state.hiveBulbDevices[device]} with id ${device} already exists"
}
}
}
def addTunableBulb() {
updateDevices()
selectedTunableBulb.each { device ->
def childDevice = getChildDevice("${device}")
if (!childDevice) {
log.debug "Adding Hive Light Tuneable device ${device}: ${state.hiveTunableBulbDevices[device]}"
def data = [
name: state.hiveTunableBulbDevices[device],
label: state.hiveTunableBulbDevices[device],
]
log.debug data
childDevice = addChildDevice("alyc100", "Hive Active Light Tuneable", "$device", null, data)
childDevice.refresh()
log.debug "Created ${state.hiveTunableBulbDevices[device]} with id: ${device}"
} else {
log.debug "found ${state.hiveTunableBulbDevices[device]} with id ${device} already exists"
}
}
}
def addColourBulb() {
updateDevices()
selectedColourBulb.each { device ->
def childDevice = getChildDevice("${device}")
if (!childDevice) {
log.debug "Adding Hive Light Colour device ${device}: ${state.hiveBulbDevices[device]}"
def data = [
name: state.hiveColourBulb[device],
label: state.hiveColourBulb[device],
]
log.debug data
childDevice = addChildDevice("alyc100", "Hive Active Light Colour Tuneable", "$device", null, data)
childDevice.refresh()
log.debug "Created ${state.hiveColourBulb[device]} with id: ${device}"
} else {
log.debug "found ${state.hiveColourBulb[device]} with id ${device} already exists"
}
}
}
def addActivePlug() {
updateDevices()
selectedActivePlug.each { device ->
def childDevice = getChildDevice("${device}")
if (!childDevice) {
log.debug "Adding Hive Plug device ${device}: ${state.hiveActivePlugDevices[device]}"
def data = [
name: state.hiveActivePlugDevices[device],
label: state.hiveActivePlugDevices[device],
]
log.debug data
childDevice = addChildDevice("alyc100", "Hive Active Plug", "$device", null, data)
childDevice.refresh()
log.debug "Created ${state.hiveActivePlugDevices[device]} with id: ${device}"
} else {
log.debug "found ${state.hiveActivePlugDevices[device]} with id ${device} already exists"
}
}
}
def refreshDevices() {
log.info("Refreshing all devices...")
getChildDevices().each { device ->
device.refresh()
}
}
def devicesList() {
logErrors([]) {
def resp = apiGET("/products")
if (resp.status == 200) {
return resp.data
} else {
log.error("Non-200 from device list call. ${resp.status} ${resp.data}")
return []
}
}
}
def getDeviceStatus(id) {
def retVal = []
def resp = apiGET("/products")
if (resp.status == 200) {
resp.data.eachWithIndex { currentDevice, i ->
if(currentDevice.id == id || (currentDevice.type + "/" + currentDevice.id) == id) {
retVal = resp.data[i]
}
}
} else {
log.error("Non-200 from device list call. ${resp.status} ${resp.data}")
}
return retVal
}
def apiGET(path, body = [:]) {
try {
if(!isLoggedIn()) {
log.debug "Need to login"
getBeekeeperAccessToken()
}
log.debug("Beginning API GET: ${apiBeekeeperUKURL(path)}, ${apiRequestHeaders()}")
httpGet(uri: apiBeekeeperUKURL(path), contentType: 'application/json', headers: apiRequestHeaders()) {response ->
logResponse(response)
return response
}
} catch (groovyx.net.http.HttpResponseException e) {
logResponse(e.response)
return e.response
}
}
def apiPOST(path, body = [:]) {
try {
if(!isLoggedIn()) {
log.debug "Need to login"
getBeekeeperAccessToken()
}
log.debug("Beginning API POST: ${path}, ${body}")
httpPostJson(uri: apiBeekeeperUKURL(path), body: body, headers: apiRequestHeaders() ) {response ->
logResponse(response)
return response
}
} catch (groovyx.net.http.HttpResponseException e) {
logResponse(e.response)
return e.response
}
}
def getBeekeeperAccessToken() {
try {
def tokens
if (state.beekeeperToken) {
tokens = [ "token": state.beekeeperToken,
"refreshToken": state.beekeeperRefreshToken,
"accessToken": state.beekeeperAccessToken ]
} else {
tokens = initTokens()
}
def params = [
uri: apiBeekeeperURL('/cognito/refresh-token'),
contentType: 'application/json',
body: tokens
]
state.cookie = ''
httpPostJson(params) {response ->
log.debug "Request was successful, $response.status"
log.debug response.headers
state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0)
log.debug "Adding cookie to collection: $cookie"
log.debug "auth: $response.data"
log.debug "cookie: $state.cookie"
log.debug "token: ${response.data.token}"
log.debug "refreshToken: ${response.data.refreshToken}"
log.debug "accessToken: ${response.data.accessToken}"
state.beekeeperToken = response.data.token
state.beekeeperRefreshToken = response.data.refreshToken
state.beekeeperAccessToken = response.data.accessToken
// set the expiration to 20 minutes
state.beekeeperToken_expires_at = new Date().getTime() + 1200000
state.loginerrors = null
}
} catch (groovyx.net.http.HttpResponseException e) {
if (e.response.status == 401) {
state.remove("beekeeperToken")
state.remove("beekeeperRefreshToken")
state.remove("beekeeperAccessToken")
state.remove("beekeeperToken_expires_at")
}
state.loginerrors = "Error: ${e.response.status}: ${e.response.data}"
logResponse(e.response)
return e.response
}
}
def apiRequestHeaders() {
return [
'Authorization': "Bearer ${state.beekeeperToken}"
]
}
def isLoggedIn() {
state.remove("hiveAccessToken")
log.debug "Calling isLoggedIn()"
log.debug "isLoggedIn state $state.beekeeperToken"
if(!state.beekeeperToken) {
log.debug "No state.beekeeperToken"
return false
}
def now = new Date().getTime()
return state.beekeeperToken_expires_at > now
}
def isTmaAppInst() {
def chldCnt = 0
childApps?.each { cApp ->
// if(cApp?.name != getWatchdogAppChildName()) { chldCnt = chldCnt + 1 }
chldCnt = chldCnt + 1
}
return (chldCnt > 0) ? true : false
}
def logResponse(response) {
log.info("Status: ${response.status}")
log.info("Body: ${response.data}")
}
def logErrors(options = [errorReturn: null, logObject: log], Closure c) {
try {
return c()
} catch (groovyx.net.http.HttpResponseException e) {
log.error("got error: ${e}, body: ${e.getResponse().getData()}")
if (e.statusCode == 401) { // token is expired
state.remove("beekeeperToken")
state.remove("beekeeperRefreshToken")
state.remove("beekeeperAccessToken")
state.remove("beekeeperToken_expires_at")
log.warn "Access token is not valid"
}
return options.errorReturn
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here"
return options.errorReturn
}
}
def appName() { return "${parent ? "Hive Mode Automation" : "Hive (Connect)"}" }