From 999c5ad32cc96a430466e3133279af388759f61b Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 19 Nov 2015 13:30:09 +0000 Subject: [PATCH 001/685] Initial version of a Device Type to integrate SmartThings with the British Gas Hive Active Heating platform. Testing using Hive Active Heating 2. --- .../hive-active-heating-2.groovy | 425 ++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy diff --git a/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy b/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy new file mode 100644 index 00000000000..0038e3f72b0 --- /dev/null +++ b/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy @@ -0,0 +1,425 @@ +/** + * Hive Active Heating + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) + * Name: Hive Active Heating + * Author: alyc100 + * Capabilities: + * Polling + * Refresh + * Temperature Measurement + * Thermostat Mode + * Thermostat Operating State + * Thermostat Heating Setpoint + * + * Custom Commands: + * setThermostatMode + * setHeatingSetpoint + * heatingSetpointUp + * heatingSetpointDown + * + * 2. Create a new device (https://graph.api.smartthings.com/device/list) + * Name: Your Choice + * Device Network Id: Your Choice + * Type: Nest (should be the last option) + * Location: Choose the correct location + * Hub/Group: Leave blank + * + * 3. Update device preferences + * Click on the new device to see the details. + * Click the edit button next to Preferences + * Fill in your your Hive user name, Hive password. + * To find your Receiver nickname, login to http://www.hivehome.com. Click on 'Manage Devices'. + * You should see 3 devices including the Hub, Thermostat and Receiver. It is the Receiver nickname you need. + * + * 4. It should be done. + * + * 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. + * + */ +preferences { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") + input("password", "password", title: "Password", description: "Your Hive password") + input("receiver", "text", title: "Receiver", description: "Your receiver nickname") +} + +metadata { + definition (name: "Hive Active Heating", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Temperature Measurement" + capability "Thermostat Heating Setpoint" + capability "Thermostat Mode" + capability "Thermostat Operating State" + + command "heatingSetpointUp" + command "heatingSetpointDown" + command "setThermostatMode" + command "setHeatingSetpoint" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "Thermostat", width: 6, height: 4, type:"thermostat") { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL"){ + attributeState "default", label: '${currentValue}°C', backgroundColors: [ + // Celsius Color Range + [value: 0, color: "#153591"], + [value: 7, color: "#1e9cbb"], + [value: 15, color: "#90d2a7"], + [value: 23, color: "#44b621"], + [value: 29, color: "#f1d801"], + [value: 33, color: "#d04e00"], + [value: 36, color: "#bc2323"] + ]} + + main "Thermostat" + details "Thermostat" + } + + controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { + state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" + } + //standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + // state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" + //} + + //standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + // state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" + //} + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { + state "default", label:'${currentValue}°C', unit:"Heat", + backgroundColors:[ + [value: 0, color: "#153591"], + [value: 7, color: "#1e9cbb"], + [value: 15, color: "#90d2a7"], + [value: 23, color: "#44b621"], + [value: 29, color: "#f1d801"], + [value: 33, color: "#d04e00"], + [value: 36, color: "#bc2323"] + ] + } + + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "idle", action:"polling.poll", label:'${name}', icon: "st.Home.home18" + state "heating", action:"polling.poll", label:'${name}', icon: "st.Home.home1" + } + + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat") { + state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" + state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#bc2323" + } + + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("auto", action:"thermostat.off", icon: "st.thermostat.auto") + state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off") + state("cool", action:"thermostat.heat", icon: "st.thermostat.cool") + state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") + } + + standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + main(["temperature", "thermostatOperatingState"]) + + // ============================================================ + // Slider or Buttons... + // To expose buttons, comment out the first detials line below and uncomment the second details line below. + // To expose sliders, uncomment the first details line below and comment out the second details line below. + + //details(["heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) + details(["heatingSetpoint", "heatSliderControl", "thermostatMode", "thermostatOperatingState", "refresh"]) + + // ============================================================ + + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'temperature' attribute + // TODO: handle 'heatingSetpoint' attribute + // TODO: handle 'thermostatSetpoint' attribute + // TODO: handle 'thermostatMode' attribute + // TODO: handle 'thermostatOperatingState' attribute +} + +// handle commands +def setHeatingSetpoint(temp) { + def latestThermostatMode = device.latestState('thermostatMode') + if (temp < 5) { + temp = 5 + } + if (temp > 32) { + temp = 32 + } + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} + def args = [ + nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] + ] + + api('temperature', args) { + runIn(2, poll) + } + +} + +def heatingSetpointUp(){ + int newSetpoint = device.currentValue("heatingSetpoint") + 1 + log.debug "Setting heat set point up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def heatingSetpointDown(){ + int newSetpoint = device.currentValue("heatingSetpoint") - 1 + log.debug "Setting heat set point down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def off() { + setThermostatMode('off') +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + setThermostatMode('heat') +} + +def auto() { + setThermostatMode('auto') +} + +def setThermostatMode(mode) { + mode = mode == 'emergency heat'? 'heat' : mode + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] + ] + if (mode == 'off') { + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] + ] + } else if (mode == 'heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + } + + api('thermostat_mode', args) { + mode = mode == 'range' ? 'auto' : mode + runIn(2, poll) + } +} + +// handle commands +def poll() { + log.debug "Executing 'poll'" + api('status', []) { + data.nodes = it.data.nodes + + // get temperature status + def temperature = data.nodes.attributes.temperature.reportedValue[0] + def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + + sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") + sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") + + // determine hive operating mode + def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] + def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] + + log.debug "activeHeatCoolMode: $activeHeatCoolMode" + log.debug "activeScheduleLock: $activeScheduleLock" + + def mode = 'auto' + + if (activeHeatCoolMode == "OFF") { + mode = 'off' + } + else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { + mode = 'heat' + } + sendEvent(name: 'thermostatMode', value: mode) + + // determine if Hive heating relay is on + def stateHeatingRelay = data.nodes.attributes.stateHeatingRelay.reportedValue[0] + + log.debug "stateHeatingRelay: $stateHeatingRelay" + + if (stateHeatingRelay == "ON") { + sendEvent(name: 'thermostatOperatingState', value: "heating") + } + else { + sendEvent(name: 'thermostatOperatingState', value: "idle") + } + } +} + +def refresh() { + log.debug "Executing 'refresh'" + // TODO: handle 'refresh' command +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + if(!isLoggedIn()) { + log.debug "Need to login" + login(method, args, success) + return + } + log.debug "Using node id: $state.nodeid" + def methods = [ + 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], + 'temperature': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'], + 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + doRequest(request.uri, args, request.type, success) +} + +// Need to be logged in before this is called. So don't call this. Call api. +def doRequest(uri, args, type, success) { + log.debug "Calling doRequest()" + log.debug "Calling $type : $uri : $args" + + def params = [ + uri: uri, + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ], + body: args + ] + + log.debug params + + def postRequest = { response -> + success.call(response) + } + + + if (type == 'post') { + httpPostJson(params, postRequest) + } else if (type == 'put') { + httpPutJson(params, postRequest) + } else if (type == 'get') { + httpGet(params, postRequest) + } + +} + +def getNodeId () { + log.debug "Calling getNodeId()" + //get thermostat node id + log.debug "Using session id, $data.auth.sessions[0].id" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ] + ] + + state.nodeid = '' + httpGet(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + + response.data.nodes.each { + log.debug "node name $it.name" + if (it.name == settings.receiver) + { + state.nodeid = it.id + } + } + + log.debug "nodeid: $state.nodeid" + } +} + +def login(method = null, args = [], success = {}) { + log.debug "Calling login()" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', + contentType: 'application/json', + headers: [ + 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Smartthings Hive Device Type', + ], + body: [ + sessions: [ [username: settings.username, + password: settings.password, + caller: 'smartthings']] + ] + ] + + state.cookie = '' + + httpPostJson(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + data.auth = response.data + + // set the expiration to 5 minutes + data.auth.expires_at = new Date().getTime() + 300000; + + state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) + log.debug "Adding cookie to collection: $cookie" + log.debug "auth: $data.auth" + log.debug "cookie: $state.cookie" + log.debug "sessionid: $data.auth.sessions[0].id" + + getNodeId() + + api(method, args, success) + + } +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $data.auth" + if(!data.auth) { + log.debug "No data.auth" + return false + } + + def now = new Date().getTime(); + return data.auth.expires_at > now +} \ No newline at end of file From b2348a572b03435721c3b3eead16b1e8fb4c679a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 19 Nov 2015 13:38:07 +0000 Subject: [PATCH 002/685] Updates to instructions --- .../hive-active-heating.groovy | 425 ++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy new file mode 100644 index 00000000000..8b07ac6e856 --- /dev/null +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -0,0 +1,425 @@ +/** + * Hive Active Heating + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) + * Name: Hive Active Heating + * Author: alyc100 + * Capabilities: + * Polling + * Refresh + * Temperature Measurement + * Thermostat Mode + * Thermostat Operating State + * Thermostat Heating Setpoint + * + * Custom Commands: + * setThermostatMode + * setHeatingSetpoint + * heatingSetpointUp + * heatingSetpointDown + * + * 2. Create a new device (https://graph.api.smartthings.com/device/list) + * Name: Your Choice + * Device Network Id: Your Choice + * Type: Hive Active Heating (should be the last option) + * Location: Choose the correct location + * Hub/Group: Leave blank + * + * 3. Update device preferences + * Click on the new device to see the details. + * Click the edit button next to Preferences + * Fill in your your Hive user name, Hive password. + * To find your Receiver nickname, login to http://www.hivehome.com. Click on 'Manage Devices'. + * You should see 3 devices including the Hub, Thermostat and Receiver. It is the Receiver nickname you need. + * + * 4. It should be done. + * + * 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. + * + */ +preferences { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") + input("password", "password", title: "Password", description: "Your Hive password") + input("receiver", "text", title: "Receiver", description: "Your receiver nickname") +} + +metadata { + definition (name: "Hive Active Heating", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Temperature Measurement" + capability "Thermostat Heating Setpoint" + capability "Thermostat Mode" + capability "Thermostat Operating State" + + command "heatingSetpointUp" + command "heatingSetpointDown" + command "setThermostatMode" + command "setHeatingSetpoint" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "Thermostat", width: 6, height: 4, type:"thermostat") { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL"){ + attributeState "default", label: '${currentValue}°C', backgroundColors: [ + // Celsius Color Range + [value: 0, color: "#153591"], + [value: 7, color: "#1e9cbb"], + [value: 15, color: "#90d2a7"], + [value: 23, color: "#44b621"], + [value: 29, color: "#f1d801"], + [value: 33, color: "#d04e00"], + [value: 36, color: "#bc2323"] + ]} + + main "Thermostat" + details "Thermostat" + } + + controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { + state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" + } + //standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + // state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" + //} + + //standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + // state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" + //} + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { + state "default", label:'${currentValue}°C', unit:"Heat", + backgroundColors:[ + [value: 0, color: "#153591"], + [value: 7, color: "#1e9cbb"], + [value: 15, color: "#90d2a7"], + [value: 23, color: "#44b621"], + [value: 29, color: "#f1d801"], + [value: 33, color: "#d04e00"], + [value: 36, color: "#bc2323"] + ] + } + + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "idle", action:"polling.poll", label:'${name}', icon: "st.Home.home18" + state "heating", action:"polling.poll", label:'${name}', icon: "st.Home.home1" + } + + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat") { + state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" + state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#bc2323" + } + + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("auto", action:"thermostat.off", icon: "st.thermostat.auto") + state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off") + state("cool", action:"thermostat.heat", icon: "st.thermostat.cool") + state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") + } + + standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + main(["temperature", "thermostatOperatingState"]) + + // ============================================================ + // Slider or Buttons... + // To expose buttons, comment out the first detials line below and uncomment the second details line below. + // To expose sliders, uncomment the first details line below and comment out the second details line below. + + //details(["heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) + details(["heatingSetpoint", "heatSliderControl", "thermostatMode", "thermostatOperatingState", "refresh"]) + + // ============================================================ + + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'temperature' attribute + // TODO: handle 'heatingSetpoint' attribute + // TODO: handle 'thermostatSetpoint' attribute + // TODO: handle 'thermostatMode' attribute + // TODO: handle 'thermostatOperatingState' attribute +} + +// handle commands +def setHeatingSetpoint(temp) { + def latestThermostatMode = device.latestState('thermostatMode') + if (temp < 5) { + temp = 5 + } + if (temp > 32) { + temp = 32 + } + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} + def args = [ + nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] + ] + + api('temperature', args) { + runIn(2, poll) + } + +} + +def heatingSetpointUp(){ + int newSetpoint = device.currentValue("heatingSetpoint") + 1 + log.debug "Setting heat set point up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def heatingSetpointDown(){ + int newSetpoint = device.currentValue("heatingSetpoint") - 1 + log.debug "Setting heat set point down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def off() { + setThermostatMode('off') +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + setThermostatMode('heat') +} + +def auto() { + setThermostatMode('auto') +} + +def setThermostatMode(mode) { + mode = mode == 'emergency heat'? 'heat' : mode + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] + ] + if (mode == 'off') { + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] + ] + } else if (mode == 'heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + } + + api('thermostat_mode', args) { + mode = mode == 'range' ? 'auto' : mode + runIn(2, poll) + } +} + +// handle commands +def poll() { + log.debug "Executing 'poll'" + api('status', []) { + data.nodes = it.data.nodes + + // get temperature status + def temperature = data.nodes.attributes.temperature.reportedValue[0] + def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + + sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") + sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") + + // determine hive operating mode + def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] + def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] + + log.debug "activeHeatCoolMode: $activeHeatCoolMode" + log.debug "activeScheduleLock: $activeScheduleLock" + + def mode = 'auto' + + if (activeHeatCoolMode == "OFF") { + mode = 'off' + } + else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { + mode = 'heat' + } + sendEvent(name: 'thermostatMode', value: mode) + + // determine if Hive heating relay is on + def stateHeatingRelay = data.nodes.attributes.stateHeatingRelay.reportedValue[0] + + log.debug "stateHeatingRelay: $stateHeatingRelay" + + if (stateHeatingRelay == "ON") { + sendEvent(name: 'thermostatOperatingState', value: "heating") + } + else { + sendEvent(name: 'thermostatOperatingState', value: "idle") + } + } +} + +def refresh() { + log.debug "Executing 'refresh'" + // TODO: handle 'refresh' command +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + if(!isLoggedIn()) { + log.debug "Need to login" + login(method, args, success) + return + } + log.debug "Using node id: $state.nodeid" + def methods = [ + 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], + 'temperature': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'], + 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + doRequest(request.uri, args, request.type, success) +} + +// Need to be logged in before this is called. So don't call this. Call api. +def doRequest(uri, args, type, success) { + log.debug "Calling doRequest()" + log.debug "Calling $type : $uri : $args" + + def params = [ + uri: uri, + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ], + body: args + ] + + log.debug params + + def postRequest = { response -> + success.call(response) + } + + + if (type == 'post') { + httpPostJson(params, postRequest) + } else if (type == 'put') { + httpPutJson(params, postRequest) + } else if (type == 'get') { + httpGet(params, postRequest) + } + +} + +def getNodeId () { + log.debug "Calling getNodeId()" + //get thermostat node id + log.debug "Using session id, $data.auth.sessions[0].id" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ] + ] + + state.nodeid = '' + httpGet(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + + response.data.nodes.each { + log.debug "node name $it.name" + if (it.name == settings.receiver) + { + state.nodeid = it.id + } + } + + log.debug "nodeid: $state.nodeid" + } +} + +def login(method = null, args = [], success = {}) { + log.debug "Calling login()" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', + contentType: 'application/json', + headers: [ + 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Smartthings Hive Device Type', + ], + body: [ + sessions: [ [username: settings.username, + password: settings.password, + caller: 'smartthings']] + ] + ] + + state.cookie = '' + + httpPostJson(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + data.auth = response.data + + // set the expiration to 5 minutes + data.auth.expires_at = new Date().getTime() + 300000; + + state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) + log.debug "Adding cookie to collection: $cookie" + log.debug "auth: $data.auth" + log.debug "cookie: $state.cookie" + log.debug "sessionid: $data.auth.sessions[0].id" + + getNodeId() + + api(method, args, success) + + } +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $data.auth" + if(!data.auth) { + log.debug "No data.auth" + return false + } + + def now = new Date().getTime(); + return data.auth.expires_at > now +} \ No newline at end of file From 7f1e4e5fb000db0dcc18480ea2afcb6d8a109fc0 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Thu, 19 Nov 2015 13:43:25 +0000 Subject: [PATCH 003/685] Rename device type to Hive Active Heating --- .../hive-active-heating-2.groovy | 425 ------------------ 1 file changed, 425 deletions(-) delete mode 100644 devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy diff --git a/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy b/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy deleted file mode 100644 index 0038e3f72b0..00000000000 --- a/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy +++ /dev/null @@ -1,425 +0,0 @@ -/** - * Hive Active Heating - * - * Copyright 2015 Alex Lee Yuk Cheung - * - * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) - * Name: Hive Active Heating - * Author: alyc100 - * Capabilities: - * Polling - * Refresh - * Temperature Measurement - * Thermostat Mode - * Thermostat Operating State - * Thermostat Heating Setpoint - * - * Custom Commands: - * setThermostatMode - * setHeatingSetpoint - * heatingSetpointUp - * heatingSetpointDown - * - * 2. Create a new device (https://graph.api.smartthings.com/device/list) - * Name: Your Choice - * Device Network Id: Your Choice - * Type: Nest (should be the last option) - * Location: Choose the correct location - * Hub/Group: Leave blank - * - * 3. Update device preferences - * Click on the new device to see the details. - * Click the edit button next to Preferences - * Fill in your your Hive user name, Hive password. - * To find your Receiver nickname, login to http://www.hivehome.com. Click on 'Manage Devices'. - * You should see 3 devices including the Hub, Thermostat and Receiver. It is the Receiver nickname you need. - * - * 4. It should be done. - * - * 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. - * - */ -preferences { - input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") - input("password", "password", title: "Password", description: "Your Hive password") - input("receiver", "text", title: "Receiver", description: "Your receiver nickname") -} - -metadata { - definition (name: "Hive Active Heating", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { - capability "Actuator" - capability "Polling" - capability "Refresh" - capability "Temperature Measurement" - capability "Thermostat Heating Setpoint" - capability "Thermostat Mode" - capability "Thermostat Operating State" - - command "heatingSetpointUp" - command "heatingSetpointDown" - command "setThermostatMode" - command "setHeatingSetpoint" - } - - simulator { - // TODO: define status and reply messages here - } - - tiles(scale: 2) { - - multiAttributeTile(name: "Thermostat", width: 6, height: 4, type:"thermostat") { - tileAttribute("device.temperature", key:"PRIMARY_CONTROL"){ - attributeState "default", label: '${currentValue}°C', backgroundColors: [ - // Celsius Color Range - [value: 0, color: "#153591"], - [value: 7, color: "#1e9cbb"], - [value: 15, color: "#90d2a7"], - [value: 23, color: "#44b621"], - [value: 29, color: "#f1d801"], - [value: 33, color: "#d04e00"], - [value: 36, color: "#bc2323"] - ]} - - main "Thermostat" - details "Thermostat" - } - - controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { - state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" - } - //standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { - // state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" - //} - - //standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { - // state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" - //} - - valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { - state "default", label:'${currentValue}°C', unit:"Heat", - backgroundColors:[ - [value: 0, color: "#153591"], - [value: 7, color: "#1e9cbb"], - [value: 15, color: "#90d2a7"], - [value: 23, color: "#44b621"], - [value: 29, color: "#f1d801"], - [value: 33, color: "#d04e00"], - [value: 36, color: "#bc2323"] - ] - } - - standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "idle", action:"polling.poll", label:'${name}', icon: "st.Home.home18" - state "heating", action:"polling.poll", label:'${name}', icon: "st.Home.home1" - } - - standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat") { - state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" - state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#bc2323" - } - - standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state("auto", action:"thermostat.off", icon: "st.thermostat.auto") - state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off") - state("cool", action:"thermostat.heat", icon: "st.thermostat.cool") - state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") - } - - standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") - } - - main(["temperature", "thermostatOperatingState"]) - - // ============================================================ - // Slider or Buttons... - // To expose buttons, comment out the first detials line below and uncomment the second details line below. - // To expose sliders, uncomment the first details line below and comment out the second details line below. - - //details(["heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) - details(["heatingSetpoint", "heatSliderControl", "thermostatMode", "thermostatOperatingState", "refresh"]) - - // ============================================================ - - } -} - -// parse events into attributes -def parse(String description) { - log.debug "Parsing '${description}'" - // TODO: handle 'temperature' attribute - // TODO: handle 'heatingSetpoint' attribute - // TODO: handle 'thermostatSetpoint' attribute - // TODO: handle 'thermostatMode' attribute - // TODO: handle 'thermostatOperatingState' attribute -} - -// handle commands -def setHeatingSetpoint(temp) { - def latestThermostatMode = device.latestState('thermostatMode') - if (temp < 5) { - temp = 5 - } - if (temp > 32) { - temp = 32 - } - // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} - def args = [ - nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] - ] - - api('temperature', args) { - runIn(2, poll) - } - -} - -def heatingSetpointUp(){ - int newSetpoint = device.currentValue("heatingSetpoint") + 1 - log.debug "Setting heat set point up to: ${newSetpoint}" - setHeatingSetpoint(newSetpoint) -} - -def heatingSetpointDown(){ - int newSetpoint = device.currentValue("heatingSetpoint") - 1 - log.debug "Setting heat set point down to: ${newSetpoint}" - setHeatingSetpoint(newSetpoint) -} - -def off() { - setThermostatMode('off') -} - -def heat() { - setThermostatMode('heat') -} - -def emergencyHeat() { - setThermostatMode('heat') -} - -def auto() { - setThermostatMode('auto') -} - -def setThermostatMode(mode) { - mode = mode == 'emergency heat'? 'heat' : mode - def args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] - ] - if (mode == 'off') { - args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] - ] - } else if (mode == 'heat') { - //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} - args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] - ] - } - - api('thermostat_mode', args) { - mode = mode == 'range' ? 'auto' : mode - runIn(2, poll) - } -} - -// handle commands -def poll() { - log.debug "Executing 'poll'" - api('status', []) { - data.nodes = it.data.nodes - - // get temperature status - def temperature = data.nodes.attributes.temperature.reportedValue[0] - def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] - - sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") - sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") - - // determine hive operating mode - def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] - def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] - - log.debug "activeHeatCoolMode: $activeHeatCoolMode" - log.debug "activeScheduleLock: $activeScheduleLock" - - def mode = 'auto' - - if (activeHeatCoolMode == "OFF") { - mode = 'off' - } - else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { - mode = 'heat' - } - sendEvent(name: 'thermostatMode', value: mode) - - // determine if Hive heating relay is on - def stateHeatingRelay = data.nodes.attributes.stateHeatingRelay.reportedValue[0] - - log.debug "stateHeatingRelay: $stateHeatingRelay" - - if (stateHeatingRelay == "ON") { - sendEvent(name: 'thermostatOperatingState', value: "heating") - } - else { - sendEvent(name: 'thermostatOperatingState', value: "idle") - } - } -} - -def refresh() { - log.debug "Executing 'refresh'" - // TODO: handle 'refresh' command -} - -def api(method, args = [], success = {}) { - log.debug "Executing 'api'" - - if(!isLoggedIn()) { - log.debug "Need to login" - login(method, args, success) - return - } - log.debug "Using node id: $state.nodeid" - def methods = [ - 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], - 'temperature': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'], - 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] - ] - - def request = methods.getAt(method) - - log.debug "Starting $method : $args" - doRequest(request.uri, args, request.type, success) -} - -// Need to be logged in before this is called. So don't call this. Call api. -def doRequest(uri, args, type, success) { - log.debug "Calling doRequest()" - log.debug "Calling $type : $uri : $args" - - def params = [ - uri: uri, - contentType: 'application/json', - headers: [ - 'Cookie': state.cookie, - 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', - 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" - ], - body: args - ] - - log.debug params - - def postRequest = { response -> - success.call(response) - } - - - if (type == 'post') { - httpPostJson(params, postRequest) - } else if (type == 'put') { - httpPutJson(params, postRequest) - } else if (type == 'get') { - httpGet(params, postRequest) - } - -} - -def getNodeId () { - log.debug "Calling getNodeId()" - //get thermostat node id - log.debug "Using session id, $data.auth.sessions[0].id" - def params = [ - uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', - contentType: 'application/json', - headers: [ - 'Cookie': state.cookie, - 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', - 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" - ] - ] - - state.nodeid = '' - httpGet(params) {response -> - log.debug "Request was successful, $response.status" - log.debug response.headers - - response.data.nodes.each { - log.debug "node name $it.name" - if (it.name == settings.receiver) - { - state.nodeid = it.id - } - } - - log.debug "nodeid: $state.nodeid" - } -} - -def login(method = null, args = [], success = {}) { - log.debug "Calling login()" - def params = [ - uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', - contentType: 'application/json', - headers: [ - 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'Smartthings Hive Device Type', - ], - body: [ - sessions: [ [username: settings.username, - password: settings.password, - caller: 'smartthings']] - ] - ] - - state.cookie = '' - - httpPostJson(params) {response -> - log.debug "Request was successful, $response.status" - log.debug response.headers - data.auth = response.data - - // set the expiration to 5 minutes - data.auth.expires_at = new Date().getTime() + 300000; - - state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) - log.debug "Adding cookie to collection: $cookie" - log.debug "auth: $data.auth" - log.debug "cookie: $state.cookie" - log.debug "sessionid: $data.auth.sessions[0].id" - - getNodeId() - - api(method, args, success) - - } -} - -def isLoggedIn() { - log.debug "Calling isLoggedIn()" - log.debug "isLoggedIn state $data.auth" - if(!data.auth) { - log.debug "No data.auth" - return false - } - - def now = new Date().getTime(); - return data.auth.expires_at > now -} \ No newline at end of file From d60aa067c8c441c592be254f283902d7bccc3e29 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 19 Nov 2015 15:30:10 +0000 Subject: [PATCH 004/685] Added Thermostat capability so Hive can be used with SmartTiles application --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 8b07ac6e856..95d2e7a5c4d 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -10,6 +10,7 @@ * Polling * Refresh * Temperature Measurement + * Thermostat * Thermostat Mode * Thermostat Operating State * Thermostat Heating Setpoint @@ -58,6 +59,7 @@ metadata { capability "Polling" capability "Refresh" capability "Temperature Measurement" + capability "Thermostat" capability "Thermostat Heating Setpoint" capability "Thermostat Mode" capability "Thermostat Operating State" From 0bde3f2919263f42484de8f63cc6628a96bb6f9d Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 19 Nov 2015 16:54:29 +0000 Subject: [PATCH 005/685] Clean up some tile code. Improvements to operating mode tile. --- .../hive-active-heating.groovy | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 95d2e7a5c4d..9e8f76817d3 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -117,12 +117,7 @@ metadata { ] } - standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "idle", action:"polling.poll", label:'${name}', icon: "st.Home.home18" - state "heating", action:"polling.poll", label:'${name}', icon: "st.Home.home1" - } - - standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat") { + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#bc2323" } @@ -134,9 +129,21 @@ metadata { state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") } - standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") } + + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"auto", icon:"st.thermostat.auto" + } + + standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"heat", icon:"st.thermostat.heat" + } + + standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"off", icon:"st.thermostat.heating-cooling-off" + } main(["temperature", "thermostatOperatingState"]) From 2041813edc70bd980d25af545fbfb791ffa6dd14 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 19 Nov 2015 17:06:52 +0000 Subject: [PATCH 006/685] Added control tiles to set thermostat mode for Heating. --- .../hive-active-heating.src/hive-active-heating.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 9e8f76817d3..de69b5cfe73 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -134,15 +134,15 @@ metadata { } standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"auto", icon:"st.thermostat.auto" + state "default", action:"auto", label:'Schedule' } standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"heat", icon:"st.thermostat.heat" + state "default", action:"heat", label:'Manual' } standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"off", icon:"st.thermostat.heating-cooling-off" + state "default", action:"off", label:'Off' } main(["temperature", "thermostatOperatingState"]) @@ -153,7 +153,7 @@ metadata { // To expose sliders, uncomment the first details line below and comment out the second details line below. //details(["heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) - details(["heatingSetpoint", "heatSliderControl", "thermostatMode", "thermostatOperatingState", "refresh"]) + details(["thermostatMode", "thermostatOperatingState", "refresh", "heatingSetpoint", "heatSliderControl", "mode_auto", "mode_manual", "mode_off"]) // ============================================================ From 794123de45f56b6ecc3996f7d9524abd63c5ae15 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 19 Nov 2015 17:35:07 +0000 Subject: [PATCH 007/685] Added logic to automatically determine correct device from Hive negating the need to add Receiver nickname in config --- .../hive-active-heating.src/hive-active-heating.groovy | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index de69b5cfe73..90889279fd3 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -32,8 +32,6 @@ * Click on the new device to see the details. * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. - * To find your Receiver nickname, login to http://www.hivehome.com. Click on 'Manage Devices'. - * You should see 3 devices including the Hub, Thermostat and Receiver. It is the Receiver nickname you need. * * 4. It should be done. * @@ -50,7 +48,6 @@ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") input("password", "password", title: "Password", description: "Your Hive password") - input("receiver", "text", title: "Receiver", description: "Your receiver nickname") } metadata { @@ -369,9 +366,9 @@ def getNodeId () { log.debug response.headers response.data.nodes.each { - log.debug "node name $it.name" - if (it.name == settings.receiver) - { + log.debug "node name $it.name" + if (it.attributes.supportsHeatCoolModes != null) + { state.nodeid = it.id } } From fc9b5d6a1fdac33c2860dd4824d467aa29d87b1e Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 19 Nov 2015 21:31:21 +0000 Subject: [PATCH 008/685] Altered temperature colours to match Hive branding (I was bored) --- .../hive-active-heating.groovy | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 90889279fd3..49558a7635a 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -44,6 +44,12 @@ * 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 + * v1.0 - Initial Release + * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule + * v1.2 - Removed requirement to type in Receiver Nickname from Hive + * v1.3 - Altered temperature colours to match Hive branding (I was bored) */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -77,13 +83,13 @@ metadata { tileAttribute("device.temperature", key:"PRIMARY_CONTROL"){ attributeState "default", label: '${currentValue}°C', backgroundColors: [ // Celsius Color Range - [value: 0, color: "#153591"], - [value: 7, color: "#1e9cbb"], - [value: 15, color: "#90d2a7"], - [value: 23, color: "#44b621"], - [value: 29, color: "#f1d801"], - [value: 33, color: "#d04e00"], - [value: 36, color: "#bc2323"] + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] ]} main "Thermostat" @@ -104,13 +110,13 @@ metadata { valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { state "default", label:'${currentValue}°C', unit:"Heat", backgroundColors:[ - [value: 0, color: "#153591"], - [value: 7, color: "#1e9cbb"], - [value: 15, color: "#90d2a7"], - [value: 23, color: "#44b621"], - [value: 29, color: "#f1d801"], - [value: 33, color: "#d04e00"], - [value: 36, color: "#bc2323"] + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] ] } @@ -142,7 +148,7 @@ metadata { state "default", action:"off", label:'Off' } - main(["temperature", "thermostatOperatingState"]) + main(["temperature", "thermostatMode"]) // ============================================================ // Slider or Buttons... @@ -150,7 +156,7 @@ metadata { // To expose sliders, uncomment the first details line below and comment out the second details line below. //details(["heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) - details(["thermostatMode", "thermostatOperatingState", "refresh", "heatingSetpoint", "heatSliderControl", "mode_auto", "mode_manual", "mode_off"]) + details(["mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "thermostatMode", "thermostatOperatingState", "refresh"]) // ============================================================ From 07b6cdaaef87d9fbe22a47d42ab83e75c589ad72 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 19 Nov 2015 22:54:32 +0000 Subject: [PATCH 009/685] Enabled options to choose sliders or buttons for Hive heating temperature control --- .../hive-active-heating.groovy | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 49558a7635a..030183ecc77 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -47,9 +47,10 @@ * * VERSION HISTORY * v1.0 - Initial Release - * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule - * v1.2 - Removed requirement to type in Receiver Nickname from Hive - * v1.3 - Altered temperature colours to match Hive branding (I was bored) + * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule. + * v1.2 - Removed requirement to type in Receiver Nickname from Hive. + * v1.3 - Altered temperature colours to match Hive branding (I was bored). + * v1.4 - Enable options for sliders or buttons for temperature control. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -99,13 +100,14 @@ metadata { controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" } - //standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { - // state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" - //} + + standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" + } - //standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { - // state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" - //} + standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" + } valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { state "default", label:'${currentValue}°C', unit:"Heat", @@ -152,10 +154,10 @@ metadata { // ============================================================ // Slider or Buttons... - // To expose buttons, comment out the first detials line below and uncomment the second details line below. - // To expose sliders, uncomment the first details line below and comment out the second details line below. + // To expose sliders, comment out the first detials line below and uncomment the second details line below. + // To expose buttons, uncomment the first details line below and comment out the second details line below. - //details(["heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) + //details(["mode_auto", "mode_manual", "mode_off", "heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) details(["mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "thermostatMode", "thermostatOperatingState", "refresh"]) // ============================================================ From cb3a393d089309bf39e5d6715358d18658b9c887 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 12:27:27 +0000 Subject: [PATCH 010/685] UI improvements. --- .../hive-active-heating.groovy | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 030183ecc77..a334431b8ee 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -46,11 +46,15 @@ * * * VERSION HISTORY + * (19/11/2015) * v1.0 - Initial Release * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule. * v1.2 - Removed requirement to type in Receiver Nickname from Hive. * v1.3 - Altered temperature colours to match Hive branding (I was bored). * v1.4 - Enable options for sliders or buttons for temperature control. + * + * (20/11/2015) + * v1.5 - UI improvements. Status now in text form and shown on top tile. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -81,8 +85,8 @@ metadata { tiles(scale: 2) { multiAttributeTile(name: "Thermostat", width: 6, height: 4, type:"thermostat") { - tileAttribute("device.temperature", key:"PRIMARY_CONTROL"){ - attributeState "default", label: '${currentValue}°C', backgroundColors: [ + tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ + attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ // Celsius Color Range [value: 0, color: "#50b5dd"], [value: 10, color: "#43a575"], @@ -92,6 +96,9 @@ metadata { [value: 25, color: "#d9372b"], [value: 29, color: "#b9203b"] ]} + tileAttribute ("statusText", key: "SECONDARY_CONTROL") { + attributeState "statusText", label:'${currentValue}' + } main "Thermostat" details "Thermostat" @@ -110,7 +117,7 @@ metadata { } valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { - state "default", label:'${currentValue}°C', unit:"Heat", + state "default", label:'${currentValue}°', unit:"C", backgroundColors:[ [value: 0, color: "#50b5dd"], [value: 10, color: "#43a575"], @@ -158,7 +165,7 @@ metadata { // To expose buttons, uncomment the first details line below and comment out the second details line below. //details(["mode_auto", "mode_manual", "mode_off", "heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) - details(["mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "thermostatMode", "thermostatOperatingState", "refresh"]) + details(["mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) // ============================================================ @@ -190,7 +197,7 @@ def setHeatingSetpoint(temp) { ] api('temperature', args) { - runIn(2, poll) + runIn(3, poll) } } @@ -241,7 +248,7 @@ def setThermostatMode(mode) { api('thermostat_mode', args) { mode = mode == 'range' ? 'auto' : mode - runIn(2, poll) + runIn(3, poll) } } @@ -251,6 +258,9 @@ def poll() { api('status', []) { data.nodes = it.data.nodes + //Construct status message + def statusMsg = "Currently" + // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] @@ -269,9 +279,14 @@ def poll() { if (activeHeatCoolMode == "OFF") { mode = 'off' + statusMsg = statusMsg + " set to OFF" } else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { mode = 'heat' + statusMsg = statusMsg + " set to MANUAL" + } + else { + statusMsg = statusMsg + " set to SCHEDULE" } sendEvent(name: 'thermostatMode', value: mode) @@ -282,10 +297,14 @@ def poll() { if (stateHeatingRelay == "ON") { sendEvent(name: 'thermostatOperatingState', value: "heating") + statusMsg = statusMsg + " and is HEATING" } else { sendEvent(name: 'thermostatOperatingState', value: "idle") - } + statusMsg = statusMsg + " and is IDLE" + } + + sendEvent("name":"statusText", "value":statusMsg) } } From 49b435d15fbf1077b9b5c9f0e9bb1ab1d2d38eac Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 13:05:29 +0000 Subject: [PATCH 011/685] Minor bug fixes --- .../hive-active-heating.src/hive-active-heating.groovy | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index a334431b8ee..02f3d6b83c0 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -46,15 +46,11 @@ * * * VERSION HISTORY - * (19/11/2015) * v1.0 - Initial Release * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule. * v1.2 - Removed requirement to type in Receiver Nickname from Hive. * v1.3 - Altered temperature colours to match Hive branding (I was bored). * v1.4 - Enable options for sliders or buttons for temperature control. - * - * (20/11/2015) - * v1.5 - UI improvements. Status now in text form and shown on top tile. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -97,7 +93,7 @@ metadata { [value: 29, color: "#b9203b"] ]} tileAttribute ("statusText", key: "SECONDARY_CONTROL") { - attributeState "statusText", label:'${currentValue}' + attributeState "hiveHeating", label:'${currentValue}' } main "Thermostat" @@ -304,7 +300,7 @@ def poll() { statusMsg = statusMsg + " and is IDLE" } - sendEvent("name":"statusText", "value":statusMsg) + sendEvent("name":"hiveHeating", "value":statusMsg) } } From 4640b8d6b080187b9406d3bb302fbb94167c322e Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 13:14:36 +0000 Subject: [PATCH 012/685] Updates to control button icons. --- .../hive-active-heating.groovy | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 02f3d6b83c0..e53ba9ef650 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -46,11 +46,16 @@ * * * VERSION HISTORY + * 19.11.2015 * v1.0 - Initial Release * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule. * v1.2 - Removed requirement to type in Receiver Nickname from Hive. * v1.3 - Altered temperature colours to match Hive branding (I was bored). * v1.4 - Enable options for sliders or buttons for temperature control. + * + * 20.11.2015 + * v1.5 - Clean up UI and make user friendly status message appear on top panel + * v1.6 - Added icons to control buttons. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -142,15 +147,15 @@ metadata { } standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"auto", label:'Schedule' + state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" } standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"heat", label:'Manual' + state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" } standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"off", label:'Off' + state "default", action:"off", label:'Off', icon:"st.Seasonal Winter.seasonal-winter-006" } main(["temperature", "thermostatMode"]) From 61abb74559180a848f29dbe3609a78e0e34bb0c1 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 13:16:32 +0000 Subject: [PATCH 013/685] Inceased poll delay for command buttons --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index e53ba9ef650..c6a9f06d503 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -55,7 +55,7 @@ * * 20.11.2015 * v1.5 - Clean up UI and make user friendly status message appear on top panel - * v1.6 - Added icons to control buttons. + * v1.6 - Added icons to control buttons. Increased poll delay time to ensure UI is updated on control press. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") From 5474b558c6a06d1e18e6011003b8017f54353843 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 13:34:35 +0000 Subject: [PATCH 014/685] Very minor bug fixes --- .../hive-active-heating.src/hive-active-heating.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index c6a9f06d503..4bde03a6f28 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -97,7 +97,7 @@ metadata { [value: 25, color: "#d9372b"], [value: 29, color: "#b9203b"] ]} - tileAttribute ("statusText", key: "SECONDARY_CONTROL") { + tileAttribute ("hiveHeating", key: "SECONDARY_CONTROL") { attributeState "hiveHeating", label:'${currentValue}' } @@ -198,7 +198,7 @@ def setHeatingSetpoint(temp) { ] api('temperature', args) { - runIn(3, poll) + runIn(4, poll) } } @@ -249,7 +249,7 @@ def setThermostatMode(mode) { api('thermostat_mode', args) { mode = mode == 'range' ? 'auto' : mode - runIn(3, poll) + runIn(4, poll) } } From 0b924a183c93837360db118e1123cb3a270ccb60 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 14:30:55 +0000 Subject: [PATCH 015/685] Minor UI changes --- .../hive-active-heating.src/hive-active-heating.groovy | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 4bde03a6f28..5b287daf4a7 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -132,13 +132,12 @@ metadata { standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" - state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#bc2323" + state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#EC6E05" } standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state("auto", action:"thermostat.off", icon: "st.thermostat.auto") - state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off") - state("cool", action:"thermostat.heat", icon: "st.thermostat.cool") + state("auto", action:"thermostat.off", icon: "st.Office.office7") + state("off", action:"thermostat.heat", icon: "st.thermostat.heating-cooling-off") state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") } From dc9887196969b4b1e6461e02699e6b322573cc59 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 14:36:55 +0000 Subject: [PATCH 016/685] Changes to synchronise upcoming Hive Hot Water UI --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 5b287daf4a7..e1c08cde1fb 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -154,7 +154,7 @@ metadata { } standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"off", label:'Off', icon:"st.Seasonal Winter.seasonal-winter-006" + state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } main(["temperature", "thermostatMode"]) From d74725a442cb9ce0c101a6d3dc6ed591d7fccfc0 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 15:15:48 +0000 Subject: [PATCH 017/685] Updated device name as there are incompatibilities with Hive 1 --- .../hive-active-heating-2.groovy | 459 ++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100644 devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy diff --git a/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy b/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy new file mode 100644 index 00000000000..29ba16f49b2 --- /dev/null +++ b/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy @@ -0,0 +1,459 @@ +/** + * Hive Active Heating 2 + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) + * Name: Hive Active Heating + * Author: alyc100 + * Capabilities: + * Polling + * Refresh + * Temperature Measurement + * Thermostat + * Thermostat Mode + * Thermostat Operating State + * Thermostat Heating Setpoint + * + * Custom Commands: + * setThermostatMode + * setHeatingSetpoint + * heatingSetpointUp + * heatingSetpointDown + * + * 2. Create a new device (https://graph.api.smartthings.com/device/list) + * Name: Your Choice + * Device Network Id: Your Choice + * Type: Hive Active Heating (should be the last option) + * Location: Choose the correct location + * Hub/Group: Leave blank + * + * 3. Update device preferences + * Click on the new device to see the details. + * Click the edit button next to Preferences + * Fill in your your Hive user name, Hive password. + * + * 4. It should be done. + * + * 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 + * 19.11.2015 + * v1.0 - Initial Release + * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule. + * v1.2 - Removed requirement to type in Receiver Nickname from Hive. + * v1.3 - Altered temperature colours to match Hive branding (I was bored). + * v1.4 - Enable options for sliders or buttons for temperature control. + * + * 20.11.2015 + * v1.5 - Clean up UI and make user friendly status message appear on top panel + * v1.6 - Added icons to control buttons. Increased poll delay time to ensure UI is updated on control press. + * v1.6.1 - Looks like only Hive 2 is supported with this API. Changing name of app + */ +preferences { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") + input("password", "password", title: "Password", description: "Your Hive password") +} + +metadata { + definition (name: "Hive Active Heating 2", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Temperature Measurement" + capability "Thermostat" + capability "Thermostat Heating Setpoint" + capability "Thermostat Mode" + capability "Thermostat Operating State" + + command "heatingSetpointUp" + command "heatingSetpointDown" + command "setThermostatMode" + command "setHeatingSetpoint" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "Thermostat", width: 6, height: 4, type:"thermostat") { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ + attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ + // Celsius Color Range + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ]} + tileAttribute ("hiveHeating", key: "SECONDARY_CONTROL") { + attributeState "hiveHeating", label:'${currentValue}' + } + + main "Thermostat" + details "Thermostat" + } + + controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { + state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" + } + + standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" + } + + standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" + } + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { + state "default", label:'${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ] + } + + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" + state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#EC6E05" + } + + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("auto", action:"thermostat.off", icon: "st.Office.office7") + state("off", action:"thermostat.heat", icon: "st.thermostat.heating-cooling-off") + state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") + } + + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" + } + + standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" + } + + standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"off", icon:"st.thermostat.heating-cooling-off" + } + + main(["temperature", "thermostatMode"]) + + // ============================================================ + // Slider or Buttons... + // To expose sliders, comment out the first detials line below and uncomment the second details line below. + // To expose buttons, uncomment the first details line below and comment out the second details line below. + + //details(["mode_auto", "mode_manual", "mode_off", "heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) + details(["mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) + + // ============================================================ + + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'temperature' attribute + // TODO: handle 'heatingSetpoint' attribute + // TODO: handle 'thermostatSetpoint' attribute + // TODO: handle 'thermostatMode' attribute + // TODO: handle 'thermostatOperatingState' attribute +} + +// handle commands +def setHeatingSetpoint(temp) { + def latestThermostatMode = device.latestState('thermostatMode') + if (temp < 5) { + temp = 5 + } + if (temp > 32) { + temp = 32 + } + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} + def args = [ + nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] + ] + + api('temperature', args) { + runIn(4, poll) + } + +} + +def heatingSetpointUp(){ + int newSetpoint = device.currentValue("heatingSetpoint") + 1 + log.debug "Setting heat set point up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def heatingSetpointDown(){ + int newSetpoint = device.currentValue("heatingSetpoint") - 1 + log.debug "Setting heat set point down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def off() { + setThermostatMode('off') +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + setThermostatMode('heat') +} + +def auto() { + setThermostatMode('auto') +} + +def setThermostatMode(mode) { + mode = mode == 'emergency heat'? 'heat' : mode + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] + ] + if (mode == 'off') { + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] + ] + } else if (mode == 'heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + } + + api('thermostat_mode', args) { + mode = mode == 'range' ? 'auto' : mode + runIn(4, poll) + } +} + +// handle commands +def poll() { + log.debug "Executing 'poll'" + api('status', []) { + data.nodes = it.data.nodes + + //Construct status message + def statusMsg = "Currently" + + // get temperature status + def temperature = data.nodes.attributes.temperature.reportedValue[0] + def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + + sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") + sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") + + // determine hive operating mode + def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] + def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] + + log.debug "activeHeatCoolMode: $activeHeatCoolMode" + log.debug "activeScheduleLock: $activeScheduleLock" + + def mode = 'auto' + + if (activeHeatCoolMode == "OFF") { + mode = 'off' + statusMsg = statusMsg + " set to OFF" + } + else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { + mode = 'heat' + statusMsg = statusMsg + " set to MANUAL" + } + else { + statusMsg = statusMsg + " set to SCHEDULE" + } + sendEvent(name: 'thermostatMode', value: mode) + + // determine if Hive heating relay is on + def stateHeatingRelay = data.nodes.attributes.stateHeatingRelay.reportedValue[0] + + log.debug "stateHeatingRelay: $stateHeatingRelay" + + if (stateHeatingRelay == "ON") { + sendEvent(name: 'thermostatOperatingState', value: "heating") + statusMsg = statusMsg + " and is HEATING" + } + else { + sendEvent(name: 'thermostatOperatingState', value: "idle") + statusMsg = statusMsg + " and is IDLE" + } + + sendEvent("name":"hiveHeating", "value":statusMsg) + } +} + +def refresh() { + log.debug "Executing 'refresh'" + // TODO: handle 'refresh' command +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + if(!isLoggedIn()) { + log.debug "Need to login" + login(method, args, success) + return + } + log.debug "Using node id: $state.nodeid" + def methods = [ + 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], + 'temperature': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'], + 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + doRequest(request.uri, args, request.type, success) +} + +// Need to be logged in before this is called. So don't call this. Call api. +def doRequest(uri, args, type, success) { + log.debug "Calling doRequest()" + log.debug "Calling $type : $uri : $args" + + def params = [ + uri: uri, + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ], + body: args + ] + + log.debug params + + def postRequest = { response -> + success.call(response) + } + + + if (type == 'post') { + httpPostJson(params, postRequest) + } else if (type == 'put') { + httpPutJson(params, postRequest) + } else if (type == 'get') { + httpGet(params, postRequest) + } + +} + +def getNodeId () { + log.debug "Calling getNodeId()" + //get thermostat node id + log.debug "Using session id, $data.auth.sessions[0].id" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ] + ] + + state.nodeid = '' + httpGet(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + + response.data.nodes.each { + log.debug "node name $it.name" + if (it.attributes.supportsHeatCoolModes != null) + { + state.nodeid = it.id + } + } + + log.debug "nodeid: $state.nodeid" + } +} + +def login(method = null, args = [], success = {}) { + log.debug "Calling login()" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', + contentType: 'application/json', + headers: [ + 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Smartthings Hive Device Type', + ], + body: [ + sessions: [ [username: settings.username, + password: settings.password, + caller: 'smartthings']] + ] + ] + + state.cookie = '' + + httpPostJson(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + data.auth = response.data + + // set the expiration to 5 minutes + data.auth.expires_at = new Date().getTime() + 300000; + + state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) + log.debug "Adding cookie to collection: $cookie" + log.debug "auth: $data.auth" + log.debug "cookie: $state.cookie" + log.debug "sessionid: $data.auth.sessions[0].id" + + getNodeId() + + api(method, args, success) + + } +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $data.auth" + if(!data.auth) { + log.debug "No data.auth" + return false + } + + def now = new Date().getTime(); + return data.auth.expires_at > now +} \ No newline at end of file From 06f19db8154d3b69a6f3a2fa7e1f5d24f7a800d0 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 20 Nov 2015 15:18:43 +0000 Subject: [PATCH 018/685] Initial Release of Hive Active Hot Water 2 device type for Smartthings --- .../hive-active-hot-water-2.groovy | 367 ++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 devicetypes/alyc100/hive-active-hot-water-2.src/hive-active-hot-water-2.groovy diff --git a/devicetypes/alyc100/hive-active-hot-water-2.src/hive-active-hot-water-2.groovy b/devicetypes/alyc100/hive-active-hot-water-2.src/hive-active-hot-water-2.groovy new file mode 100644 index 00000000000..e3d1b559bad --- /dev/null +++ b/devicetypes/alyc100/hive-active-hot-water-2.src/hive-active-hot-water-2.groovy @@ -0,0 +1,367 @@ +/** + * Hive Active Hot Water 2 + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) + * Name: Hive Active Heating + * Author: alyc100 + * Capabilities: + * Polling + * Refresh + * Thermostat + * Thermostat Mode + * + * Custom Commands: + * setThermostatMode + * + * 2. Create a new device (https://graph.api.smartthings.com/device/list) + * Name: Your Choice + * Device Network Id: Your Choice + * Type: Hive Active Hot Water (should be the last option) + * Location: Choose the correct location + * Hub/Group: Leave blank + * + * 3. Update device preferences + * Click on the new device to see the details. + * Click the edit button next to Preferences + * Fill in your your Hive user name, Hive password. + * + * 4. It should be done. + * + * 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 + * 20.11.2015 + * v1.0 - Initial Release - There seems to be an issue on the Hive side where the Hot Water Relay status is being reported back incorrectly sometimes. + */ +preferences { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") + input("password", "password", title: "Password", description: "Your Hive password") +} + +metadata { + definition (name: "Hive Active Hot Water 2", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Thermostat" + capability "Thermostat Mode" + + command "setThermostatMode" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "Hot Water Relay", width: 6, height: 4, type:"generic") { + tileAttribute("device.thermostatOperatingState", key:"PRIMARY_CONTROL"){ + attributeState "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05" + attributeState "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff" + } + tileAttribute ("hiveHotWater", key: "SECONDARY_CONTROL") { + attributeState "hiveHotWater", label:'${currentValue}' + } + + main "Hot Water Relay" + details "Hot Water Relay" + } + + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("auto", action:"thermostat.off", icon: "st.Office.office7") + state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off") + state("cool", action:"thermostat.heat", icon: "st.thermostat.cool") + state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") + } + + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" + } + + standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" + } + + standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"off", icon:"st.thermostat.heating-cooling-off" + } + + main(["switch", "thermostatMode"]) + details(["mode_auto", "mode_manual", "mode_off", "refresh"]) + + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'switch' attribute + // TODO: handle 'thermostatMode' attribute + +} + +// handle commands +def setHeatingSetpoint(temp) { + //Not implemented +} + +def heatingSetpointUp(){ + //Not implemented +} + +def heatingSetpointDown(){ + //Not implemented +} + +def on() { + log.debug "Executing 'on'" + setThermostatMode('heat') +} + +def off() { + setThermostatMode('off') +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + setThermostatMode('heat') +} + +def auto() { + setThermostatMode('auto') +} + +def setThermostatMode(mode) { + mode = mode == 'emergency heat'? 'heat' : mode + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] + ] + if (mode == 'off') { + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] + ] + } else if (mode == 'heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + } + + api('thermostat_mode', args) { + mode = mode == 'range' ? 'auto' : mode + runIn(3, poll) + } +} + +def poll() { +log.debug "Executing 'poll'" + api('status', []) { + data.nodes = it.data.nodes + + //Construct status message + def statusMsg = "Currently" + + // determine hive hot water operating mode + def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] + def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] + + log.debug "activeHeatCoolMode: $activeHeatCoolMode" + log.debug "activeScheduleLock: $activeScheduleLock" + + def mode = 'auto' + + if (activeHeatCoolMode == "OFF") { + mode = 'off' + statusMsg = statusMsg + " set to OFF" + } + else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { + mode = 'heat' + statusMsg = statusMsg + " set to MANUAL" + } + else { + statusMsg = statusMsg + " set to SCHEDULE" + } + + sendEvent(name: 'thermostatMode', value: mode) + + // determine if Hive hot water relay is on + def stateHotWaterRelay = data.nodes.attributes.stateHotWaterRelay.reportedValue[0] + + log.debug "stateHotWaterRelay: $stateHotWaterRelay" + + if (stateHotWaterRelay == "ON") { + sendEvent(name: 'thermostatOperatingState', value: "heating") + statusMsg = statusMsg + " and is HEATING" + } + else { + sendEvent(name: 'thermostatOperatingState', value: "idle") + statusMsg = statusMsg + " and is IDLE" + } + sendEvent("name":"hiveHotWater", "value":statusMsg) + } +} + +def refresh() { + log.debug "Executing 'refresh'" + // TODO: handle 'refresh' command +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + if(!isLoggedIn()) { + log.debug "Need to login" + login(method, args, success) + return + } + log.debug "Using node id: $state.nodeid" + def methods = [ + 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], + 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + doRequest(request.uri, args, request.type, success) +} + +// Need to be logged in before this is called. So don't call this. Call api. +def doRequest(uri, args, type, success) { + log.debug "Calling doRequest()" + log.debug "Calling $type : $uri : $args" + + def params = [ + uri: uri, + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ], + body: args + ] + + log.debug params + + def postRequest = { response -> + success.call(response) + } + + if (type == 'post') { + httpPostJson(params, postRequest) + } else if (type == 'put') { + httpPutJson(params, postRequest) + } else if (type == 'get') { + httpGet(params, postRequest) + } + +} + +def getNodeId () { + log.debug "Calling getNodeId()" + //get thermostat node id + log.debug "Using session id, $data.auth.sessions[0].id" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ] + ] + + state.nodeid = '' + httpGet(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + + response.data.nodes.each { + log.debug "node name $it.name" + if ((it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == true)) + { + state.nodeid = it.id + } + } + + log.debug "nodeid: $state.nodeid" + } +} + +def login(method = null, args = [], success = {}) { + log.debug "Calling login()" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', + contentType: 'application/json', + headers: [ + 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Smartthings Hive Device Type', + ], + body: [ + sessions: [ [username: settings.username, + password: settings.password, + caller: 'smartthings']] + ] + ] + + state.cookie = '' + + httpPostJson(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + data.auth = response.data + + // set the expiration to 5 minutes + data.auth.expires_at = new Date().getTime() + 300000; + + state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) + log.debug "Adding cookie to collection: $cookie" + log.debug "auth: $data.auth" + log.debug "cookie: $state.cookie" + log.debug "sessionid: $data.auth.sessions[0].id" + + getNodeId() + + api(method, args, success) + + } +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $data.auth" + if(!data.auth) { + log.debug "No data.auth" + return false + } + + def now = new Date().getTime(); + return data.auth.expires_at > now +} + From d701b4757d15885a59e2d8609bd2729849c71377 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 12:22:58 +0000 Subject: [PATCH 019/685] --- .../hive-active-heating.groovy | 5 +- .../hive-active-hot-water.groovy | 367 ++++++++++++++++++ 2 files changed, 370 insertions(+), 2 deletions(-) create mode 100644 devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index e1c08cde1fb..5abe2c62546 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -56,6 +56,7 @@ * 20.11.2015 * v1.5 - Clean up UI and make user friendly status message appear on top panel * v1.6 - Added icons to control buttons. Increased poll delay time to ensure UI is updated on control press. + * v1.7 - Fixed issue where 'supportsHeatCoolModes' attribute does not exist. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -394,7 +395,7 @@ def getNodeId () { response.data.nodes.each { log.debug "node name $it.name" - if (it.attributes.supportsHeatCoolModes != null) + if ((it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == false)) { state.nodeid = it.id } @@ -431,7 +432,7 @@ def login(method = null, args = [], success = {}) { // set the expiration to 5 minutes data.auth.expires_at = new Date().getTime() + 300000; - + state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) log.debug "Adding cookie to collection: $cookie" log.debug "auth: $data.auth" diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy new file mode 100644 index 00000000000..1bbf7ab0010 --- /dev/null +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -0,0 +1,367 @@ +/** + * Hive Active Hot Water + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) + * Name: Hive Active Heating + * Author: alyc100 + * Capabilities: + * Polling + * Refresh + * Thermostat + * Thermostat Mode + * + * Custom Commands: + * setThermostatMode + * + * 2. Create a new device (https://graph.api.smartthings.com/device/list) + * Name: Your Choice + * Device Network Id: Your Choice + * Type: Hive Active Hot Water (should be the last option) + * Location: Choose the correct location + * Hub/Group: Leave blank + * + * 3. Update device preferences + * Click on the new device to see the details. + * Click the edit button next to Preferences + * Fill in your your Hive user name, Hive password. + * + * 4. It should be done. + * + * 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 + * 20.11.2015 + * v1.0 - Initial Release - There seems to be an issue on the Hive side where the Hot Water Relay status is being reported back incorrectly sometimes. + */ +preferences { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") + input("password", "password", title: "Password", description: "Your Hive password") +} + +metadata { + definition (name: "Hive Active Hot Water", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Thermostat" + capability "Thermostat Mode" + + command "setThermostatMode" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "Hot Water Relay", width: 6, height: 4, type:"generic") { + tileAttribute("device.thermostatOperatingState", key:"PRIMARY_CONTROL"){ + attributeState "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05" + attributeState "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff" + } + tileAttribute ("hiveHotWater", key: "SECONDARY_CONTROL") { + attributeState "hiveHotWater", label:'${currentValue}' + } + + main "Hot Water Relay" + details "Hot Water Relay" + } + + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("auto", action:"thermostat.off", icon: "st.Office.office7") + state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off") + state("cool", action:"thermostat.heat", icon: "st.thermostat.cool") + state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") + } + + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" + } + + standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" + } + + standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"off", icon:"st.thermostat.heating-cooling-off" + } + + main(["switch", "thermostatMode"]) + details(["mode_auto", "mode_manual", "mode_off", "refresh"]) + + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'switch' attribute + // TODO: handle 'thermostatMode' attribute + +} + +// handle commands +def setHeatingSetpoint(temp) { + //Not implemented +} + +def heatingSetpointUp(){ + //Not implemented +} + +def heatingSetpointDown(){ + //Not implemented +} + +def on() { + log.debug "Executing 'on'" + setThermostatMode('heat') +} + +def off() { + setThermostatMode('off') +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + setThermostatMode('heat') +} + +def auto() { + setThermostatMode('auto') +} + +def setThermostatMode(mode) { + mode = mode == 'emergency heat'? 'heat' : mode + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] + ] + if (mode == 'off') { + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] + ] + } else if (mode == 'heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + } + + api('thermostat_mode', args) { + mode = mode == 'range' ? 'auto' : mode + runIn(3, poll) + } +} + +def poll() { +log.debug "Executing 'poll'" + api('status', []) { + data.nodes = it.data.nodes + + //Construct status message + def statusMsg = "Currently" + + // determine hive hot water operating mode + def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] + def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] + + log.debug "activeHeatCoolMode: $activeHeatCoolMode" + log.debug "activeScheduleLock: $activeScheduleLock" + + def mode = 'auto' + + if (activeHeatCoolMode == "OFF") { + mode = 'off' + statusMsg = statusMsg + " set to OFF" + } + else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { + mode = 'heat' + statusMsg = statusMsg + " set to MANUAL" + } + else { + statusMsg = statusMsg + " set to SCHEDULE" + } + + sendEvent(name: 'thermostatMode', value: mode) + + // determine if Hive hot water relay is on + def stateHotWaterRelay = data.nodes.attributes.stateHotWaterRelay.reportedValue[0] + + log.debug "stateHotWaterRelay: $stateHotWaterRelay" + + if (stateHotWaterRelay == "ON") { + sendEvent(name: 'thermostatOperatingState', value: "heating") + statusMsg = statusMsg + " and is HEATING" + } + else { + sendEvent(name: 'thermostatOperatingState', value: "idle") + statusMsg = statusMsg + " and is IDLE" + } + sendEvent("name":"hiveHotWater", "value":statusMsg) + } +} + +def refresh() { + log.debug "Executing 'refresh'" + // TODO: handle 'refresh' command +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + if(!isLoggedIn()) { + log.debug "Need to login" + login(method, args, success) + return + } + log.debug "Using node id: $state.nodeid" + def methods = [ + 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], + 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + doRequest(request.uri, args, request.type, success) +} + +// Need to be logged in before this is called. So don't call this. Call api. +def doRequest(uri, args, type, success) { + log.debug "Calling doRequest()" + log.debug "Calling $type : $uri : $args" + + def params = [ + uri: uri, + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ], + body: args + ] + + log.debug params + + def postRequest = { response -> + success.call(response) + } + + if (type == 'post') { + httpPostJson(params, postRequest) + } else if (type == 'put') { + httpPutJson(params, postRequest) + } else if (type == 'get') { + httpGet(params, postRequest) + } + +} + +def getNodeId () { + log.debug "Calling getNodeId()" + //get thermostat node id + log.debug "Using session id, $data.auth.sessions[0].id" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'smartthings', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ] + ] + + state.nodeid = '' + httpGet(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + + response.data.nodes.each { + log.debug "node name $it.name" + if ((it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == true)) + { + state.nodeid = it.id + } + } + + log.debug "nodeid: $state.nodeid" + } +} + +def login(method = null, args = [], success = {}) { + log.debug "Calling login()" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', + contentType: 'application/json', + headers: [ + 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Smartthings Hive Device Type', + ], + body: [ + sessions: [ [username: settings.username, + password: settings.password, + caller: 'smartthings']] + ] + ] + + state.cookie = '' + + httpPostJson(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + data.auth = response.data + + // set the expiration to 5 minutes + data.auth.expires_at = new Date().getTime() + 300000; + + state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) + log.debug "Adding cookie to collection: $cookie" + log.debug "auth: $data.auth" + log.debug "cookie: $state.cookie" + log.debug "sessionid: $data.auth.sessions[0].id" + + getNodeId() + + api(method, args, success) + + } +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $data.auth" + if(!data.auth) { + log.debug "No data.auth" + return false + } + + def now = new Date().getTime(); + return data.auth.expires_at > now +} + From f44938c7ab0f50827af87bcd82711c28b6a46bfa Mon Sep 17 00:00:00 2001 From: alyc100 Date: Sat, 21 Nov 2015 12:34:17 +0000 Subject: [PATCH 020/685] Remove old namespace --- .../hive-active-heating-2.groovy | 459 ------------------ .../hive-active-hot-water-2.groovy | 367 -------------- 2 files changed, 826 deletions(-) delete mode 100644 devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy delete mode 100644 devicetypes/alyc100/hive-active-hot-water-2.src/hive-active-hot-water-2.groovy diff --git a/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy b/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy deleted file mode 100644 index 29ba16f49b2..00000000000 --- a/devicetypes/alyc100/hive-active-heating-2.src/hive-active-heating-2.groovy +++ /dev/null @@ -1,459 +0,0 @@ -/** - * Hive Active Heating 2 - * - * Copyright 2015 Alex Lee Yuk Cheung - * - * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) - * Name: Hive Active Heating - * Author: alyc100 - * Capabilities: - * Polling - * Refresh - * Temperature Measurement - * Thermostat - * Thermostat Mode - * Thermostat Operating State - * Thermostat Heating Setpoint - * - * Custom Commands: - * setThermostatMode - * setHeatingSetpoint - * heatingSetpointUp - * heatingSetpointDown - * - * 2. Create a new device (https://graph.api.smartthings.com/device/list) - * Name: Your Choice - * Device Network Id: Your Choice - * Type: Hive Active Heating (should be the last option) - * Location: Choose the correct location - * Hub/Group: Leave blank - * - * 3. Update device preferences - * Click on the new device to see the details. - * Click the edit button next to Preferences - * Fill in your your Hive user name, Hive password. - * - * 4. It should be done. - * - * 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 - * 19.11.2015 - * v1.0 - Initial Release - * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule. - * v1.2 - Removed requirement to type in Receiver Nickname from Hive. - * v1.3 - Altered temperature colours to match Hive branding (I was bored). - * v1.4 - Enable options for sliders or buttons for temperature control. - * - * 20.11.2015 - * v1.5 - Clean up UI and make user friendly status message appear on top panel - * v1.6 - Added icons to control buttons. Increased poll delay time to ensure UI is updated on control press. - * v1.6.1 - Looks like only Hive 2 is supported with this API. Changing name of app - */ -preferences { - input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") - input("password", "password", title: "Password", description: "Your Hive password") -} - -metadata { - definition (name: "Hive Active Heating 2", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { - capability "Actuator" - capability "Polling" - capability "Refresh" - capability "Temperature Measurement" - capability "Thermostat" - capability "Thermostat Heating Setpoint" - capability "Thermostat Mode" - capability "Thermostat Operating State" - - command "heatingSetpointUp" - command "heatingSetpointDown" - command "setThermostatMode" - command "setHeatingSetpoint" - } - - simulator { - // TODO: define status and reply messages here - } - - tiles(scale: 2) { - - multiAttributeTile(name: "Thermostat", width: 6, height: 4, type:"thermostat") { - tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ - attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ - // Celsius Color Range - [value: 0, color: "#50b5dd"], - [value: 10, color: "#43a575"], - [value: 13, color: "#c5d11b"], - [value: 17, color: "#f4961a"], - [value: 20, color: "#e75928"], - [value: 25, color: "#d9372b"], - [value: 29, color: "#b9203b"] - ]} - tileAttribute ("hiveHeating", key: "SECONDARY_CONTROL") { - attributeState "hiveHeating", label:'${currentValue}' - } - - main "Thermostat" - details "Thermostat" - } - - controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { - state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" - } - - standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { - state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" - } - - standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { - state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" - } - - valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { - state "default", label:'${currentValue}°', unit:"C", - backgroundColors:[ - [value: 0, color: "#50b5dd"], - [value: 10, color: "#43a575"], - [value: 13, color: "#c5d11b"], - [value: 17, color: "#f4961a"], - [value: 20, color: "#e75928"], - [value: 25, color: "#d9372b"], - [value: 29, color: "#b9203b"] - ] - } - - standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" - state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#EC6E05" - } - - standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state("auto", action:"thermostat.off", icon: "st.Office.office7") - state("off", action:"thermostat.heat", icon: "st.thermostat.heating-cooling-off") - state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") - } - - standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") - } - - standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" - } - - standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" - } - - standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"off", icon:"st.thermostat.heating-cooling-off" - } - - main(["temperature", "thermostatMode"]) - - // ============================================================ - // Slider or Buttons... - // To expose sliders, comment out the first detials line below and uncomment the second details line below. - // To expose buttons, uncomment the first details line below and comment out the second details line below. - - //details(["mode_auto", "mode_manual", "mode_off", "heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) - details(["mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) - - // ============================================================ - - } -} - -// parse events into attributes -def parse(String description) { - log.debug "Parsing '${description}'" - // TODO: handle 'temperature' attribute - // TODO: handle 'heatingSetpoint' attribute - // TODO: handle 'thermostatSetpoint' attribute - // TODO: handle 'thermostatMode' attribute - // TODO: handle 'thermostatOperatingState' attribute -} - -// handle commands -def setHeatingSetpoint(temp) { - def latestThermostatMode = device.latestState('thermostatMode') - if (temp < 5) { - temp = 5 - } - if (temp > 32) { - temp = 32 - } - // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} - def args = [ - nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] - ] - - api('temperature', args) { - runIn(4, poll) - } - -} - -def heatingSetpointUp(){ - int newSetpoint = device.currentValue("heatingSetpoint") + 1 - log.debug "Setting heat set point up to: ${newSetpoint}" - setHeatingSetpoint(newSetpoint) -} - -def heatingSetpointDown(){ - int newSetpoint = device.currentValue("heatingSetpoint") - 1 - log.debug "Setting heat set point down to: ${newSetpoint}" - setHeatingSetpoint(newSetpoint) -} - -def off() { - setThermostatMode('off') -} - -def heat() { - setThermostatMode('heat') -} - -def emergencyHeat() { - setThermostatMode('heat') -} - -def auto() { - setThermostatMode('auto') -} - -def setThermostatMode(mode) { - mode = mode == 'emergency heat'? 'heat' : mode - def args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] - ] - if (mode == 'off') { - args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] - ] - } else if (mode == 'heat') { - //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} - args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] - ] - } - - api('thermostat_mode', args) { - mode = mode == 'range' ? 'auto' : mode - runIn(4, poll) - } -} - -// handle commands -def poll() { - log.debug "Executing 'poll'" - api('status', []) { - data.nodes = it.data.nodes - - //Construct status message - def statusMsg = "Currently" - - // get temperature status - def temperature = data.nodes.attributes.temperature.reportedValue[0] - def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] - - sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") - sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") - - // determine hive operating mode - def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] - def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] - - log.debug "activeHeatCoolMode: $activeHeatCoolMode" - log.debug "activeScheduleLock: $activeScheduleLock" - - def mode = 'auto' - - if (activeHeatCoolMode == "OFF") { - mode = 'off' - statusMsg = statusMsg + " set to OFF" - } - else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { - mode = 'heat' - statusMsg = statusMsg + " set to MANUAL" - } - else { - statusMsg = statusMsg + " set to SCHEDULE" - } - sendEvent(name: 'thermostatMode', value: mode) - - // determine if Hive heating relay is on - def stateHeatingRelay = data.nodes.attributes.stateHeatingRelay.reportedValue[0] - - log.debug "stateHeatingRelay: $stateHeatingRelay" - - if (stateHeatingRelay == "ON") { - sendEvent(name: 'thermostatOperatingState', value: "heating") - statusMsg = statusMsg + " and is HEATING" - } - else { - sendEvent(name: 'thermostatOperatingState', value: "idle") - statusMsg = statusMsg + " and is IDLE" - } - - sendEvent("name":"hiveHeating", "value":statusMsg) - } -} - -def refresh() { - log.debug "Executing 'refresh'" - // TODO: handle 'refresh' command -} - -def api(method, args = [], success = {}) { - log.debug "Executing 'api'" - - if(!isLoggedIn()) { - log.debug "Need to login" - login(method, args, success) - return - } - log.debug "Using node id: $state.nodeid" - def methods = [ - 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], - 'temperature': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'], - 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] - ] - - def request = methods.getAt(method) - - log.debug "Starting $method : $args" - doRequest(request.uri, args, request.type, success) -} - -// Need to be logged in before this is called. So don't call this. Call api. -def doRequest(uri, args, type, success) { - log.debug "Calling doRequest()" - log.debug "Calling $type : $uri : $args" - - def params = [ - uri: uri, - contentType: 'application/json', - headers: [ - 'Cookie': state.cookie, - 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', - 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" - ], - body: args - ] - - log.debug params - - def postRequest = { response -> - success.call(response) - } - - - if (type == 'post') { - httpPostJson(params, postRequest) - } else if (type == 'put') { - httpPutJson(params, postRequest) - } else if (type == 'get') { - httpGet(params, postRequest) - } - -} - -def getNodeId () { - log.debug "Calling getNodeId()" - //get thermostat node id - log.debug "Using session id, $data.auth.sessions[0].id" - def params = [ - uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', - contentType: 'application/json', - headers: [ - 'Cookie': state.cookie, - 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', - 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" - ] - ] - - state.nodeid = '' - httpGet(params) {response -> - log.debug "Request was successful, $response.status" - log.debug response.headers - - response.data.nodes.each { - log.debug "node name $it.name" - if (it.attributes.supportsHeatCoolModes != null) - { - state.nodeid = it.id - } - } - - log.debug "nodeid: $state.nodeid" - } -} - -def login(method = null, args = [], success = {}) { - log.debug "Calling login()" - def params = [ - uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', - contentType: 'application/json', - headers: [ - 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'Smartthings Hive Device Type', - ], - body: [ - sessions: [ [username: settings.username, - password: settings.password, - caller: 'smartthings']] - ] - ] - - state.cookie = '' - - httpPostJson(params) {response -> - log.debug "Request was successful, $response.status" - log.debug response.headers - data.auth = response.data - - // set the expiration to 5 minutes - data.auth.expires_at = new Date().getTime() + 300000; - - state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) - log.debug "Adding cookie to collection: $cookie" - log.debug "auth: $data.auth" - log.debug "cookie: $state.cookie" - log.debug "sessionid: $data.auth.sessions[0].id" - - getNodeId() - - api(method, args, success) - - } -} - -def isLoggedIn() { - log.debug "Calling isLoggedIn()" - log.debug "isLoggedIn state $data.auth" - if(!data.auth) { - log.debug "No data.auth" - return false - } - - def now = new Date().getTime(); - return data.auth.expires_at > now -} \ No newline at end of file diff --git a/devicetypes/alyc100/hive-active-hot-water-2.src/hive-active-hot-water-2.groovy b/devicetypes/alyc100/hive-active-hot-water-2.src/hive-active-hot-water-2.groovy deleted file mode 100644 index e3d1b559bad..00000000000 --- a/devicetypes/alyc100/hive-active-hot-water-2.src/hive-active-hot-water-2.groovy +++ /dev/null @@ -1,367 +0,0 @@ -/** - * Hive Active Hot Water 2 - * - * Copyright 2015 Alex Lee Yuk Cheung - * - * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) - * Name: Hive Active Heating - * Author: alyc100 - * Capabilities: - * Polling - * Refresh - * Thermostat - * Thermostat Mode - * - * Custom Commands: - * setThermostatMode - * - * 2. Create a new device (https://graph.api.smartthings.com/device/list) - * Name: Your Choice - * Device Network Id: Your Choice - * Type: Hive Active Hot Water (should be the last option) - * Location: Choose the correct location - * Hub/Group: Leave blank - * - * 3. Update device preferences - * Click on the new device to see the details. - * Click the edit button next to Preferences - * Fill in your your Hive user name, Hive password. - * - * 4. It should be done. - * - * 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 - * 20.11.2015 - * v1.0 - Initial Release - There seems to be an issue on the Hive side where the Hot Water Relay status is being reported back incorrectly sometimes. - */ -preferences { - input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") - input("password", "password", title: "Password", description: "Your Hive password") -} - -metadata { - definition (name: "Hive Active Hot Water 2", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { - capability "Actuator" - capability "Polling" - capability "Refresh" - capability "Thermostat" - capability "Thermostat Mode" - - command "setThermostatMode" - } - - simulator { - // TODO: define status and reply messages here - } - - tiles(scale: 2) { - - multiAttributeTile(name: "Hot Water Relay", width: 6, height: 4, type:"generic") { - tileAttribute("device.thermostatOperatingState", key:"PRIMARY_CONTROL"){ - attributeState "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05" - attributeState "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff" - } - tileAttribute ("hiveHotWater", key: "SECONDARY_CONTROL") { - attributeState "hiveHotWater", label:'${currentValue}' - } - - main "Hot Water Relay" - details "Hot Water Relay" - } - - standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state("auto", action:"thermostat.off", icon: "st.Office.office7") - state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off") - state("cool", action:"thermostat.heat", icon: "st.thermostat.cool") - state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") - } - - standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") - } - - standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" - } - - standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" - } - - standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"off", icon:"st.thermostat.heating-cooling-off" - } - - main(["switch", "thermostatMode"]) - details(["mode_auto", "mode_manual", "mode_off", "refresh"]) - - } -} - -// parse events into attributes -def parse(String description) { - log.debug "Parsing '${description}'" - // TODO: handle 'switch' attribute - // TODO: handle 'thermostatMode' attribute - -} - -// handle commands -def setHeatingSetpoint(temp) { - //Not implemented -} - -def heatingSetpointUp(){ - //Not implemented -} - -def heatingSetpointDown(){ - //Not implemented -} - -def on() { - log.debug "Executing 'on'" - setThermostatMode('heat') -} - -def off() { - setThermostatMode('off') -} - -def heat() { - setThermostatMode('heat') -} - -def emergencyHeat() { - setThermostatMode('heat') -} - -def auto() { - setThermostatMode('auto') -} - -def setThermostatMode(mode) { - mode = mode == 'emergency heat'? 'heat' : mode - def args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] - ] - if (mode == 'off') { - args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] - ] - } else if (mode == 'heat') { - //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} - args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] - ] - } - - api('thermostat_mode', args) { - mode = mode == 'range' ? 'auto' : mode - runIn(3, poll) - } -} - -def poll() { -log.debug "Executing 'poll'" - api('status', []) { - data.nodes = it.data.nodes - - //Construct status message - def statusMsg = "Currently" - - // determine hive hot water operating mode - def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] - def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] - - log.debug "activeHeatCoolMode: $activeHeatCoolMode" - log.debug "activeScheduleLock: $activeScheduleLock" - - def mode = 'auto' - - if (activeHeatCoolMode == "OFF") { - mode = 'off' - statusMsg = statusMsg + " set to OFF" - } - else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { - mode = 'heat' - statusMsg = statusMsg + " set to MANUAL" - } - else { - statusMsg = statusMsg + " set to SCHEDULE" - } - - sendEvent(name: 'thermostatMode', value: mode) - - // determine if Hive hot water relay is on - def stateHotWaterRelay = data.nodes.attributes.stateHotWaterRelay.reportedValue[0] - - log.debug "stateHotWaterRelay: $stateHotWaterRelay" - - if (stateHotWaterRelay == "ON") { - sendEvent(name: 'thermostatOperatingState', value: "heating") - statusMsg = statusMsg + " and is HEATING" - } - else { - sendEvent(name: 'thermostatOperatingState', value: "idle") - statusMsg = statusMsg + " and is IDLE" - } - sendEvent("name":"hiveHotWater", "value":statusMsg) - } -} - -def refresh() { - log.debug "Executing 'refresh'" - // TODO: handle 'refresh' command -} - -def api(method, args = [], success = {}) { - log.debug "Executing 'api'" - - if(!isLoggedIn()) { - log.debug "Need to login" - login(method, args, success) - return - } - log.debug "Using node id: $state.nodeid" - def methods = [ - 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], - 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] - ] - - def request = methods.getAt(method) - - log.debug "Starting $method : $args" - doRequest(request.uri, args, request.type, success) -} - -// Need to be logged in before this is called. So don't call this. Call api. -def doRequest(uri, args, type, success) { - log.debug "Calling doRequest()" - log.debug "Calling $type : $uri : $args" - - def params = [ - uri: uri, - contentType: 'application/json', - headers: [ - 'Cookie': state.cookie, - 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', - 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" - ], - body: args - ] - - log.debug params - - def postRequest = { response -> - success.call(response) - } - - if (type == 'post') { - httpPostJson(params, postRequest) - } else if (type == 'put') { - httpPutJson(params, postRequest) - } else if (type == 'get') { - httpGet(params, postRequest) - } - -} - -def getNodeId () { - log.debug "Calling getNodeId()" - //get thermostat node id - log.debug "Using session id, $data.auth.sessions[0].id" - def params = [ - uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', - contentType: 'application/json', - headers: [ - 'Cookie': state.cookie, - 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', - 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" - ] - ] - - state.nodeid = '' - httpGet(params) {response -> - log.debug "Request was successful, $response.status" - log.debug response.headers - - response.data.nodes.each { - log.debug "node name $it.name" - if ((it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == true)) - { - state.nodeid = it.id - } - } - - log.debug "nodeid: $state.nodeid" - } -} - -def login(method = null, args = [], success = {}) { - log.debug "Calling login()" - def params = [ - uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', - contentType: 'application/json', - headers: [ - 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', - 'Accept': 'application/vnd.alertme.zoo-6.2+json', - 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'Smartthings Hive Device Type', - ], - body: [ - sessions: [ [username: settings.username, - password: settings.password, - caller: 'smartthings']] - ] - ] - - state.cookie = '' - - httpPostJson(params) {response -> - log.debug "Request was successful, $response.status" - log.debug response.headers - data.auth = response.data - - // set the expiration to 5 minutes - data.auth.expires_at = new Date().getTime() + 300000; - - state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) - log.debug "Adding cookie to collection: $cookie" - log.debug "auth: $data.auth" - log.debug "cookie: $state.cookie" - log.debug "sessionid: $data.auth.sessions[0].id" - - getNodeId() - - api(method, args, success) - - } -} - -def isLoggedIn() { - log.debug "Calling isLoggedIn()" - log.debug "isLoggedIn state $data.auth" - if(!data.auth) { - log.debug "No data.auth" - return false - } - - def now = new Date().getTime(); - return data.auth.expires_at > now -} - From 6f9a0149aa45cd5370ba14aba74008610d5b1511 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 13:38:18 +0000 Subject: [PATCH 021/685] Includes attempted fix to detect correct receiver node for controls and changes to behaviour when temperature is altered in OFF mode. --- .../hive-active-heating.groovy | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 5abe2c62546..55cddbc6400 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -56,7 +56,10 @@ * 20.11.2015 * v1.5 - Clean up UI and make user friendly status message appear on top panel * v1.6 - Added icons to control buttons. Increased poll delay time to ensure UI is updated on control press. - * v1.7 - Fixed issue where 'supportsHeatCoolModes' attribute does not exist. + * + * 21.11.2015 + * v1.7 - Fixed issue where 'supportsHeatCoolModes' attribute does not exist. + * v1.8 - Changed behaviour when temperature is set when Hive Heating is in off mode to match Hive app behaviour. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -185,17 +188,27 @@ def parse(String description) { // handle commands def setHeatingSetpoint(temp) { + log.debug "Executing 'setHeatingSetpoint with temp $temp'" def latestThermostatMode = device.latestState('thermostatMode') + if (temp < 5) { temp = 5 } if (temp > 32) { temp = 32 } - // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} + + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} def args = [ nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] ] + //if thermostat is off, set to manual + if (latestThermostatMode.stringValue == 'off') { + + args = [ + nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp], activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + } api('temperature', args) { runIn(4, poll) @@ -232,6 +245,7 @@ def auto() { } def setThermostatMode(mode) { + log.debug "Executing 'setThermostatMode with mode $mode'" mode = mode == 'emergency heat'? 'heat' : mode def args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] @@ -253,7 +267,6 @@ def setThermostatMode(mode) { } } -// handle commands def poll() { log.debug "Executing 'poll'" api('status', []) { From 119022f46e4f9dce8c3062ba982be26c8b240994 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 13:56:31 +0000 Subject: [PATCH 022/685] Minor configuration changes --- .../hive-active-hot-water.src/hive-active-hot-water.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 1bbf7ab0010..16523f39294 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -100,7 +100,7 @@ metadata { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - main(["switch", "thermostatMode"]) + main(["thermostatOperatingState", "thermostatMode"]) details(["mode_auto", "mode_manual", "mode_off", "refresh"]) } @@ -365,3 +365,4 @@ def isLoggedIn() { return data.auth.expires_at > now } + From f39370821e945b9d742872069f49a821b1516e75 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 20:13:20 +0000 Subject: [PATCH 023/685] Added android tile layout --- .../hive-active-heating.groovy | 71 ++++++++++++------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 55cddbc6400..66be31097f8 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -60,6 +60,7 @@ * 21.11.2015 * v1.7 - Fixed issue where 'supportsHeatCoolModes' attribute does not exist. * v1.8 - Changed behaviour when temperature is set when Hive Heating is in off mode to match Hive app behaviour. + * v1.9 - Added new Android tile layout option. Requires uncommenting/commenting out lines. Updated behaviour when Hive Heating is in off mode. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -89,7 +90,7 @@ metadata { tiles(scale: 2) { - multiAttributeTile(name: "Thermostat", width: 6, height: 4, type:"thermostat") { + multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"thermostat") { tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ // Celsius Color Range @@ -104,9 +105,6 @@ metadata { tileAttribute ("hiveHeating", key: "SECONDARY_CONTROL") { attributeState "hiveHeating", label:'${currentValue}' } - - main "Thermostat" - details "Thermostat" } controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { @@ -122,7 +120,7 @@ metadata { } valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { - state "default", label:'${currentValue}°', unit:"C", + state "default", label:'${currentValue}°', unit:"C", backgroundColors:[ [value: 0, color: "#50b5dd"], [value: 10, color: "#43a575"], @@ -134,7 +132,7 @@ metadata { ] } - standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#EC6E05" } @@ -149,6 +147,14 @@ metadata { state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") } + standardTile("boost", "device.boost", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'boost', action:"boost", icon:"st.Home.home30") + } + + standardTile("boost_off", "device.boost", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'boost off', action:"boost_off", icon:"st.Home.home30") + } + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" } @@ -160,19 +166,28 @@ metadata { standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } + + valueTile("statusPanel", "device.hiveHeating", inactiveLabel: true, decoration: "flat", width: 6, height: 2) { + state "default", label:'${currentValue}', backgroundColor:"#ffffff" + } - main(["temperature", "thermostatMode"]) + main(["thermostat", "thermostatMode"]) // ============================================================ - // Slider or Buttons... - // To expose sliders, comment out the first detials line below and uncomment the second details line below. - // To expose buttons, uncomment the first details line below and comment out the second details line below. - - //details(["mode_auto", "mode_manual", "mode_off", "heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "thermostatMode", "thermostatOperatingState", "refresh"]) - details(["mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) + // iOS TILES + // To expose iOS optimised tiles, comment out the details line in Android Tiles section and uncomment details line below. + + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) // ============================================================ + // ============================================================ + // ANDROID TILES + // To expose Android optimised tiles, comment out the details line in iOS Tiles section and uncomment details line below. + + //details(["thermostat", "statusPanel", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) + + // ============================================================ } } @@ -187,6 +202,10 @@ def parse(String description) { } // handle commands + +def boost() { +} + def setHeatingSetpoint(temp) { log.debug "Executing 'setHeatingSetpoint with temp $temp'" def latestThermostatMode = device.latestState('thermostatMode') @@ -202,18 +221,14 @@ def setHeatingSetpoint(temp) { def args = [ nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] ] - //if thermostat is off, set to manual - if (latestThermostatMode.stringValue == 'off') { - - args = [ - nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp], activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] - ] - } api('temperature', args) { - runIn(4, poll) - } - + //if thermostat is off, set to manual + if (latestThermostatMode.stringValue == 'off') { + heat() + } + runIn(4, poll) + } } def heatingSetpointUp(){ @@ -252,7 +267,7 @@ def setThermostatMode(mode) { ] if (mode == 'off') { args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"], activeScheduleLock: [targetValue: true]]]] ] } else if (mode == 'heat') { //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} @@ -277,7 +292,13 @@ def poll() { // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] - def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + + // convert temperature reading of 1 degree to 7 as Hive app does + log.debug "tempreature: $heatingSetpoint" + if (heatingSetpoint == 1.0) { + heatingSetpoint = 7.0 + } sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") From 2ddc24f1a0555ddf907f796482207a2269d95dda Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 20:33:07 +0000 Subject: [PATCH 024/685] Changes to temperature precision --- .../hive-active-heating.src/hive-active-heating.groovy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 66be31097f8..32e97fe532e 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -60,7 +60,7 @@ * 21.11.2015 * v1.7 - Fixed issue where 'supportsHeatCoolModes' attribute does not exist. * v1.8 - Changed behaviour when temperature is set when Hive Heating is in off mode to match Hive app behaviour. - * v1.9 - Added new Android tile layout option. Requires uncommenting/commenting out lines. Updated behaviour when Hive Heating is in off mode. + * v1.9 - Added new Android tile layout option. Requires uncommenting/commenting out lines. Updated behaviour when Hive Heating is in off mode. Altered temperatue precision. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -292,10 +292,11 @@ def poll() { // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] - def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + temperature = convertTemperatureIfNeeded(temperature, "C", 2) + heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 2) // convert temperature reading of 1 degree to 7 as Hive app does - log.debug "tempreature: $heatingSetpoint" if (heatingSetpoint == 1.0) { heatingSetpoint = 7.0 } From 671ec3aab33387df042387c0d7be4cbc10e7bb65 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 21:26:24 +0000 Subject: [PATCH 025/685] Tweaks to Android layout --- .../hive-active-heating.groovy | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 32e97fe532e..5994c5e7d74 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -61,6 +61,7 @@ * v1.7 - Fixed issue where 'supportsHeatCoolModes' attribute does not exist. * v1.8 - Changed behaviour when temperature is set when Hive Heating is in off mode to match Hive app behaviour. * v1.9 - Added new Android tile layout option. Requires uncommenting/commenting out lines. Updated behaviour when Hive Heating is in off mode. Altered temperatue precision. + * v1.9.1 - Tweaks to Android layout. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -106,6 +107,19 @@ metadata { attributeState "hiveHeating", label:'${currentValue}' } } + + valueTile("thermostat_small", "device.temperature", width: 4, height: 4) { + state "default", label:'${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ] + } controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" @@ -138,9 +152,9 @@ metadata { } standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state("auto", action:"thermostat.off", icon: "st.Office.office7") - state("off", action:"thermostat.heat", icon: "st.thermostat.heating-cooling-off") - state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") + state("auto", action:"thermostat.off", label: "SCHEDULED") + state("off", action:"thermostat.heat", label: "OFF") + state("heat", action:"thermostat.auto", label: "MANUAL") } standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { @@ -166,12 +180,8 @@ metadata { standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - - valueTile("statusPanel", "device.hiveHeating", inactiveLabel: true, decoration: "flat", width: 6, height: 2) { - state "default", label:'${currentValue}', backgroundColor:"#ffffff" - } - main(["thermostat", "thermostatMode"]) + main(["thermostat", "thermostatOperatingState"]) // ============================================================ // iOS TILES @@ -185,7 +195,7 @@ metadata { // ANDROID TILES // To expose Android optimised tiles, comment out the details line in iOS Tiles section and uncomment details line below. - //details(["thermostat", "statusPanel", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) + //details(["thermostat_small", "thermostatOperatingState", "thermostatMode", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) // ============================================================ } From 62a6d660b190bf850bdf155a6f20da6edf3b6727 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 21:42:40 +0000 Subject: [PATCH 026/685] Updated instructions for Android users --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 5994c5e7d74..add9210b23b 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -33,7 +33,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. It should be done. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 180 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 188 before publishing. * * 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: From f8f2d4e6e4e97f0ff8c05a553255a0dd5e76e765 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 22:08:29 +0000 Subject: [PATCH 027/685] Updated heating set point temperature reporting when in frost protect mode. --- .../hive-active-heating.src/hive-active-heating.groovy | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index add9210b23b..a6ecde41e8f 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -63,6 +63,7 @@ * v1.8 - Changed behaviour when temperature is set when Hive Heating is in off mode to match Hive app behaviour. * v1.9 - Added new Android tile layout option. Requires uncommenting/commenting out lines. Updated behaviour when Hive Heating is in off mode. Altered temperatue precision. * v1.9.1 - Tweaks to Android layout. + * v1.9.2 - Changes to heating set point temperature reporting when in frost protect mode. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -188,7 +189,7 @@ metadata { // iOS TILES // To expose iOS optimised tiles, comment out the details line in Android Tiles section and uncomment details line below. - details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "boost_off", "refresh"]) // ============================================================ @@ -308,8 +309,9 @@ def poll() { heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 2) // convert temperature reading of 1 degree to 7 as Hive app does - if (heatingSetpoint == 1.0) { - heatingSetpoint = 7.0 + log.debug "heatingSetpoint: $heatingSetpoint" + if (heatingSetpoint == "1.0") { + heatingSetpoint = "7.0" } sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") From 144bd38ca46564b0b1a992a448f87d5077e57f9f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 22:12:44 +0000 Subject: [PATCH 028/685] Removed unnecessary debugs --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index a6ecde41e8f..695a502e356 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -63,7 +63,7 @@ * v1.8 - Changed behaviour when temperature is set when Hive Heating is in off mode to match Hive app behaviour. * v1.9 - Added new Android tile layout option. Requires uncommenting/commenting out lines. Updated behaviour when Hive Heating is in off mode. Altered temperatue precision. * v1.9.1 - Tweaks to Android layout. - * v1.9.2 - Changes to heating set point temperature reporting when in frost protect mode. + * v1.9.2 - Tweaks to how set heating point temperature is reported in frost mode. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -309,7 +309,6 @@ def poll() { heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 2) // convert temperature reading of 1 degree to 7 as Hive app does - log.debug "heatingSetpoint: $heatingSetpoint" if (heatingSetpoint == "1.0") { heatingSetpoint = "7.0" } From f8e63b0559802715924810fda1dcd3c752e948a7 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 22:27:49 +0000 Subject: [PATCH 029/685] Improvements to handling temperature setting when in 'off' mode --- .../hive-active-heating.groovy | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 695a502e356..b9f847bf4de 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -64,6 +64,7 @@ * v1.9 - Added new Android tile layout option. Requires uncommenting/commenting out lines. Updated behaviour when Hive Heating is in off mode. Altered temperatue precision. * v1.9.1 - Tweaks to Android layout. * v1.9.2 - Tweaks to how set heating point temperature is reported in frost mode. + * v1.9.3 - Improvements to handling temperature setting when in 'off' mode. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -228,17 +229,22 @@ def setHeatingSetpoint(temp) { if (temp > 32) { temp = 32 } + + //if thermostat is off, set to manual + if (latestThermostatMode.stringValue == 'off') { + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + api('thermostat_mode', args) { + } + } + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} def args = [ nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] - ] - - api('temperature', args) { - //if thermostat is off, set to manual - if (latestThermostatMode.stringValue == 'off') { - heat() - } + ] + api('temperature', args) { runIn(4, poll) } } From c81cc485981a325a6a20fc3b40b86ab238fb338f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 21 Nov 2015 22:30:32 +0000 Subject: [PATCH 030/685] Minor update --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index b9f847bf4de..45aed9960e3 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -190,7 +190,7 @@ metadata { // iOS TILES // To expose iOS optimised tiles, comment out the details line in Android Tiles section and uncomment details line below. - details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "boost_off", "refresh"]) + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) // ============================================================ From 18b1b082f7802fc7f6b0e3846d92738b6ae374bd Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 00:37:44 +0000 Subject: [PATCH 031/685] Added Boost functionality. --- .../hive-active-heating.groovy | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 45aed9960e3..92f632d1b38 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -65,6 +65,7 @@ * v1.9.1 - Tweaks to Android layout. * v1.9.2 - Tweaks to how set heating point temperature is reported in frost mode. * v1.9.3 - Improvements to handling temperature setting when in 'off' mode. + * v1.10 - Added Boost button!! Reduced number of activity messages. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -155,21 +156,18 @@ metadata { } standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state("auto", action:"thermostat.off", label: "SCHEDULED") - state("off", action:"thermostat.heat", label: "OFF") - state("heat", action:"thermostat.auto", label: "MANUAL") + state("auto", label: "SCHEDULED") + state("off", label: "OFF") + state("heat", label: "MANUAL") + state("emergency heat", label: "BOOST") } standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") } - standardTile("boost", "device.boost", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state("default", label:'boost', action:"boost", icon:"st.Home.home30") - } - - standardTile("boost_off", "device.boost", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state("default", label:'boost off', action:"boost_off", icon:"st.Home.home30") + valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'${currentValue}', action:"emergencyHeat") } standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { @@ -190,7 +188,7 @@ metadata { // iOS TILES // To expose iOS optimised tiles, comment out the details line in Android Tiles section and uncomment details line below. - details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "refresh"]) // ============================================================ @@ -198,7 +196,7 @@ metadata { // ANDROID TILES // To expose Android optimised tiles, comment out the details line in iOS Tiles section and uncomment details line below. - //details(["thermostat_small", "thermostatOperatingState", "thermostatMode", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "refresh"]) + //details(["thermostat_small", "thermostatOperatingState", "thermostatMode", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "refresh"]) // ============================================================ } @@ -215,10 +213,6 @@ def parse(String description) { } // handle commands - -def boost() { -} - def setHeatingSetpoint(temp) { log.debug "Executing 'setHeatingSetpoint with temp $temp'" def latestThermostatMode = device.latestState('thermostatMode') @@ -270,7 +264,15 @@ def heat() { } def emergencyHeat() { - setThermostatMode('heat') + log.debug "Executing 'boost'" + + def latestThermostatMode = device.latestState('thermostatMode') + + //Don't do if already in BOOST mode. + if (latestThermostatMode.stringValue != 'emergency heat') { + setThermostatMode('emergency heat') + } + } def auto() { @@ -279,20 +281,24 @@ def auto() { def setThermostatMode(mode) { log.debug "Executing 'setThermostatMode with mode $mode'" - mode = mode == 'emergency heat'? 'heat' : mode def args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: false]]]] ] if (mode == 'off') { args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"], activeScheduleLock: [targetValue: true]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] ] } else if (mode == 'heat') { //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] + ] + } else if (mode == 'emergency heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":22}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: 60], targetHeatTemperature: [targetValue: "22"]]]] ] - } + } api('thermostat_mode', args) { mode = mode == 'range' ? 'auto' : mode @@ -308,6 +314,9 @@ def poll() { //Construct status message def statusMsg = "Currently" + //Boost button label + def boostLabel = "Start\nBoost" + // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] @@ -335,6 +344,13 @@ def poll() { mode = 'off' statusMsg = statusMsg + " set to OFF" } + else if (activeHeatCoolMode == "BOOST") { + mode = 'emergency heat' + statusMsg = statusMsg + " set to BOOST" + def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] + boostLabel = "Boosting for \n" + boostTime + " mins" + sendEvent("name":"boostTimeRemainingIs", "value": boostTime + " mins") + } else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { mode = 'heat' statusMsg = statusMsg + " set to MANUAL" @@ -356,15 +372,16 @@ def poll() { else { sendEvent(name: 'thermostatOperatingState', value: "idle") statusMsg = statusMsg + " and is IDLE" - } - - sendEvent("name":"hiveHeating", "value":statusMsg) + } + + sendEvent("name":"hiveHeating", "value": statusMsg, displayed: false) + sendEvent("name":"boostLabel", "value": boostLabel, displayed: false) } } def refresh() { log.debug "Executing 'refresh'" - // TODO: handle 'refresh' command + poll() } def api(method, args = [], success = {}) { From 70338dc7522a94c6c59b4f83e3f3714cae953015 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 01:16:37 +0000 Subject: [PATCH 032/685] Tweaks to temperature formatting --- .../hive-active-heating.src/hive-active-heating.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 92f632d1b38..f0445f694b6 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -66,6 +66,7 @@ * v1.9.2 - Tweaks to how set heating point temperature is reported in frost mode. * v1.9.3 - Improvements to handling temperature setting when in 'off' mode. * v1.10 - Added Boost button!! Reduced number of activity messages. + * v1.10.1 - Tweaks to temperature formatting. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -320,8 +321,8 @@ def poll() { // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] - temperature = convertTemperatureIfNeeded(temperature, "C", 2) - heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 2) + temperature = String.format("%5.1f",temperature) + heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 1) // convert temperature reading of 1 degree to 7 as Hive app does if (heatingSetpoint == "1.0") { From 5369f158a1acad9ae4afe55f5df41682cd0bf457 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 03:44:17 +0000 Subject: [PATCH 033/685] Add icons to thermostat mode --- .../hive-active-heating.groovy | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index f0445f694b6..ddf2f28b045 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -67,6 +67,7 @@ * v1.9.3 - Improvements to handling temperature setting when in 'off' mode. * v1.10 - Added Boost button!! Reduced number of activity messages. * v1.10.1 - Tweaks to temperature formatting. + * v1.10.2 - Added icons to thermostat mode states */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -157,10 +158,10 @@ metadata { } standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state("auto", label: "SCHEDULED") - state("off", label: "OFF") - state("heat", label: "MANUAL") - state("emergency heat", label: "BOOST") + state("auto", label: "SCHEDULED", icon:"st.Office.office7") + state("off", label: "OFF", icon:"st.thermostat.heating-cooling-off") + state("heat", label: "MANUAL", icon:"st.Weather.weather2") + state("emergency heat", label: "BOOST", icon:"st.Health & Wellness.health7") } standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { @@ -321,7 +322,7 @@ def poll() { // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] - temperature = String.format("%5.1f",temperature) + temperature = String.format("%2.1f",temperature) heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 1) // convert temperature reading of 1 degree to 7 as Hive app does From 77d3cb2d8420fd96ef1ad096afa8dcb043a58a1f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 14:16:19 +0000 Subject: [PATCH 034/685] Minor update --- .../hive-active-heating.src/hive-active-heating.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index ddf2f28b045..a19ed05cd1a 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -246,12 +246,14 @@ def setHeatingSetpoint(temp) { } def heatingSetpointUp(){ + log.debug "Executing 'heatingSetpointUp'" int newSetpoint = device.currentValue("heatingSetpoint") + 1 log.debug "Setting heat set point up to: ${newSetpoint}" setHeatingSetpoint(newSetpoint) } def heatingSetpointDown(){ + log.debug "Executing 'heatingSetpointDown'" int newSetpoint = device.currentValue("heatingSetpoint") - 1 log.debug "Setting heat set point down to: ${newSetpoint}" setHeatingSetpoint(newSetpoint) @@ -351,7 +353,7 @@ def poll() { statusMsg = statusMsg + " set to BOOST" def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] boostLabel = "Boosting for \n" + boostTime + " mins" - sendEvent("name":"boostTimeRemainingIs", "value": boostTime + " mins") + sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") } else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { mode = 'heat' From c84ab5e1cd6e46dfcb3511426145557c50696781 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 15:54:06 +0000 Subject: [PATCH 035/685] Implemented Hot Water Boost functionality. Added Android tile layout --- .../hive-active-hot-water.groovy | 89 +++++++++++++++---- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 16523f39294..9960a55051f 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -27,7 +27,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. It should be done. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 121 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 129 before publishing. * * 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: @@ -41,6 +42,10 @@ * VERSION HISTORY * 20.11.2015 * v1.0 - Initial Release - There seems to be an issue on the Hive side where the Hot Water Relay status is being reported back incorrectly sometimes. + * v1.0.1 - Minor tweaks to improve support for thermostat capability + * + * 22.11.2015 + * v1.1 - Implemented Boost functionality! Added optimised Android tile layout. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -64,7 +69,7 @@ metadata { tiles(scale: 2) { - multiAttributeTile(name: "Hot Water Relay", width: 6, height: 4, type:"generic") { + multiAttributeTile(name: "hotWaterRelay", width: 6, height: 4, type:"generic") { tileAttribute("device.thermostatOperatingState", key:"PRIMARY_CONTROL"){ attributeState "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05" attributeState "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff" @@ -72,22 +77,29 @@ metadata { tileAttribute ("hiveHotWater", key: "SECONDARY_CONTROL") { attributeState "hiveHotWater", label:'${currentValue}' } - - main "Hot Water Relay" - details "Hot Water Relay" + } + + standardTile("hotWaterRelay_small", "device.thermostatOperatingState", inactiveLabel: true, width: 3, height: 3) { + state( "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05") + state( "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff") } - standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state("auto", action:"thermostat.off", icon: "st.Office.office7") - state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off") - state("cool", action:"thermostat.heat", icon: "st.thermostat.cool") - state("heat", action:"thermostat.auto", icon: "st.thermostat.heat") + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 3, height: 3) { + state("auto", label: "SCHEDULED", icon:"st.Office.office7") + state("off", label: "OFF", icon:"st.thermostat.heating-cooling-off") + state("heat", label: "MANUAL", icon:"st.Weather.weather2") + state("emergency heat", label: "BOOST", icon:"st.Health & Wellness.health7") } + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") } + valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'${currentValue}', action:"emergencyHeat") + } + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" } @@ -101,7 +113,22 @@ metadata { } main(["thermostatOperatingState", "thermostatMode"]) - details(["mode_auto", "mode_manual", "mode_off", "refresh"]) + + // ============================================================ + // iOS TILES + // To expose iOS optimised tiles, comment out the details line in Android Tiles section and uncomment details line below. + + details(["hotWaterRelay", "mode_auto", "mode_manual", "mode_off", "boost", "refresh"]) + + // ============================================================ + + // ============================================================ + // ANDROID TILES + // To expose Android optimised tiles, comment out the details line in iOS Tiles section and uncomment details line below. + + //details(["hotWaterRelay_small", "thermostatMode", "mode_auto", "mode_manual", "mode_off", "boost", "refresh"]) + + // ============================================================ } } @@ -141,7 +168,19 @@ def heat() { } def emergencyHeat() { - setThermostatMode('heat') + log.debug "Executing 'boost'" + + def latestThermostatMode = device.latestState('thermostatMode') + + //Don't do if already in BOOST mode. + if (latestThermostatMode.stringValue != 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + log.debug "Already in boost mode." + } + + } def auto() { @@ -149,7 +188,6 @@ def auto() { } def setThermostatMode(mode) { - mode = mode == 'emergency heat'? 'heat' : mode def args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] ] @@ -162,7 +200,12 @@ def setThermostatMode(mode) { args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] ] - } + } else if (mode == 'emergency heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":99}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: 60], targetHeatTemperature: [targetValue: "99"]]]] + ] + } api('thermostat_mode', args) { mode = mode == 'range' ? 'auto' : mode @@ -178,6 +221,9 @@ log.debug "Executing 'poll'" //Construct status message def statusMsg = "Currently" + //Boost button label + def boostLabel = "Start\nBoost" + // determine hive hot water operating mode def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] @@ -191,6 +237,13 @@ log.debug "Executing 'poll'" mode = 'off' statusMsg = statusMsg + " set to OFF" } + else if (activeHeatCoolMode == "BOOST") { + mode = 'emergency heat' + statusMsg = statusMsg + " set to BOOST" + def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] + boostLabel = "Boosting for \n" + boostTime + " mins" + sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") + } else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { mode = 'heat' statusMsg = statusMsg + " set to MANUAL" @@ -207,14 +260,20 @@ log.debug "Executing 'poll'" log.debug "stateHotWaterRelay: $stateHotWaterRelay" if (stateHotWaterRelay == "ON") { + sendEvent(name: 'temperature', value: 99, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'heatingSetpoint', value: 99, unit: "C", state: "heat", displayed: false) sendEvent(name: 'thermostatOperatingState', value: "heating") statusMsg = statusMsg + " and is HEATING" } else { + sendEvent(name: 'temperature', value: 0, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'heatingSetpoint', value: 0, unit: "C", state: "heat", displayed: false) sendEvent(name: 'thermostatOperatingState', value: "idle") statusMsg = statusMsg + " and is IDLE" } - sendEvent("name":"hiveHotWater", "value":statusMsg) + sendEvent("name":"hiveHotWater", "value":statusMsg, displayed: false) + sendEvent("name":"boostLabel", "value": boostLabel, displayed: false) + } } From 34f4a31b633694ccfe05f81f06386ff4df887483 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 17:48:40 +0000 Subject: [PATCH 036/685] Tweaks to Android tile layout option --- .../hive-active-hot-water.src/hive-active-hot-water.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 9960a55051f..be7d820f7ca 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -112,7 +112,7 @@ metadata { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - main(["thermostatOperatingState", "thermostatMode"]) + main(["hotWaterRelay", "thermostatMode"]) // ============================================================ // iOS TILES From 367ef88eb84edff3421a88c2c305d5c4c55c090e Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 21:58:02 +0000 Subject: [PATCH 037/685] New smart app to turn thermostats off in selected hub modes and back on when hub mode changes back. --- .../auto-off-for-thermostats.groovy | 54 ++++++ .../thermostat-off-automation.groovy | 163 ++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 smartapps/alyc100/parent/auto-off-for-thermostats.src/auto-off-for-thermostats.groovy create mode 100644 smartapps/alyc100/thermostatoffautomation/thermostat-off-automation.src/thermostat-off-automation.groovy diff --git a/smartapps/alyc100/parent/auto-off-for-thermostats.src/auto-off-for-thermostats.groovy b/smartapps/alyc100/parent/auto-off-for-thermostats.src/auto-off-for-thermostats.groovy new file mode 100644 index 00000000000..c3e9ecd56d9 --- /dev/null +++ b/smartapps/alyc100/parent/auto-off-for-thermostats.src/auto-off-for-thermostats.groovy @@ -0,0 +1,54 @@ +/** + * Auto Off for Thermostats + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). + * Turns thermostats back into desired operating state when mode changes back (e.g home). + * + * 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 + * 22.11.2015 + * v1.0 - Initial Release + */ + +definition( + name: "Auto Off for Thermostats", + namespace: "alyc100/parent", + author: "Alex Lee Yuk Cheung", + description: "Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into desired operating state when mode changes back (e.g home).", + category: "My Apps", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo@2x.png" +) + +preferences { + page(name: "mainPage", title: "Thermostat Off Automation", install: true, uninstall: true,submitOnChange: true) { + section { + app(name: "thermostatOffAutomation", appName: "Thermostat Off Automation", namespace: "alyc100/thermostatoffautomation", title: "Create New Thermostat Off Rule", multiple: true) + } + } +} + +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + unsubscribe() + initialize() +} + +def initialize() { + // nothing needed here, since the child apps will handle preferences/subscriptions +} \ No newline at end of file diff --git a/smartapps/alyc100/thermostatoffautomation/thermostat-off-automation.src/thermostat-off-automation.groovy b/smartapps/alyc100/thermostatoffautomation/thermostat-off-automation.src/thermostat-off-automation.groovy new file mode 100644 index 00000000000..ba728526de1 --- /dev/null +++ b/smartapps/alyc100/thermostatoffautomation/thermostat-off-automation.src/thermostat-off-automation.groovy @@ -0,0 +1,163 @@ +/** + * Thermostat Off Automation + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). + * Turns thermostats back into desired operating state when mode changes back (e.g home). + * + * 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 + * 22.11.2015 + * v1.0 - Initial Release + */ + +definition( + name: "Thermostat Off Automation", + namespace: "alyc100/thermostatoffautomation", + singleInstance: true, + author: "Alex Lee Yuk Cheung", + description: "Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into desired operating state when mode changes back (e.g home).", + category: "My Apps", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo@2x.png" +) + +preferences { + section("When SmartThings enters these modes") { + input "modes", "mode", multiple: true, required: true + } + + section("Turn these thermostats off") { + input "thermostats", "capability.thermostat", multiple: true, required: true + } + + section("And reactivate thermostats to this mode") { + input "thermostatMode", "enum", multiple: false, + options: ["Set To Schedule", "Boost for 60 minutes", "Keep Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule' + } + + section("If setting to Manual, set the temperature to this") { + input "temp", "number", required: false, defaultValue: 21 + } + + section( "Notifications" ) { + input "sendPushMessage", "enum", title: "Send a push notification?", + options: ["Yes", "No"], required: false + input "phone", "phone", title: "Send a Text Message?", required: false + } +} + +def installed() { + log.debug "Installed with settings: ${settings}" + def currentMode = location.mode + log.debug "currentMode = $currentMode" + //set up initial thermostat state and force thermostat into correct mode + state.thermostatDeactivated = false + if (currentMode in modes) { + takeAction(currentMode) + } + subscribe(location, "mode", modeevent) +} + +def updated() { + log.debug "Updated with settings: ${settings}" + def currentMode = location.mode + log.debug "currentMode = $currentMode" + unsubscribe() + //set up initial thermostat state and force thermostat into correct mode + state.thermostatDeactivated = false + if (currentMode in modes) { + takeAction(currentMode) + } + subscribe(location, "mode", modeevent) +} + +def modeevent(evt) { + log.debug "evt.name: $evt.value" + takeAction(evt.value) +} + +def takeAction(mode) { + // Is incoming mode in the event input enumeration + if (mode in modes) + { + //Check thermostat is not already deactivated + if (!state.thermostatDeactivated) + { + //Turn selected thermostats off + log.debug "$mode in selected modes" + def message = '' + for (thermostat in thermostats) { + message = "SmartThings has turned $thermostat.label off because mode has changed to $mode" + log.info message + send(message) + thermostat.off() + } + state.thermostatDeactivated = true + } + } + else { + log.debug "$mode is not in select modes" + //Check if thermostats have previously been deactivated + if (state.thermostatDeactivated) + { + //Add detail to push message if set to Manual is specified + log.debug "Thermostats have been deactivated, turning back on" + def thermostatModeDetail = thermostatMode + if (thermostatMode == "Set to Manual") { + thermostatModeDetail = thermostatModeDetail + " at $temp°C" + } + + //Turn each thermostat to desired mode + def message = '' + for (thermostat in thermostats) { + message = "SmartThings has turned $thermostat.label to $thermostatModeDetail because mode has changed to $mode." + log.info message + send(message) + log.debug "Setting $thermostat.label to $thermostatModeDetail" + if (thermostatMode == "Set to Manual") { + thermostat.heat() + thermostat.setHeatingSetpoint(temp) + } + else if (thermostatMode == "Keep Off") { + thermostat.off() + } + else if (thermostatMode == "Boost for 60 minutes") { + thermostat.auto() + thermostat.emergencyHeat() + } + else { + thermostat.auto() + } + } + state.thermostatDeactivated = false + } + else + { + log.debug "Thermostats were not deactivated. No action taken." + } + } +} + +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 +} \ No newline at end of file From 277182d243093093b7a2c01ae468342e02aabbc2 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 22:58:27 +0000 Subject: [PATCH 038/685] Initial release. --- .../auto-mode-for-thermostats.groovy | 55 +++++ .../thermostat-mode-automation.groovy | 190 ++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy create mode 100644 smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy diff --git a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy new file mode 100644 index 00000000000..5318d989141 --- /dev/null +++ b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy @@ -0,0 +1,55 @@ +/** + * Auto Mode for Thermostats + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * Changes operating mode (e.g off) of selected thermostats when Smartthings hub changes into selected modes (e.g away). + * Turns thermostats back into another desired operating mode (e.g Emergency Heat) when mode changes back (e.g home). + * + * 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 + * 22.11.2015 + * v1.0 - Initial Release + */ + +definition( + name: "Auto Mode for Thermostats", + namespace: "alyc100/parent", + author: "Alex Lee Yuk Cheung", + description: "Changes operating mode (e.g off) of selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into another desired operating mode (e.g Emergency Heat) when mode changes back (e.g home).", + category: "My Apps", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo@2x.png" +) + +preferences { + page(name: "mainPage", title: "Thermostat Mode Automation", install: true, uninstall: true,submitOnChange: true) { + section { + app(name: "thermostatmodeautomation", appName: "Thermostat Mode Automation", namespace: "alyc100/thermostatmodeautomation", title: "Create New Thermostat Mode Rule", multiple: true) + } + } +} + +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + unsubscribe() + initialize() +} + +def initialize() { + // nothing needed here, since the child apps will handle preferences/subscriptions +} + diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy new file mode 100644 index 00000000000..ed158c00cd1 --- /dev/null +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -0,0 +1,190 @@ +/** + * Thermostat Mode Automation + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * Changes operating mode (e.g off) of selected thermostats when Smartthings hub changes into selected modes (e.g away). + * Turns thermostats back into another desired operating mode (e.g Emergency Heat) when mode changes back (e.g home). + * + * 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 + * 22.11.2015 + * v1.0 - Initial Release + */ + +definition( + name: "Thermostat Mode Automation", + namespace: "alyc100/thermostatmodeautomation", + singleInstance: true, + author: "Alex Lee Yuk Cheung", + description: "Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into desired operating state when mode changes back (e.g home).", + category: "My Apps", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo@2x.png" +) + +preferences { + section("When SmartThings enters these modes") { + input "modes", "mode", multiple: true, required: true + } + + section("Using these thermostats") { + input "thermostats", "capability.thermostat", multiple: true, required: true + } + + section("Set thermostats to this mode") { + input "alteredThermostatMode", "enum", multiple: false, + options: ["Set To Schedule", "Boost for 60 minutes", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Keep Off' + } + + section("And then change thermostats back to this mode when SmartThings mode changes back") { + input "thermostatMode", "enum", multiple: false, + options: ["Set To Schedule", "Boost for 60 minutes", "Keep Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule' + } + + section("If setting to Manual, set the temperature to this") { + input "temp", "number", required: false, defaultValue: 21 + } + + section( "Notifications" ) { + input "sendPushMessage", "enum", title: "Send a push notification?", + options: ["Yes", "No"], required: false + input "phone", "phone", title: "Send a Text Message?", required: false + } +} + +def installed() { + log.debug "Installed with settings: ${settings}" + def currentMode = location.mode + log.debug "currentMode = $currentMode" + //set up initial thermostat state and force thermostat into correct mode + state.thermostatAltered = false + if (currentMode in modes) { + takeAction(currentMode) + } + subscribe(location, "mode", modeevent) +} + +def updated() { + log.debug "Updated with settings: ${settings}" + def currentMode = location.mode + log.debug "currentMode = $currentMode" + unsubscribe() + //set up initial thermostat state and force thermostat into correct mode + state.thermostatAltered = false + if (currentMode in modes) { + takeAction(currentMode) + } + subscribe(location, "mode", modeevent) +} + +def modeevent(evt) { + log.debug "evt.name: $evt.value" + takeAction(evt.value) +} + +def takeAction(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" + def thermostatModeDetail = alteredThermostatMode + if (alteredThermostatMode == "Set to Manual") { + thermostatModeDetail = thermostatModeDetail + " at $temp°C" + } + + //Turn each thermostat to desired mode + def message = '' + for (thermostat in thermostats) { + message = "SmartThings has turned $thermostat.label to $alteredThermostatMode because mode has changed to $mode" + log.info message + send(message) + log.debug "Setting $thermostat.label to $thermostatModeDetail" + if (alteredThermostatMode == "Set to Manual") { + thermostat.heat() + thermostat.setHeatingSetpoint(temp) + } + else if (alteredThermostatMode == "Turn Off") { + thermostat.off() + } + else if (alteredThermostatMode == "Boost for 60 minutes") { + thermostat.auto() + thermostat.emergencyHeat() + } + else { + thermostat.auto() + } + } + state.thermostatAltered = true + } + } + else { + log.debug "$mode is not in select modes" + //Check if thermostats have previously been altered + if (state.thermostatAltered) + { + //Add detail to push message if set to Manual is specified + log.debug "Thermostats have been altered, turning back to $thermostatMode" + def thermostatModeDetail = thermostatMode + if (thermostatMode == "Set to Manual") { + thermostatModeDetail = thermostatModeDetail + " at $temp°C" + } + + //Turn each thermostat to desired mode + def message = '' + for (thermostat in thermostats) { + message = "SmartThings has turned $thermostat.label to $thermostatModeDetail because mode has changed to $mode." + log.info message + send(message) + log.debug "Setting $thermostat.label to $thermostatModeDetail" + if (thermostatMode == "Set to Manual") { + thermostat.heat() + thermostat.setHeatingSetpoint(temp) + } + else if (thermostatMode == "Keep Off") { + thermostat.off() + } + else if (thermostatMode == "Boost for 60 minutes") { + thermostat.auto() + thermostat.emergencyHeat() + } + else { + thermostat.auto() + } + } + state.thermostatAltered = false + } + else + { + log.debug "Thermostats were not altered. No action taken." + } + } +} + +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 +} \ No newline at end of file From 25bf2eb78d1c86b6da08bb4e2f46e63ec7f0c063 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Sun, 22 Nov 2015 23:00:00 +0000 Subject: [PATCH 039/685] Removed old namespace --- .../auto-off-for-thermostats.groovy | 54 ------ .../thermostat-off-automation.groovy | 163 ------------------ 2 files changed, 217 deletions(-) delete mode 100644 smartapps/alyc100/parent/auto-off-for-thermostats.src/auto-off-for-thermostats.groovy delete mode 100644 smartapps/alyc100/thermostatoffautomation/thermostat-off-automation.src/thermostat-off-automation.groovy diff --git a/smartapps/alyc100/parent/auto-off-for-thermostats.src/auto-off-for-thermostats.groovy b/smartapps/alyc100/parent/auto-off-for-thermostats.src/auto-off-for-thermostats.groovy deleted file mode 100644 index c3e9ecd56d9..00000000000 --- a/smartapps/alyc100/parent/auto-off-for-thermostats.src/auto-off-for-thermostats.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Auto Off for Thermostats - * - * Copyright 2015 Alex Lee Yuk Cheung - * - * Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). - * Turns thermostats back into desired operating state when mode changes back (e.g home). - * - * 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 - * 22.11.2015 - * v1.0 - Initial Release - */ - -definition( - name: "Auto Off for Thermostats", - namespace: "alyc100/parent", - author: "Alex Lee Yuk Cheung", - description: "Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into desired operating state when mode changes back (e.g home).", - category: "My Apps", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo@2x.png" -) - -preferences { - page(name: "mainPage", title: "Thermostat Off Automation", install: true, uninstall: true,submitOnChange: true) { - section { - app(name: "thermostatOffAutomation", appName: "Thermostat Off Automation", namespace: "alyc100/thermostatoffautomation", title: "Create New Thermostat Off Rule", multiple: true) - } - } -} - -def installed() { - log.debug "Installed with settings: ${settings}" - initialize() -} - -def updated() { - log.debug "Updated with settings: ${settings}" - unsubscribe() - initialize() -} - -def initialize() { - // nothing needed here, since the child apps will handle preferences/subscriptions -} \ No newline at end of file diff --git a/smartapps/alyc100/thermostatoffautomation/thermostat-off-automation.src/thermostat-off-automation.groovy b/smartapps/alyc100/thermostatoffautomation/thermostat-off-automation.src/thermostat-off-automation.groovy deleted file mode 100644 index ba728526de1..00000000000 --- a/smartapps/alyc100/thermostatoffautomation/thermostat-off-automation.src/thermostat-off-automation.groovy +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Thermostat Off Automation - * - * Copyright 2015 Alex Lee Yuk Cheung - * - * Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). - * Turns thermostats back into desired operating state when mode changes back (e.g home). - * - * 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 - * 22.11.2015 - * v1.0 - Initial Release - */ - -definition( - name: "Thermostat Off Automation", - namespace: "alyc100/thermostatoffautomation", - singleInstance: true, - author: "Alex Lee Yuk Cheung", - description: "Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into desired operating state when mode changes back (e.g home).", - category: "My Apps", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo@2x.png" -) - -preferences { - section("When SmartThings enters these modes") { - input "modes", "mode", multiple: true, required: true - } - - section("Turn these thermostats off") { - input "thermostats", "capability.thermostat", multiple: true, required: true - } - - section("And reactivate thermostats to this mode") { - input "thermostatMode", "enum", multiple: false, - options: ["Set To Schedule", "Boost for 60 minutes", "Keep Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule' - } - - section("If setting to Manual, set the temperature to this") { - input "temp", "number", required: false, defaultValue: 21 - } - - section( "Notifications" ) { - input "sendPushMessage", "enum", title: "Send a push notification?", - options: ["Yes", "No"], required: false - input "phone", "phone", title: "Send a Text Message?", required: false - } -} - -def installed() { - log.debug "Installed with settings: ${settings}" - def currentMode = location.mode - log.debug "currentMode = $currentMode" - //set up initial thermostat state and force thermostat into correct mode - state.thermostatDeactivated = false - if (currentMode in modes) { - takeAction(currentMode) - } - subscribe(location, "mode", modeevent) -} - -def updated() { - log.debug "Updated with settings: ${settings}" - def currentMode = location.mode - log.debug "currentMode = $currentMode" - unsubscribe() - //set up initial thermostat state and force thermostat into correct mode - state.thermostatDeactivated = false - if (currentMode in modes) { - takeAction(currentMode) - } - subscribe(location, "mode", modeevent) -} - -def modeevent(evt) { - log.debug "evt.name: $evt.value" - takeAction(evt.value) -} - -def takeAction(mode) { - // Is incoming mode in the event input enumeration - if (mode in modes) - { - //Check thermostat is not already deactivated - if (!state.thermostatDeactivated) - { - //Turn selected thermostats off - log.debug "$mode in selected modes" - def message = '' - for (thermostat in thermostats) { - message = "SmartThings has turned $thermostat.label off because mode has changed to $mode" - log.info message - send(message) - thermostat.off() - } - state.thermostatDeactivated = true - } - } - else { - log.debug "$mode is not in select modes" - //Check if thermostats have previously been deactivated - if (state.thermostatDeactivated) - { - //Add detail to push message if set to Manual is specified - log.debug "Thermostats have been deactivated, turning back on" - def thermostatModeDetail = thermostatMode - if (thermostatMode == "Set to Manual") { - thermostatModeDetail = thermostatModeDetail + " at $temp°C" - } - - //Turn each thermostat to desired mode - def message = '' - for (thermostat in thermostats) { - message = "SmartThings has turned $thermostat.label to $thermostatModeDetail because mode has changed to $mode." - log.info message - send(message) - log.debug "Setting $thermostat.label to $thermostatModeDetail" - if (thermostatMode == "Set to Manual") { - thermostat.heat() - thermostat.setHeatingSetpoint(temp) - } - else if (thermostatMode == "Keep Off") { - thermostat.off() - } - else if (thermostatMode == "Boost for 60 minutes") { - thermostat.auto() - thermostat.emergencyHeat() - } - else { - thermostat.auto() - } - } - state.thermostatDeactivated = false - } - else - { - log.debug "Thermostats were not deactivated. No action taken." - } - } -} - -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 -} \ No newline at end of file From 2db238e8d0b1df7b952124f48042cec2e6cb6195 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 23:13:48 +0000 Subject: [PATCH 040/685] Updates to Hive Hot Water device in Things screen --- .../hive-active-hot-water.groovy | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index be7d820f7ca..edbf7ea0bd1 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -27,8 +27,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 121 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 129 before publishing. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 122 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 130 before publishing. * * 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: @@ -46,6 +46,7 @@ * * 22.11.2015 * v1.1 - Implemented Boost functionality! Added optimised Android tile layout. + * v1.1.1 - Improvements to display of Things screen. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -84,11 +85,11 @@ metadata { state( "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff") } - standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 3, height: 3) { - state("auto", label: "SCHEDULED", icon:"st.Office.office7") - state("off", label: "OFF", icon:"st.thermostat.heating-cooling-off") - state("heat", label: "MANUAL", icon:"st.Weather.weather2") - state("emergency heat", label: "BOOST", icon:"st.Health & Wellness.health7") + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 3, height: 3) { + state("auto", label: "SCHEDULED", action:"heat", icon:"st.Bath.bath6") + state("off", label: "OFF", action:"auto", icon:"st.Bath.bath6") + state("heat", label: "MANUAL", action:"off", icon:"st.Bath.bath6") + state("emergency heat", label: "BOOST", action:"auto", icon:"st.Bath.bath6") } @@ -112,7 +113,7 @@ metadata { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - main(["hotWaterRelay", "thermostatMode"]) + main(["thermostatMode"]) // ============================================================ // iOS TILES From d500984d66b95dbeafc469fe35f2e122bbf1f26c Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 22 Nov 2015 23:40:38 +0000 Subject: [PATCH 041/685] Updates to Things screen display --- .../hive-active-heating.groovy | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index a19ed05cd1a..dc5bbe62e3d 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -33,8 +33,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 180 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 188 before publishing. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 197 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 207 before publishing. * * 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: @@ -68,6 +68,7 @@ * v1.10 - Added Boost button!! Reduced number of activity messages. * v1.10.1 - Tweaks to temperature formatting. * v1.10.2 - Added icons to thermostat mode states + * v1.10.3 - Tweaks to display on Things screen. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -126,7 +127,12 @@ metadata { [value: 29, color: "#b9203b"] ] } - + + standardTile("thermostat_main", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state "idle", label:'${currentValue}', icon: "st.Weather.weather2" + state "heating", label:'${currentValue}', icon: "st.Weather.weather2", backgroundColor:"#EC6E05" + } + controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" } @@ -184,7 +190,7 @@ metadata { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - main(["thermostat", "thermostatOperatingState"]) + main(["thermostat_main"]) // ============================================================ // iOS TILES @@ -276,6 +282,9 @@ def emergencyHeat() { if (latestThermostatMode.stringValue != 'emergency heat') { setThermostatMode('emergency heat') } + else { + log.debug "Already in boost mode." + } } @@ -334,6 +343,7 @@ def poll() { sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") + sendEvent(name: 'thermostatSetpoint', value: heatingSetpoint, unit: "C", state: "heat", displayed: false) // determine hive operating mode def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] From e0e3b22e37fb15ee326582f86d1ffdce6266608a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 23 Nov 2015 21:13:42 +0000 Subject: [PATCH 042/685] Improve display in SmartTiles app --- .../hive-active-heating.src/hive-active-heating.groovy | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index dc5bbe62e3d..ec269ef674f 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -33,8 +33,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 197 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 207 before publishing. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 202 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 210 before publishing. * * 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: @@ -69,6 +69,9 @@ * v1.10.1 - Tweaks to temperature formatting. * v1.10.2 - Added icons to thermostat mode states * v1.10.3 - Tweaks to display on Things screen. + * + * 23.11.2015 + * v1.10.4 - Set thermostatFanMode to 'off' to improve SmartTiles display */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -344,6 +347,7 @@ def poll() { sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") sendEvent(name: 'thermostatSetpoint', value: heatingSetpoint, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) // determine hive operating mode def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] From c6085b3a2841b282e5fb3d2f27fcb4352327b7bf Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 23 Nov 2015 22:02:48 +0000 Subject: [PATCH 043/685] Improvements to instructions --- .../auto-mode-for-thermostats.groovy | 10 +++++++++- .../thermostat-mode-automation.groovy | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy index 5318d989141..ef52bdddf77 100644 --- a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy +++ b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy @@ -1,8 +1,16 @@ /** - * Auto Mode for Thermostats + * Auto Mode for Thermostats (PARENT APP) * * Copyright 2015 Alex Lee Yuk Cheung * + * 1. Save and Self-publish 'Auto Mode for Thermostats' SmartApp (https://github.com/alyc100/SmartThingsPublic/blob/master/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy) + * by creating a new SmartApp in the SmartThings IDE and pasting the source code in the "From Code" tab. + * + * 2. Save (do not publish) 'Thermostat Mode Automation' SmartApp (https://github.com/alyc100/SmartThingsPublic/blob/master/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy) + * by creating a new SmartApp in the SmartThings IDE and pasting the source code in the "From Code" tab. + * + * 3. Open SmartThings mobile app and locate "Auto Mode for Thermostats" SmartApp in the "My Apps" section of the Marketplace. + * * Changes operating mode (e.g off) of selected thermostats when Smartthings hub changes into selected modes (e.g away). * Turns thermostats back into another desired operating mode (e.g Emergency Heat) when mode changes back (e.g home). * diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index ed158c00cd1..695c5f36eed 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -1,8 +1,16 @@ /** - * Thermostat Mode Automation + * Thermostat Mode Automation (CHILD APP to Auto Mode for Thermostats) * * Copyright 2015 Alex Lee Yuk Cheung * + * 1. Save and Self-publish 'Auto Mode for Thermostats' SmartApp (https://github.com/alyc100/SmartThingsPublic/blob/master/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy) + * by creating a new SmartApp in the SmartThings IDE and pasting the source code in the "From Code" tab. + * + * 2. Save (do not publish) 'Thermostat Mode Automation' SmartApp (https://github.com/alyc100/SmartThingsPublic/blob/master/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy) + * by creating a new SmartApp in the SmartThings IDE and pasting the source code in the "From Code" tab. + * + * 3. Open SmartThings mobile app and locate "Auto Mode for Thermostats" SmartApp in the "My Apps" section of the Marketplace. + * * Changes operating mode (e.g off) of selected thermostats when Smartthings hub changes into selected modes (e.g away). * Turns thermostats back into another desired operating mode (e.g Emergency Heat) when mode changes back (e.g home). * From 61ee8f1563cb464794532827bb747caa61a921fb Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 23 Nov 2015 22:56:38 +0000 Subject: [PATCH 044/685] Minor update --- .../auto-mode-for-thermostats.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy index ef52bdddf77..ad1451bb95e 100644 --- a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy +++ b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy @@ -31,6 +31,7 @@ definition( name: "Auto Mode for Thermostats", namespace: "alyc100/parent", + singleInstance: true, author: "Alex Lee Yuk Cheung", description: "Changes operating mode (e.g off) of selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into another desired operating mode (e.g Emergency Heat) when mode changes back (e.g home).", category: "My Apps", From cf02a8018e5c5e4fdcd3441110983b0763ecbdc4 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 23 Nov 2015 23:11:30 +0000 Subject: [PATCH 045/685] Minor update --- .../thermostat-mode-automation.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 695c5f36eed..f7afc24dd99 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -31,7 +31,6 @@ definition( name: "Thermostat Mode Automation", namespace: "alyc100/thermostatmodeautomation", - singleInstance: true, author: "Alex Lee Yuk Cheung", description: "Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into desired operating state when mode changes back (e.g home).", category: "My Apps", From 53bf59822e317161f38e1936fd0ede0ef1af2322 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 01:00:23 +0000 Subject: [PATCH 046/685] v1.1 - Support for Switches. Better preferences. Optional reset action. --- .../thermostat-mode-automation.groovy | 268 ++++++++++++------ 1 file changed, 177 insertions(+), 91 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index f7afc24dd99..12aba37d234 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -26,11 +26,15 @@ * VERSION HISTORY * 22.11.2015 * v1.0 - Initial Release + * v1.1 - Now with support for Switch detection. + * Dynamic preference screen. + * Introduced option to disable thermostat reset. */ definition( name: "Thermostat Mode Automation", namespace: "alyc100/thermostatmodeautomation", + singleInstance: true, author: "Alex Lee Yuk Cheung", description: "Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into desired operating state when mode changes back (e.g home).", category: "My Apps", @@ -39,66 +43,163 @@ definition( ) preferences { - section("When SmartThings enters these modes") { - input "modes", "mode", multiple: true, required: true - } - - section("Using these thermostats") { - input "thermostats", "capability.thermostat", multiple: true, required: true - } - - section("Set thermostats to this mode") { - input "alteredThermostatMode", "enum", multiple: false, - options: ["Set To Schedule", "Boost for 60 minutes", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Keep Off' - } + page(name: "configurePage") - section("And then change thermostats back to this mode when SmartThings mode changes back") { - input "thermostatMode", "enum", multiple: false, - options: ["Set To Schedule", "Boost for 60 minutes", "Keep Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule' - } +} + +def configurePage() { + dynamicPage(name: "configurePage", title: "", 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 for 60 minutes", "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", + options: ["Set To Schedule", "Boost for 60 minutes", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule') + } + } - section("If setting to Manual, set the temperature to this") { - input "temp", "number", required: false, defaultValue: 21 - } + section( "Additional configuration" ) { + input ("temp", "number", 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: false - input "phone", "phone", title: "Send a Text Message?", required: false - } + 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) + } + } } def installed() { log.debug "Installed with settings: ${settings}" - def currentMode = location.mode - log.debug "currentMode = $currentMode" //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false - if (currentMode in modes) { - takeAction(currentMode) + + //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", modeeventHandler) + } + else { + if (theSwitch.currentSwitch == "on") { + takeActionForSwitch(theSwitch.currentSwitch) + } + subscribe(theSwitch, "switch", switchHandler) } - subscribe(location, "mode", modeevent) } def updated() { log.debug "Updated with settings: ${settings}" - def currentMode = location.mode - log.debug "currentMode = $currentMode" unsubscribe() //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false - if (currentMode in modes) { - takeAction(currentMode) + + //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", modeeventHandler) + } + else { + if (theSwitch.currentSwitch == "on") { + takeActionForSwitch(theSwitch.currentSwitch) + } + subscribe(theSwitch, "switch", switchHandler) + } +} + +//Handler and action for switch detection +def switchHandler(evt) { + log.debug "evt.name: $evt.value" + takeActionForSwitch(evt.value) +} + +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" + changeAllThermostatsModes(thermostats, alteredThermostatMode, "$theSwitch.label has turned on") + 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") + { + //Add detail to push message if set to Manual is specified + log.debug "Thermostats have been altered, turning back to $resumedThermostatMode" + changeAllThermostatsModes(thermostats, resumedThermostatMode, "$theSwitch.label has turned off") + } + state.thermostatAltered = false + } + else + { + log.debug "Thermostats were not altered. No action taken." + } } - subscribe(location, "mode", modeevent) } -def modeevent(evt) { +//Handler and action for mode detection +def modeeventHandler(evt) { log.debug "evt.name: $evt.value" - takeAction(evt.value) + takeActionForMode(evt.value) } -def takeAction(mode) { +def takeActionForMode(mode) { // Is incoming mode in the event input enumeration if (mode in modes) { @@ -109,33 +210,8 @@ def takeAction(mode) { //Add detail to push message if set to Manual is specified log.debug "$mode in selected modes, turning thermostats to $alteredThermostatMode" - def thermostatModeDetail = alteredThermostatMode - if (alteredThermostatMode == "Set to Manual") { - thermostatModeDetail = thermostatModeDetail + " at $temp°C" - } + changeAllThermostatsModes(thermostats, alteredThermostatMode, "mode has changed to $mode") - //Turn each thermostat to desired mode - def message = '' - for (thermostat in thermostats) { - message = "SmartThings has turned $thermostat.label to $alteredThermostatMode because mode has changed to $mode" - log.info message - send(message) - log.debug "Setting $thermostat.label to $thermostatModeDetail" - if (alteredThermostatMode == "Set to Manual") { - thermostat.heat() - thermostat.setHeatingSetpoint(temp) - } - else if (alteredThermostatMode == "Turn Off") { - thermostat.off() - } - else if (alteredThermostatMode == "Boost for 60 minutes") { - thermostat.auto() - thermostat.emergencyHeat() - } - else { - thermostat.auto() - } - } state.thermostatAltered = true } } @@ -144,35 +220,15 @@ def takeAction(mode) { //Check if thermostats have previously been altered if (state.thermostatAltered) { - //Add detail to push message if set to Manual is specified - log.debug "Thermostats have been altered, turning back to $thermostatMode" - def thermostatModeDetail = thermostatMode - if (thermostatMode == "Set to Manual") { - thermostatModeDetail = thermostatModeDetail + " at $temp°C" + //Check if user wants to reset thermostats + if (resetThermostats == "true") + { + log.debug "Thermostats have been altered, turning back to $resumedThermostatMode" + + //Turn each thermostat to desired mode + changeAllThermostatsModes(thermostats, resumedThermostatMode, "mode has changed to $mode") + } - - //Turn each thermostat to desired mode - def message = '' - for (thermostat in thermostats) { - message = "SmartThings has turned $thermostat.label to $thermostatModeDetail because mode has changed to $mode." - log.info message - send(message) - log.debug "Setting $thermostat.label to $thermostatModeDetail" - if (thermostatMode == "Set to Manual") { - thermostat.heat() - thermostat.setHeatingSetpoint(temp) - } - else if (thermostatMode == "Keep Off") { - thermostat.off() - } - else if (thermostatMode == "Boost for 60 minutes") { - thermostat.auto() - thermostat.emergencyHeat() - } - else { - thermostat.auto() - } - } state.thermostatAltered = false } else @@ -182,6 +238,36 @@ def takeAction(mode) { } } +//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 for 60 minutes") { + thermostat.auto() + thermostat.emergencyHeat() + } + else { + thermostat.auto() + } + } +} + private send(msg) { if ( sendPushMessage != "No" ) { log.debug( "sending push message" ) From 98cb5e83abd8728386c51eede89dece91285e691 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 01:01:23 +0000 Subject: [PATCH 047/685] Version history update --- .../auto-mode-for-thermostats.groovy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy index ad1451bb95e..395c053d954 100644 --- a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy +++ b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy @@ -26,6 +26,9 @@ * VERSION HISTORY * 22.11.2015 * v1.0 - Initial Release + * v1.1 - Now with support for Switch detection. + * Dynamic preference screen. + * Introduced option to disable thermostat reset. */ definition( From 3c5f42193b9dcfc0c38d71fef61c6437e8be0825 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 01:14:15 +0000 Subject: [PATCH 048/685] Added missing label option in preferences --- .../thermostat-mode-automation.groovy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 12aba37d234..c24a2efc9c7 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -34,7 +34,6 @@ definition( name: "Thermostat Mode Automation", namespace: "alyc100/thermostatmodeautomation", - singleInstance: true, author: "Alex Lee Yuk Cheung", description: "Turns off selected thermostats when Smartthings hub changes into selected modes (e.g away). Turns thermostats back into desired operating state when mode changes back (e.g home).", category: "My Apps", @@ -103,6 +102,10 @@ def configurePage() { options: ["Yes", "No"], required: true) input ("phone", "phone", title: "Send a Text Message?", required: false) } + + section { + label title: "Assign a name", required: false + } } } From 1d542e33c2c1d7bc0589473d28642ffe1a49bb57 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 13:07:38 +0000 Subject: [PATCH 049/685] Bug fixes and support for switches and boost mode / emergency heat --- .../thermostat-mode-automation.groovy | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index c24a2efc9c7..219e9d62f37 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -26,9 +26,15 @@ * VERSION HISTORY * 22.11.2015 * v1.0 - Initial Release + * + * 23.11.2015 * v1.1 - Now with support for Switch detection. * Dynamic preference screen. * Introduced option to disable thermostat reset. + * + * 24.11.2015 + * v1.2 - Extra Boost handling capabilities. + Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. */ definition( @@ -128,6 +134,7 @@ def installed() { takeActionForSwitch(theSwitch.currentSwitch) } subscribe(theSwitch, "switch", switchHandler) + subscribe(thermostats, "thermostatMode", thermostateventHandler) } } @@ -151,6 +158,7 @@ def updated() { takeActionForSwitch(theSwitch.currentSwitch) } subscribe(theSwitch, "switch", switchHandler) + subscribe(thermostats, "thermostatMode", thermostateventHandler) } } @@ -172,7 +180,11 @@ def takeActionForSwitch(switchState) { //Add detail to push message if set to Manual is specified log.debug "$theSwitch.label is on, turning thermostats to $alteredThermostatMode" changeAllThermostatsModes(thermostats, alteredThermostatMode, "$theSwitch.label has turned on") - state.thermostatAltered = true + //Only if reset action is specified, set the thermostatAltered state. + if (resetThermostats == "true") + { + state.thermostatAltered = true + } } } else { @@ -187,6 +199,7 @@ def takeActionForSwitch(switchState) { log.debug "Thermostats have been altered, turning back to $resumedThermostatMode" changeAllThermostatsModes(thermostats, resumedThermostatMode, "$theSwitch.label has turned off") } + //Reset app state state.thermostatAltered = false } else @@ -215,7 +228,11 @@ def takeActionForMode(mode) { log.debug "$mode in selected modes, turning thermostats to $alteredThermostatMode" changeAllThermostatsModes(thermostats, alteredThermostatMode, "mode has changed to $mode") - state.thermostatAltered = true + //Only if reset action is specified, set the thermostatAltered state. + if (resetThermostats == "true") + { + state.thermostatAltered = true + } } } else { @@ -232,6 +249,7 @@ def takeActionForMode(mode) { changeAllThermostatsModes(thermostats, resumedThermostatMode, "mode has changed to $mode") } + //Reset app state state.thermostatAltered = false } else @@ -241,6 +259,21 @@ def takeActionForMode(mode) { } } +//Handler for thermostat mode change +def thermostateventHandler(evt) { + log.debug "evt.name: $evt.value" + //If boost mode is selected as the trigger, turn switch off if boost mode finishes... + if (alteredThermostatMode == "Boost for 60 minutes") { + //if the switch is currently on, check the new mode of the thermostat and set switch to off if necessary + if (evt != "emergency heat") { + if (theSwitch.currentSwitch == "on") { + theSwitch.off() + state.thermostatAltered = false + } + } + } +} + //Helper method for thermostat mode change private changeAllThermostatsModes(thermostats, newThermostatMode, reason) { //Add detail to push message if set to Manual is specified From 854b3d407a76bc2ad640955b90329478b844f392 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 13:18:42 +0000 Subject: [PATCH 050/685] Minor update --- .../thermostat-mode-automation.groovy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 219e9d62f37..4581044651e 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -268,6 +268,10 @@ def thermostateventHandler(evt) { if (evt != "emergency heat") { if (theSwitch.currentSwitch == "on") { theSwitch.off() + def message = '' + message = "Boost has now finished. Turning $theSwitch.label off." + log.info message + send(message) state.thermostatAltered = false } } From d847264a66a429e341d0e06cb18cbf8af730d245 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 13:22:24 +0000 Subject: [PATCH 051/685] Minor update --- .../thermostat-mode-automation.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 4581044651e..cc4d85aeca7 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -265,7 +265,7 @@ def thermostateventHandler(evt) { //If boost mode is selected as the trigger, turn switch off if boost mode finishes... if (alteredThermostatMode == "Boost for 60 minutes") { //if the switch is currently on, check the new mode of the thermostat and set switch to off if necessary - if (evt != "emergency heat") { + if (evt.value != "emergency heat") { if (theSwitch.currentSwitch == "on") { theSwitch.off() def message = '' From c423174b455e52daf79a4c66d82ec69a4d2eeae4 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 13:32:46 +0000 Subject: [PATCH 052/685] Bug fixed --- .../thermostat-mode-automation.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index cc4d85aeca7..e331ac47b98 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -267,12 +267,13 @@ def thermostateventHandler(evt) { //if the switch is currently on, check the new mode of the thermostat and set switch to off if necessary if (evt.value != "emergency heat") { if (theSwitch.currentSwitch == "on") { - theSwitch.off() def message = '' message = "Boost has now finished. Turning $theSwitch.label off." log.info message send(message) - state.thermostatAltered = false + //Switching the switch to off should trigger an event that resets app state + theSwitch.off() + } } } From 591aad5b7adb261a741a9743c24b3a247ddf7dea Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 13:32:49 +0000 Subject: [PATCH 053/685] Bug fixes From 2c5f4cf8b9eab0d3cf7a2a84cdbbb228bade9cf9 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 13:34:39 +0000 Subject: [PATCH 054/685] Update version history --- .../thermostat-mode-automation.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index e331ac47b98..d6767b155f3 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -33,8 +33,9 @@ * Introduced option to disable thermostat reset. * * 24.11.2015 - * v1.2 - Extra Boost handling capabilities. - Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. + * v1.2 - Extra Boost handling capabilities. + * Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. + * v1.2.1 - Bug fixes */ definition( From 7d60b55729ccf92b54f063e6c73ee19335d16819 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 13:48:09 +0000 Subject: [PATCH 055/685] Bug fixes --- .../auto-mode-for-thermostats.groovy | 7 +++++++ .../thermostat-mode-automation.groovy | 5 ++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy index 395c053d954..07c3b3ad59b 100644 --- a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy +++ b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy @@ -26,9 +26,16 @@ * VERSION HISTORY * 22.11.2015 * v1.0 - Initial Release + * + * 23.11.2015 * v1.1 - Now with support for Switch detection. * Dynamic preference screen. * Introduced option to disable thermostat reset. + * + * 24.11.2015 + * v1.2 - Extra Boost handling capabilities. + * Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. + * v1.2.1 - Bug fixes */ definition( diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index d6767b155f3..61525541d52 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -277,7 +277,7 @@ def thermostateventHandler(evt) { } } - } + } } //Helper method for thermostat mode change @@ -301,8 +301,7 @@ private changeAllThermostatsModes(thermostats, newThermostatMode, reason) { thermostat.off() } else if (newThermostatMode == "Boost for 60 minutes") { - thermostat.auto() - thermostat.emergencyHeat() + thermostat.emergencyHeat() } else { thermostat.auto() From b6772ddab84985d7eb1501b744358e83da8031b2 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 14:34:57 +0000 Subject: [PATCH 056/685] v1.3 - Added option to select further thermostat mode if reset mode is set to Boost --- .../thermostat-mode-automation.groovy | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 61525541d52..2751e7112e0 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -36,6 +36,7 @@ * v1.2 - Extra Boost handling capabilities. * Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. * v1.2.1 - Bug fixes + * v1.3 - Option added to set mode of thermostat after boost action if reset mode is set to 'Boost for 60 minutes'. */ definition( @@ -95,9 +96,16 @@ def configurePage() { if (resetThermostats == "true") { section { - input ("resumedThermostatMode", "enum", multiple: false, title: "Reset thermostats back to this mode", + input ("resumedThermostatMode", "enum", multiple: false, title: "Reset thermostats back to this mode", submitOnChange: true, options: ["Set To Schedule", "Boost for 60 minutes", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule') } + + if (resumedThermostatMode == "Boost for 60 minutes") { + 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" ) { @@ -120,6 +128,7 @@ def installed() { log.debug "Installed with settings: ${settings}" //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false + state.boostingReset = false //Check if mode or switch is the trigger and run initialisation if (modeTrigger == "true") { @@ -144,6 +153,7 @@ def updated() { unsubscribe() //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false + state.boostingReset = false //Check if mode or switch is the trigger and run initialisation if (modeTrigger == "true") { @@ -195,10 +205,16 @@ def takeActionForSwitch(switchState) { { //Check if user wants to reset thermostats if (resetThermostats == "true") - { - //Add detail to push message if set to Manual is specified - log.debug "Thermostats have been altered, turning back to $resumedThermostatMode" + { + log.debug "Thermostats have been altered, turning back to $resumedThermostatMode" + //Turn selected thermostats into selected mode 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 for 60 minutes") { + state.boostingReset = true + } + } //Reset app state state.thermostatAltered = false @@ -245,10 +261,15 @@ def takeActionForMode(mode) { if (resetThermostats == "true") { log.debug "Thermostats have been altered, turning back to $resumedThermostatMode" - - //Turn each thermostat to desired mode + + //Turn each thermostat to selected mode 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 for 60 minutes") { + state.boostingReset = true + } + } //Reset app state state.thermostatAltered = false @@ -264,7 +285,7 @@ def takeActionForMode(mode) { def thermostateventHandler(evt) { log.debug "evt.name: $evt.value" //If boost mode is selected as the trigger, turn switch off if boost mode finishes... - if (alteredThermostatMode == "Boost for 60 minutes") { + if (state.thermostatAltered && alteredThermostatMode == "Boost for 60 minutes") { //if the switch is currently on, check the new mode of the thermostat and set switch to off if necessary if (evt.value != "emergency heat") { if (theSwitch.currentSwitch == "on") { @@ -278,6 +299,15 @@ def thermostateventHandler(evt) { } } } + + //If boost mode is selected as resumed state, need to set thermostat mode as per preference + if (state.boostingReset) { + if (evt.value != "emergency heat") { + changeAllThermostatsModes(thermostats, thermostatModeAfterBoost, "Boost has now finished") + //Reset boosting reset flag + state.boostingReset = false + } + } } //Helper method for thermostat mode change From bcd8434f0e4ea981df7b967004a0d1cfb0490e32 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 14:37:14 +0000 Subject: [PATCH 057/685] Comment updates --- .../auto-mode-for-thermostats.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy index 07c3b3ad59b..ee3b9b0360e 100644 --- a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy +++ b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy @@ -36,6 +36,7 @@ * v1.2 - Extra Boost handling capabilities. * Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. * v1.2.1 - Bug fixes + * v1.3 - Option added to set mode of thermostat after boost action if reset mode is set to 'Boost for 60 minutes'. */ definition( @@ -71,4 +72,3 @@ def updated() { def initialize() { // nothing needed here, since the child apps will handle preferences/subscriptions } - From 4cb34c1a32eaf24fc5f750d493967e3db21c1134 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 14:58:22 +0000 Subject: [PATCH 058/685] Minor bug fix --- .../thermostat-mode-automation.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 2751e7112e0..8c403e296fd 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -285,7 +285,7 @@ def takeActionForMode(mode) { def thermostateventHandler(evt) { log.debug "evt.name: $evt.value" //If boost mode is selected as the trigger, turn switch off if boost mode finishes... - if (state.thermostatAltered && alteredThermostatMode == "Boost for 60 minutes") { + if (state.thermostatAltered && (alteredThermostatMode == "Boost for 60 minutes")) { //if the switch is currently on, check the new mode of the thermostat and set switch to off if necessary if (evt.value != "emergency heat") { if (theSwitch.currentSwitch == "on") { From 87c76f70fc033c2a6c163f58f7fa580dff9cb881 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 15:01:27 +0000 Subject: [PATCH 059/685] Extra debugging --- .../thermostat-mode-automation.groovy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 8c403e296fd..3c72dbe2fcd 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -284,6 +284,9 @@ def takeActionForMode(mode) { //Handler for thermostat mode change def thermostateventHandler(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.thermostatAltered && (alteredThermostatMode == "Boost for 60 minutes")) { //if the switch is currently on, check the new mode of the thermostat and set switch to off if necessary From 65e5e0999afedb6d9c85b3bb30fa1fda03a169cc Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 15:14:03 +0000 Subject: [PATCH 060/685] Bug fix --- .../thermostat-mode-automation.groovy | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 3c72dbe2fcd..ecced0b4adc 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -129,7 +129,7 @@ def installed() { //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false state.boostingReset = false - + subscribe(thermostats, "thermostatMode", thermostateventHandler) //Check if mode or switch is the trigger and run initialisation if (modeTrigger == "true") { def currentMode = location.mode @@ -154,7 +154,7 @@ def updated() { //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false state.boostingReset = false - + subscribe(thermostats, "thermostatMode", thermostateventHandler) //Check if mode or switch is the trigger and run initialisation if (modeTrigger == "true") { def currentMode = location.mode @@ -168,9 +168,8 @@ def updated() { if (theSwitch.currentSwitch == "on") { takeActionForSwitch(theSwitch.currentSwitch) } - subscribe(theSwitch, "switch", switchHandler) - subscribe(thermostats, "thermostatMode", thermostateventHandler) - } + subscribe(theSwitch, "switch", switchHandler) + } } //Handler and action for switch detection From 968c341261b9b8ab918c7ace39d34277da274e19 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 15:37:49 +0000 Subject: [PATCH 061/685] Bug fix --- .../thermostat-mode-automation.groovy | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index ecced0b4adc..21ff38ad78a 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -287,18 +287,18 @@ def thermostateventHandler(evt) { 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.thermostatAltered && (alteredThermostatMode == "Boost for 60 minutes")) { + if (modeTrigger == "false") { //if the switch is currently on, check the new mode of the thermostat and set switch to off if necessary - if (evt.value != "emergency heat") { - if (theSwitch.currentSwitch == "on") { - def message = '' - message = "Boost has now finished. Turning $theSwitch.label off." - log.info message - send(message) + if (alteredThermostatMode == "Boost for 60 minutes") { + 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() + } } } From 1fe063392bd53b353704c2f580feb367a1d102e6 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 15:49:15 +0000 Subject: [PATCH 062/685] v1.3.1 - Bug fixed --- .../auto-mode-for-thermostats.groovy | 3 ++- .../thermostat-mode-automation.groovy | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy index ee3b9b0360e..eee6db9d35f 100644 --- a/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy +++ b/smartapps/alyc100/parent/auto-mode-for-thermostats.src/auto-mode-for-thermostats.groovy @@ -35,8 +35,9 @@ * 24.11.2015 * v1.2 - Extra Boost handling capabilities. * Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. - * v1.2.1 - Bug fixes + * v1.2.1 - Bug fixes. * v1.3 - Option added to set mode of thermostat after boost action if reset mode is set to 'Boost for 60 minutes'. + * v1.3.1 - Bug fixes. */ definition( diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 21ff38ad78a..d8f738cdfb3 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -35,8 +35,9 @@ * 24.11.2015 * v1.2 - Extra Boost handling capabilities. * Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. - * v1.2.1 - Bug fixes + * v1.2.1 - Bug fixes. * v1.3 - Option added to set mode of thermostat after boost action if reset mode is set to 'Boost for 60 minutes'. + * v1.3.1 - Bug fixes. */ definition( From 95c29de3664a6ffc4ca9898b2f5291e115daf50b Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 16:13:20 +0000 Subject: [PATCH 063/685] v1.3.1 - Bug Fixes --- .../thermostat-mode-automation.groovy | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index d8f738cdfb3..3278d3ff447 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -130,6 +130,7 @@ def installed() { //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false state.boostingReset = false + state.disableEvent = false subscribe(thermostats, "thermostatMode", thermostateventHandler) //Check if mode or switch is the trigger and run initialisation if (modeTrigger == "true") { @@ -155,6 +156,7 @@ def updated() { //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false state.boostingReset = false + state.disableEvent = false subscribe(thermostats, "thermostatMode", thermostateventHandler) //Check if mode or switch is the trigger and run initialisation if (modeTrigger == "true") { @@ -175,8 +177,10 @@ def updated() { //Handler and action for switch detection def switchHandler(evt) { - log.debug "evt.name: $evt.value" - takeActionForSwitch(evt.value) + if (state.disableEvent == false) { + log.debug "evt.name: $evt.value" + takeActionForSwitch(evt.value) + } } def takeActionForSwitch(switchState) { @@ -228,7 +232,7 @@ def takeActionForSwitch(switchState) { //Handler and action for mode detection def modeeventHandler(evt) { - log.debug "evt.name: $evt.value" + log.debug "evt.valuve: $evt.value" takeActionForMode(evt.value) } @@ -288,28 +292,36 @@ def thermostateventHandler(evt) { 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 (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 for 60 minutes") { - 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 (state.disableEvent == 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 for 60 minutes") { + if (evt.value != "emergency heat") { + //Switching the switch to off should trigger an event that resets app state + //Don't trigger event + state.disableEvent = true + theSwitch.off() + state.disableEvent = false + } + else { + //Switching the switch to on so it can't be boost again + state.disableEvent = true + theSwitch.on() + state.disableEvent = false + } } - } - } + } - //If boost mode is selected as resumed state, need to set thermostat mode as per preference - if (state.boostingReset) { - if (evt.value != "emergency heat") { - changeAllThermostatsModes(thermostats, thermostatModeAfterBoost, "Boost has now finished") - //Reset boosting reset flag - state.boostingReset = false - } + //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.disableEvent = true + changeAllThermostatsModes(thermostats, thermostatModeAfterBoost, "Boost has now finished") + state.disableEvent = false + //Reset boosting reset flag + state.boostingReset = false + } + } } } From 10e6f0a3a77d9f344d87b61f44d2cec4992a5458 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 17:01:14 +0000 Subject: [PATCH 064/685] v1.3.2 - Further bug fixes --- .../thermostat-mode-automation.groovy | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 3278d3ff447..9d9685dc268 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -38,6 +38,7 @@ * v1.2.1 - Bug fixes. * v1.3 - Option added to set mode of thermostat after boost action if reset mode is set to 'Boost for 60 minutes'. * v1.3.1 - Bug fixes. + * v1.3.2 - Stop possible infinite loop when handlers create events themselves. */ definition( @@ -130,7 +131,11 @@ def installed() { //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false state.boostingReset = false - state.disableEvent = false + + //Flags to stop possible infinite loop scenarios when handlers create events + state.internalThermostatEvent = false + state.internalSwitchEvent = false + subscribe(thermostats, "thermostatMode", thermostateventHandler) //Check if mode or switch is the trigger and run initialisation if (modeTrigger == "true") { @@ -156,7 +161,8 @@ def updated() { //set up initial thermostat state and force thermostat into correct mode state.thermostatAltered = false state.boostingReset = false - state.disableEvent = false + state.internalThermostatEvent = false + state.internalSwitchEvent = false subscribe(thermostats, "thermostatMode", thermostateventHandler) //Check if mode or switch is the trigger and run initialisation if (modeTrigger == "true") { @@ -177,10 +183,12 @@ def updated() { //Handler and action for switch detection def switchHandler(evt) { - if (state.disableEvent == false) { - log.debug "evt.name: $evt.value" + log.debug "evt.value: $evt.value" + log.debug "state.internalSwitchEvent: $state.internalSwitchEvent" + if (state.internalSwitchEvent == false) { takeActionForSwitch(evt.value) } + state.internalSwitchEvent = false } def takeActionForSwitch(switchState) { @@ -194,6 +202,7 @@ def takeActionForSwitch(switchState) { //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") @@ -212,6 +221,7 @@ def takeActionForSwitch(switchState) { { 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' @@ -232,7 +242,7 @@ def takeActionForSwitch(switchState) { //Handler and action for mode detection def modeeventHandler(evt) { - log.debug "evt.valuve: $evt.value" + log.debug "evt.value: $evt.value" takeActionForMode(evt.value) } @@ -247,6 +257,7 @@ def takeActionForMode(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. @@ -267,6 +278,7 @@ def takeActionForMode(mode) { 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' @@ -292,22 +304,18 @@ def thermostateventHandler(evt) { 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.disableEvent == false) { + 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 for 60 minutes") { + state.internalSwitchEvent = true if (evt.value != "emergency heat") { //Switching the switch to off should trigger an event that resets app state - //Don't trigger event - state.disableEvent = true theSwitch.off() - state.disableEvent = false } else { //Switching the switch to on so it can't be boost again - state.disableEvent = true theSwitch.on() - state.disableEvent = false } } } @@ -315,14 +323,14 @@ def thermostateventHandler(evt) { //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.disableEvent = true + state.internalThermostatEvent = true changeAllThermostatsModes(thermostats, thermostatModeAfterBoost, "Boost has now finished") - state.disableEvent = false //Reset boosting reset flag state.boostingReset = false } } } + state.internalThermostatEvent = false } //Helper method for thermostat mode change From 1c5752fa551c3cbc5b60e4f13c8378c6b1e70228 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 20:29:28 +0000 Subject: [PATCH 065/685] v1.1.2 - Fixes to 'On' command. Changes to Things screen display to match Hive Active Heating --- .../hive-active-hot-water.groovy | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index edbf7ea0bd1..f51e52ba7d6 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -27,8 +27,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 122 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 130 before publishing. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 128 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 136 before publishing. * * 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: @@ -47,6 +47,7 @@ * 22.11.2015 * v1.1 - Implemented Boost functionality! Added optimised Android tile layout. * v1.1.1 - Improvements to display of Things screen. + * v1.1.2 - Fixes to 'On' mode. Changes to display on Things to match Hive Heating. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -80,6 +81,11 @@ metadata { } } + standardTile("hotWaterRelay_main", "device.thermostatOperatingState", inactiveLabel: true, width: 3, height: 3) { + state( "heating", label:'${currentValue}', icon: "st.Bath.bath6", backgroundColor: "#EC6E05") + state( "idle", label:'${currentValue}', icon: "st.Bath.bath6", backgroundColor: "#ffffff") + } + standardTile("hotWaterRelay_small", "device.thermostatOperatingState", inactiveLabel: true, width: 3, height: 3) { state( "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05") state( "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff") @@ -113,7 +119,7 @@ metadata { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - main(["thermostatMode"]) + main(["hotWaterRelay_main"]) // ============================================================ // iOS TILES @@ -197,9 +203,9 @@ def setThermostatMode(mode) { nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] ] } else if (mode == 'heat') { - //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true},"targetHeatTemperature":{"targetValue":99}}}]} args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true], targetHeatTemperature: [targetValue: "99"]]]] ] } else if (mode == 'emergency heat') { //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":99}}}]} From 2b1797310ffdf9af2cbb136cf163154186c199fe Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 20:38:47 +0000 Subject: [PATCH 066/685] Changed wording from 'Manual' to 'On' --- .../hive-active-hot-water.groovy | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index f51e52ba7d6..50fde1a320b 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -27,8 +27,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 128 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 136 before publishing. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 130 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 138 before publishing. * * 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: @@ -47,7 +47,9 @@ * 22.11.2015 * v1.1 - Implemented Boost functionality! Added optimised Android tile layout. * v1.1.1 - Improvements to display of Things screen. - * v1.1.2 - Fixes to 'On' mode. Changes to display on Things to match Hive Heating. + * v1.1.2 - Change wording from 'Manual' to 'On' to match Hive app. + * Fixes to 'On' mode. + * Changes to display on Things to match Hive Heating. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -94,7 +96,7 @@ metadata { standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 3, height: 3) { state("auto", label: "SCHEDULED", action:"heat", icon:"st.Bath.bath6") state("off", label: "OFF", action:"auto", icon:"st.Bath.bath6") - state("heat", label: "MANUAL", action:"off", icon:"st.Bath.bath6") + state("heat", label: "ON", action:"off", icon:"st.Bath.bath6") state("emergency heat", label: "BOOST", action:"auto", icon:"st.Bath.bath6") } @@ -112,7 +114,7 @@ metadata { } standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" + state "default", action:"heat", label:'On', icon:"st.Weather.weather2" } standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { @@ -253,7 +255,7 @@ log.debug "Executing 'poll'" } else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { mode = 'heat' - statusMsg = statusMsg + " set to MANUAL" + statusMsg = statusMsg + " set to ON" } else { statusMsg = statusMsg + " set to SCHEDULE" From b7342a095be0219a5078d18419af2e7e3ff0991b Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 22:24:15 +0000 Subject: [PATCH 067/685] Minor bug fixes --- .../thermostat-mode-automation.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 9d9685dc268..b7ee62aa279 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -151,7 +151,6 @@ def installed() { takeActionForSwitch(theSwitch.currentSwitch) } subscribe(theSwitch, "switch", switchHandler) - subscribe(thermostats, "thermostatMode", thermostateventHandler) } } From 9f5518481e96897303ad70e9ce4a563be847800f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 24 Nov 2015 22:26:03 +0000 Subject: [PATCH 068/685] Minor bug fixes From 26882402ee33a2882d76bcbb7683dfa7da6a95e1 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 26 Nov 2015 17:12:47 +0000 Subject: [PATCH 069/685] v1.0 - Initial Release --- .../ovo-energy-meter.groovy | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy diff --git a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy new file mode 100644 index 00000000000..e358578aee1 --- /dev/null +++ b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy @@ -0,0 +1,243 @@ +/** + * OVO Energy Meter + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 1. Create a new device type 'from code' and paste this code in.(https://graph.api.smartthings.com/ide/devices) + * + * 2. Create a new device (https://graph.api.smartthings.com/device/list) + * Name: Your Choice + * Device Network Id: Your Choice + * Type: OVO Energy Meter (should be the last option) + * Location: Choose the correct location + * Hub/Group: Leave blank + * + * 3. Update device preferences + * Click on the new device to see the details. + * Click the edit button next to Preferences + * Fill in your your OVO user name, OVO password. + * You need to fill in your meter's MPAN number (Meter Point Administration Number for electricity meter) or MPRN number (Meter Point Reference Number for gas meter). + * MPAN and MPRN can be found in your OVO statements within the App under the sections 'Electricity Used' and 'Gas Used' + * + * 4. Save preferences and go into the SmartThings app and click refresh. Use an SmartApp like Pollster to refresh status every 1 minute. + * + * 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 + * 26.11.2015 + * v1.0 - Initial Release + */ +preferences { + input("username", "text", title: "Username", description: "Your OVO username (usually an email address)") + input("password", "password", title: "Password", description: "Your OVO password") + input("meterId", "text", title: "MPAN / MPRN Number", description: "The MPAN (Electricity) or MPRN (Gas) number of the meter found on your OVO correspondence") +} + +metadata { + definition (name: "OVO Energy Meter", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Polling" + capability "Power Meter" + capability "Refresh" + } + + tiles(scale: 2) { + multiAttributeTile(name:"power", type:"generic", width:6, height:4) { + tileAttribute("device.power", key: "PRIMARY_CONTROL") { + attributeState "default", label: '${currentValue}', icon:"st.Appliances.appliances17", backgroundColor:"#0a9928" + } + } + valueTile("consumptionPrice", "device.consumptionPrice", decoration: "flat", width: 3, height: 2) { + state "default", label: 'Current cost:\n${currentValue}/h' + } + valueTile("unitPrice", "device.unitPrice", decoration: "flat", width: 3, height: 2) { + state "default", label: 'Unit Price:\n${currentValue}' + } + standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" + } + + main (["power"]) + details(["power", "consumptionPrice", "unitPrice", "totalDemand", "totalConsumptionPrice", "refresh"]) + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'power' attribute + +} + +// handle commands +def poll() { + log.debug "Executing 'poll'" + refresh() +} + +def refresh() { + log.debug "Executing 'refresh'" + refreshLiveData() + + //OVO historical data API takes too long and SmartThings complains. Commented out for now. + //refreshHistoricalData() +} + +def refreshLiveData() { + api('live', []) { + data.meterlive = it.data + + // get electricity readings + def demand = (Math.round((data.meterlive.consumption.demand as BigDecimal) * 1000))/1000 + def consumptionPrice = (Math.round((data.meterlive.consumption.consumptionPrice.amount as BigDecimal) * 100))/100 + def consumptionPriceCurrency = data.meterlive.consumption.consumptionPrice.currency + def unitPrice = (Math.round((data.meterlive.consumption.unitPrice.amount as BigDecimal) * 100))/100 + def unitPriceCurrency = data.meterlive.consumption.unitPrice.currency + + demand = String.format("%1.2f",demand) + consumptionPrice = String.format("%1.2f",consumptionPrice) + unitPrice = String.format("%1.2f",unitPrice) + + // set local variables + sendEvent(name: 'power', value: "$demand KW") + sendEvent(name: 'consumptionPrice', value: "£$consumptionPrice", displayed: false) + sendEvent(name: 'unitPrice', value: "£$unitPrice", displayed: false) + + } + +} + +def refreshHistoricalData() { + api('historical', []) { + data.meterhistorical = it.data + log.debug = "$it.data" + + //{"totalConsumption":{"demand":"14.05009999999993","timestamp":1448496000000,"consumptionPrice":{"amount":"1.6538372709999916","currency":"GBP"},"unitPrice":{"amount":"0.11771","currency":"GBP"}}} + // get historical electricity readings + def totalDemand = (Math.round((data.meterhistorical.totalConsumption.demand as BigDecimal) * 1000))/1000 + def totalConsumptionPrice = (Math.round((data.meterhistorical.totalConsumption.consumptionPrice.amount as BigDecimal) * 100))/100 + def totalCconsumptionPriceCurrency = data.meterhistorical.totalConsumption.consumptionPrice.currency + + totalDemand = String.format("%1.2f",totalDemand) + totalConsumptionPrice = String.format("%1.2f",totalConsumptionPrice) + + // set local variables + sendEvent(name: 'totalPower', value: "$totalDemand KW", displayed: false) + sendEvent(name: 'totalConsumptionPrice', value: "£$totalConsumptionPrice", displayed: false) + } +} + + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + if(!isLoggedIn()) { + log.debug "Need to login" + login(method, args, success) + return + } + + def methods = [ + 'live': [uri: "https://live.ovoenergy.com/api/live/meters/${settings.meterId}/consumptions/instant", type: 'get'], + 'historical': [uri: "https://live.ovoenergy.com/api/live/meters/${settings.meterId}/consumptions/historical?startTimestamp=${(int)(new Date().getTime())/1000}&period=DAY&dataSource=CAD", type: 'get'], + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + doRequest(request.uri, args, request.type, success) +} + +// Need to be logged in before this is called. So don't call this. Call api. +def doRequest(uri, args, type, success) { + log.debug "Calling doRequest()" + log.debug "Calling $type : $uri : $args" + + def params = [ + uri: uri, + contentType: 'application/json', + headers: [ + 'Accept': 'application/json, text/plain, */*', + 'Content-Type': 'application/json;charset=UTF-8', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36', + 'Origin': 'https://my.ovoenergy.com', + 'Authorization': "${data.auth.token}" + ], + body: args + ] + + log.debug params + + def postRequest = { response -> + success.call(response) + } + + try { + if (type == 'post') { + httpPostJson(params, postRequest) + } else if (type == 'put') { + httpPutJson(params, postRequest) + } else if (type == 'get') { + httpGet(params, postRequest) + } + } catch(SocketTimeoutException e) { + log.debug("OVO data API is too slow for SmartThings!") + } + +} + +def login(method = null, args = [], success = {}) { + log.debug "Calling login()" + def params = [ + uri: 'https://my.ovoenergy.com/api/auth/login', + contentType: 'application/json;charset=UTF-8', + headers: [ + 'Accept': 'application/json, text/plain, */*', + 'Content-Type': 'application/json;charset=UTF-8', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36', + 'Origin': 'https://my.ovoenergy.com' + ], + body: [ + username: settings.username, + password: settings.password, + ] + ] + + state.cookie = '' + + httpPostJson(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + data.auth = response.data + + // set the expiration to 15 minutes + data.auth.expires_at = new Date().getTime() + 600000; + //data.auth.expires_at = new Date().getTime() + 10000; + + state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) + log.debug "Adding cookie to collection: $cookie" + log.debug "auth: $data.auth" + log.debug "cookie: $state.cookie" + + api(method, args, success) + + } +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $data.auth" + if(!data.auth) { + log.debug "No data.auth" + return false + } + + def now = new Date().getTime(); + return data.auth.expires_at > now +} From 0058a38a7a2b3d43c3d6aed35b3b7352f72b3708 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 26 Nov 2015 18:09:32 +0000 Subject: [PATCH 070/685] v1.0.1 - Updates to tiles and ability to change icon --- .../alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy index e358578aee1..ea1f77b9d66 100644 --- a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy +++ b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy @@ -33,6 +33,7 @@ * VERSION HISTORY * 26.11.2015 * v1.0 - Initial Release + * v1.0.1 - Tile layout update. Ability to change icon!!! */ preferences { input("username", "text", title: "Username", description: "Your OVO username (usually an email address)") @@ -48,11 +49,12 @@ metadata { } tiles(scale: 2) { - multiAttributeTile(name:"power", type:"generic", width:6, height:4) { + multiAttributeTile(name:"power", type:"generic", width:6, height:4, canChangeIcon: true) { tileAttribute("device.power", key: "PRIMARY_CONTROL") { attributeState "default", label: '${currentValue}', icon:"st.Appliances.appliances17", backgroundColor:"#0a9928" } } + valueTile("consumptionPrice", "device.consumptionPrice", decoration: "flat", width: 3, height: 2) { state "default", label: 'Current cost:\n${currentValue}/h' } From 48db3f3dc55596593dcc535a5d90eeff71a6b8b2 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 26 Nov 2015 19:17:25 +0000 Subject: [PATCH 071/685] v1.0.2 - SmartTiles compatability --- .../alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy index ea1f77b9d66..0df38bf073d 100644 --- a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy +++ b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy @@ -34,6 +34,7 @@ * 26.11.2015 * v1.0 - Initial Release * v1.0.1 - Tile layout update. Ability to change icon!!! + * v1.0.2 - SmartTiles compatability as Power Meter */ preferences { input("username", "text", title: "Username", description: "Your OVO username (usually an email address)") @@ -51,7 +52,7 @@ metadata { tiles(scale: 2) { multiAttributeTile(name:"power", type:"generic", width:6, height:4, canChangeIcon: true) { tileAttribute("device.power", key: "PRIMARY_CONTROL") { - attributeState "default", label: '${currentValue}', icon:"st.Appliances.appliances17", backgroundColor:"#0a9928" + attributeState "default", label: '${currentValue} W', icon:"st.Appliances.appliances17", backgroundColor:"#0a9928" } } @@ -96,18 +97,18 @@ def refreshLiveData() { data.meterlive = it.data // get electricity readings - def demand = (Math.round((data.meterlive.consumption.demand as BigDecimal) * 1000))/1000 + def demand = ((int)Math.round((data.meterlive.consumption.demand as BigDecimal) * 1000)) def consumptionPrice = (Math.round((data.meterlive.consumption.consumptionPrice.amount as BigDecimal) * 100))/100 def consumptionPriceCurrency = data.meterlive.consumption.consumptionPrice.currency def unitPrice = (Math.round((data.meterlive.consumption.unitPrice.amount as BigDecimal) * 100))/100 def unitPriceCurrency = data.meterlive.consumption.unitPrice.currency - demand = String.format("%1.2f",demand) + //demand = String.format("%4f",demand) consumptionPrice = String.format("%1.2f",consumptionPrice) unitPrice = String.format("%1.2f",unitPrice) // set local variables - sendEvent(name: 'power', value: "$demand KW") + sendEvent(name: 'power', value: "$demand", unit: "W") sendEvent(name: 'consumptionPrice', value: "£$consumptionPrice", displayed: false) sendEvent(name: 'unitPrice', value: "£$unitPrice", displayed: false) From d6ef871b6971d60fc7d854986513b2968e1c7493 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 27 Nov 2015 21:07:34 +0000 Subject: [PATCH 072/685] v1.1 - Added ability to calculate daily running costs --- .../ovo-energy-meter.groovy | 78 ++++++++++++++++++- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy index 0df38bf073d..c4196af7d42 100644 --- a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy +++ b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy @@ -35,6 +35,7 @@ * v1.0 - Initial Release * v1.0.1 - Tile layout update. Ability to change icon!!! * v1.0.2 - SmartTiles compatability as Power Meter + * v1.1 - Calculates cumulative daily costs without OVO API */ preferences { input("username", "text", title: "Username", description: "Your OVO username (usually an email address)") @@ -57,17 +58,31 @@ metadata { } valueTile("consumptionPrice", "device.consumptionPrice", decoration: "flat", width: 3, height: 2) { - state "default", label: 'Current cost:\n${currentValue}/h' + state "default", label: 'Curr. Cost:\n${currentValue}/h' } valueTile("unitPrice", "device.unitPrice", decoration: "flat", width: 3, height: 2) { state "default", label: 'Unit Price:\n${currentValue}' } + + valueTile("totalDemand", "device.averageDailyTotalPower", decoration: "flat", width: 3, height: 2) { + state "default", label: 'Total Power:\n${currentValue} kWh' + } + valueTile("totalConsumptionPrice", "device.currentDailyTotalPowerCost", decoration: "flat", width: 3, height: 2) { + state "default", label: 'Total Price:\n${currentValue}' + } + + valueTile("yesterdayTotalPower", "device.yesterdayTotalPower", decoration: "flat", width: 3, height: 1) { + state "default", label: 'Prev. Daily Power :\n${currentValue} kWh' + } + valueTile("yesterdayTotalPowerCost", "device.yesterdayTotalPowerCost", decoration: "flat", width: 3, height: 1) { + state "default", label: 'Prev. Daily Cost:\n${currentValue}' + } + standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" } - main (["power"]) - details(["power", "consumptionPrice", "unitPrice", "totalDemand", "totalConsumptionPrice", "refresh"]) + details(["power", "consumptionPrice", "unitPrice", "totalDemand", "totalConsumptionPrice", "yesterdayTotalPower", "yesterdayTotalPowerCost", "refresh"]) } } @@ -112,10 +127,66 @@ def refreshLiveData() { sendEvent(name: 'consumptionPrice', value: "£$consumptionPrice", displayed: false) sendEvent(name: 'unitPrice', value: "£$unitPrice", displayed: false) + //Calculate power costs manually without need for terrible OVO API. + if (data.dailyPowerHistory == null) + { + data.dailyPowerHistory = [:] + } + //Get current hour + + //data.hour = null + def currentHour = new Date().getAt(Calendar.HOUR_OF_DAY) + if ((data.hour == null) || (data.hour != currentHour)) { + //Reset at midnight or initial call + if ((data.hour == null) || (currentHour == 0)) { + //Store the day's power info as yesterdays + def totalPower = getTotalDailyPower() + data.yesterdayTotalPower = (Math.round((totalPower as BigDecimal) * 1000))/1000 + data.yesterdayTotalPowerCost = (Math.round(((totalPower as BigDecimal) * (data.meterlive.consumption.unitPrice.amount as BigDecimal)) * 100))/100 + sendEvent(name: 'yesterdayTotalPower', value: "$data.yesterdayTotalPower", unit: "KWh", displayed: false) + sendEvent(name: 'yesterdayTotalPowerCost', value: "£$data.yesterdayTotalPowerCost", displayed: false) + + //Reset power history + data.dailyPowerHistory = [:] + } + data.hour = currentHour + data.currentHourPowerTotal = 0 + data.currentHourPowerEntryNumber = 1 + } + else { + data.currentHourPowerEntryNumber = data.currentHourPowerEntryNumber + 1 + } + + data.currentHourPowerTotal = data.currentHourPowerTotal + (data.meterlive.consumption.demand as BigDecimal) + data.dailyPowerHistory["Hour $data.hour"] = ((data.currentHourPowerTotal as BigDecimal) / data.currentHourPowerEntryNumber) + + def totalDailyPower = getTotalDailyPower() + def hourCount = 0 + + def formattedAverageTotalPower = (Math.round((totalDailyPower as BigDecimal) * 1000))/1000 + def formattedCurrentTotalPowerCost = (Math.round(((totalDailyPower as BigDecimal) * (data.meterlive.consumption.unitPrice.amount as BigDecimal)) * 100))/100 + + formattedAverageTotalPower = String.format("%1.2f",formattedAverageTotalPower) + formattedCurrentTotalPowerCost = String.format("%1.2f",formattedCurrentTotalPowerCost) + + sendEvent(name: 'averageDailyTotalPower', value: "$formattedAverageTotalPower", unit: "KWh", displayed: false) + sendEvent(name: 'currentDailyTotalPowerCost', value: "£$formattedCurrentTotalPowerCost", displayed: false) + + log.debug "currentHour: $currentHour, data.hour: $data.hour, data.currentHourPowerTotal: $data.currentHourPowerTotal, data.currentHourPowerEntryNumber: $data.currentHourPowerEntryNumber, data.dailyPowerHistory: $data.dailyPowerHistory" + log.debug "formattedAverageTotalPower: $formattedAverageTotalPower, formattedCurrentTotalPowerCost: $formattedCurrentTotalPowerCost" + } } +private def getTotalDailyPower() { + def totalDailyPower = 0 + data.dailyPowerHistory.each { hour, averagePower -> + totalDailyPower = totalDailyPower + averagePower + }; + return totalDailyPower +} + def refreshHistoricalData() { api('historical', []) { data.meterhistorical = it.data @@ -244,3 +315,4 @@ def isLoggedIn() { def now = new Date().getTime(); return data.auth.expires_at > now } + From 13e872b9c49f2efb899f0eb573e54749ee5f4321 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 30 Nov 2015 12:29:11 +0000 Subject: [PATCH 073/685] Changes to client name when calling Hive API --- .../hive-active-hot-water.src/hive-active-hot-water.groovy | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 50fde1a320b..10716e2d9fc 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -324,7 +324,7 @@ def doRequest(uri, args, type, success) { 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', 'Accept': 'application/vnd.alertme.zoo-6.2+json', 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', + 'X-AlertMe-Client': 'Hive Web Dashboard', 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" ], body: args @@ -358,7 +358,7 @@ def getNodeId () { 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', 'Accept': 'application/vnd.alertme.zoo-6.2+json', 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', + 'X-AlertMe-Client': 'Hive Web Dashboard', 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" ] ] @@ -389,7 +389,7 @@ def login(method = null, args = [], success = {}) { 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', 'Accept': 'application/vnd.alertme.zoo-6.2+json', 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'Smartthings Hive Device Type', + 'X-AlertMe-Client': 'Hive Web Dashboard', ], body: [ sessions: [ [username: settings.username, @@ -433,4 +433,3 @@ def isLoggedIn() { return data.auth.expires_at > now } - From 71aaa7b724c3ad9b56f8730304e45c97cabb82d9 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 1 Dec 2015 10:12:03 +0000 Subject: [PATCH 074/685] v1.10.5 - Handle 'cool' mode --- .../hive-active-heating.src/hive-active-heating.groovy | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index ec269ef674f..42167ae61a4 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -72,6 +72,9 @@ * * 23.11.2015 * v1.10.4 - Set thermostatFanMode to 'off' to improve SmartTiles display + * + * 01.12.2015 + * v1.10.5 - Handle event of thermostat being set to 'cool'. Changed API client name. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -296,6 +299,7 @@ def auto() { } def setThermostatMode(mode) { + mode = mode == 'cool' ? 'heat' : mode log.debug "Executing 'setThermostatMode with mode $mode'" def args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: false]]]] @@ -436,7 +440,7 @@ def doRequest(uri, args, type, success) { 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', 'Accept': 'application/vnd.alertme.zoo-6.2+json', 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', + 'X-AlertMe-Client': 'Hive Web Dashboard', 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" ], body: args @@ -471,7 +475,7 @@ def getNodeId () { 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', 'Accept': 'application/vnd.alertme.zoo-6.2+json', 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'smartthings', + 'X-AlertMe-Client': 'Hive Web Dashboard', 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" ] ] @@ -502,7 +506,7 @@ def login(method = null, args = [], success = {}) { 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', 'Accept': 'application/vnd.alertme.zoo-6.2+json', 'Content-Type': 'application/*+json', - 'X-AlertMe-Client': 'Smartthings Hive Device Type', + 'X-AlertMe-Client': 'Hive Web Dashboard', ], body: [ sessions: [ [username: settings.username, From 98ca4642ee78cff74145ce3378675dc4ce5f3d56 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 1 Dec 2015 10:17:41 +0000 Subject: [PATCH 075/685] Comment updates --- .../hive-active-heating.src/hive-active-heating.groovy | 4 ++-- .../hive-active-hot-water.groovy | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 42167ae61a4..2804fa63800 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -33,8 +33,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 202 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 210 before publishing. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 205 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 213 before publishing. * * 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: diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 10716e2d9fc..73550f2dae2 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -27,8 +27,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 130 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 138 before publishing. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 133 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 141 before publishing. * * 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: @@ -50,6 +50,9 @@ * v1.1.2 - Change wording from 'Manual' to 'On' to match Hive app. * Fixes to 'On' mode. * Changes to display on Things to match Hive Heating. + * + * 01.12.2015 + * v1.1.3 - Handle 'cool' mode. Change API client id. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -197,6 +200,7 @@ def auto() { } def setThermostatMode(mode) { + mode = mode == 'cool' ? 'heat' : mode def args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] ] From 32767a372ad90142e5fc5fae1b9a5616edd50683 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 4 Jan 2016 16:08:49 +0000 Subject: [PATCH 076/685] v1.11 - Support for Hive Multi Zone systems --- .../hive-multi-zone-active-heating.groovy | 604 ++++++++++++++++++ 1 file changed, 604 insertions(+) create mode 100644 devicetypes/alyc100/hive-multi-zone-active-heating.src/hive-multi-zone-active-heating.groovy diff --git a/devicetypes/alyc100/hive-multi-zone-active-heating.src/hive-multi-zone-active-heating.groovy b/devicetypes/alyc100/hive-multi-zone-active-heating.src/hive-multi-zone-active-heating.groovy new file mode 100644 index 00000000000..1d7e0b220f3 --- /dev/null +++ b/devicetypes/alyc100/hive-multi-zone-active-heating.src/hive-multi-zone-active-heating.groovy @@ -0,0 +1,604 @@ +/** + * Hive Active Heating + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 1. Create a new device type (https://graph.api.smartthings.com/ide/devices) + * Name: Hive Active Heating + * Author: alyc100 + * Capabilities: + * Polling + * Refresh + * Temperature Measurement + * Thermostat + * Thermostat Mode + * Thermostat Operating State + * Thermostat Heating Setpoint + * + * Custom Commands: + * setThermostatMode + * setHeatingSetpoint + * heatingSetpointUp + * heatingSetpointDown + * + * 2. Create a new device (https://graph.api.smartthings.com/device/list) + * Name: Your Choice + * Device Network Id: Your Choice + * Type: Hive Active Heating (should be the last option) + * Location: Choose the correct location + * Hub/Group: Leave blank + * + * 3. Update device preferences + * Click on the new device to see the details. + * Click the edit button next to Preferences + * Fill in your your Hive user name, Hive password. + * + * 4. ANDROID USERS - You have to comment out the iOS details line at line 205 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 213 before publishing. + * + * 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 + * 19.11.2015 + * v1.0 - Initial Release + * v1.1 - Added function buttons to set Hive Heating to Off, Manual or Schedule. + * v1.2 - Removed requirement to type in Receiver Nickname from Hive. + * v1.3 - Altered temperature colours to match Hive branding (I was bored). + * v1.4 - Enable options for sliders or buttons for temperature control. + * + * 20.11.2015 + * v1.5 - Clean up UI and make user friendly status message appear on top panel + * v1.6 - Added icons to control buttons. Increased poll delay time to ensure UI is updated on control press. + * + * 21.11.2015 + * v1.7 - Fixed issue where 'supportsHeatCoolModes' attribute does not exist. + * v1.8 - Changed behaviour when temperature is set when Hive Heating is in off mode to match Hive app behaviour. + * v1.9 - Added new Android tile layout option. Requires uncommenting/commenting out lines. Updated behaviour when Hive Heating is in off mode. Altered temperatue precision. + * v1.9.1 - Tweaks to Android layout. + * v1.9.2 - Tweaks to how set heating point temperature is reported in frost mode. + * v1.9.3 - Improvements to handling temperature setting when in 'off' mode. + * v1.10 - Added Boost button!! Reduced number of activity messages. + * v1.10.1 - Tweaks to temperature formatting. + * v1.10.2 - Added icons to thermostat mode states + * v1.10.3 - Tweaks to display on Things screen. + * + * 23.11.2015 + * v1.10.4 - Set thermostatFanMode to 'off' to improve SmartTiles display + * + * 01.12.2015 + * v1.10.5 - Handle event of thermostat being set to 'cool'. Changed API client name. + * + * 04.01.2016 + * v1.11 - Support for multi zone systems with new thermostat name attribute. Changes to multi attribute tile to attempt to unify android UI with iOS. + */ +preferences { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") + input("password", "password", title: "Password", description: "Your Hive password") + input("thermostat", "thermostat", title: "Thermostat Name", description: "Multi Zone Hive Systems Only. The name of the thermostat controlling the zone (i.e, Ground Floor)") +} + +metadata { + definition (name: "Hive Multi Zone Active Heating", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Temperature Measurement" + capability "Thermostat" + capability "Thermostat Heating Setpoint" + capability "Thermostat Mode" + capability "Thermostat Operating State" + + command "heatingSetpointUp" + command "heatingSetpointDown" + command "setThermostatMode" + command "setHeatingSetpoint" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"lighting") { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ + attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ + // Celsius Color Range + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ]} + tileAttribute ("hiveHeating", key: "SECONDARY_CONTROL") { + attributeState "hiveHeating", label:'${currentValue}' + } + } + + valueTile("thermostat_small", "device.temperature", width: 4, height: 4) { + state "default", label:'${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ] + } + + standardTile("thermostat_main", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state "idle", label:'${currentValue}', icon: "st.Weather.weather2" + state "heating", label:'${currentValue}', icon: "st.Weather.weather2", backgroundColor:"#EC6E05" + } + + controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { + state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" + } + + standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" + } + + standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" + } + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { + state "default", label:'${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ] + } + + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" + state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#EC6E05" + } + + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("auto", label: "SCHEDULED", icon:"st.Office.office7") + state("off", label: "OFF", icon:"st.thermostat.heating-cooling-off") + state("heat", label: "MANUAL", icon:"st.Weather.weather2") + state("emergency heat", label: "BOOST", icon:"st.Health & Wellness.health7") + } + + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'${currentValue}', action:"emergencyHeat") + } + + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" + } + + standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" + } + + standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"off", icon:"st.thermostat.heating-cooling-off" + } + + main(["thermostat_main"]) + + // ============================================================ + // iOS TILES + // To expose iOS optimised tiles, comment out the details line in Android Tiles section and uncomment details line below. + + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "refresh"]) + + // ============================================================ + + // ============================================================ + // ANDROID TILES + // To expose Android optimised tiles, comment out the details line in iOS Tiles section and uncomment details line below. + + //details(["thermostat_small", "thermostatOperatingState", "thermostatMode", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "refresh"]) + + // ============================================================ + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'temperature' attribute + // TODO: handle 'heatingSetpoint' attribute + // TODO: handle 'thermostatSetpoint' attribute + // TODO: handle 'thermostatMode' attribute + // TODO: handle 'thermostatOperatingState' attribute +} + +// handle commands +def setHeatingSetpoint(temp) { + log.debug "Executing 'setHeatingSetpoint with temp $temp'" + def latestThermostatMode = device.latestState('thermostatMode') + + if (temp < 5) { + temp = 5 + } + if (temp > 32) { + temp = 32 + } + + + //if thermostat is off, set to manual + if (latestThermostatMode.stringValue == 'off') { + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + api('thermostat_mode', args) { + } + } + + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} + def args = [ + nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] + ] + api('temperature', args) { + runIn(4, poll) + } +} + +def heatingSetpointUp(){ + log.debug "Executing 'heatingSetpointUp'" + int newSetpoint = device.currentValue("heatingSetpoint") + 1 + log.debug "Setting heat set point up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def heatingSetpointDown(){ + log.debug "Executing 'heatingSetpointDown'" + int newSetpoint = device.currentValue("heatingSetpoint") - 1 + log.debug "Setting heat set point down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def off() { + setThermostatMode('off') +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + log.debug "Executing 'boost'" + + def latestThermostatMode = device.latestState('thermostatMode') + + //Don't do if already in BOOST mode. + if (latestThermostatMode.stringValue != 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + log.debug "Already in boost mode." + } + +} + +def auto() { + setThermostatMode('auto') +} + +def setThermostatMode(mode) { + mode = mode == 'cool' ? 'heat' : mode + log.debug "Executing 'setThermostatMode with mode $mode'" + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: false]]]] + ] + if (mode == 'off') { + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] + ] + } else if (mode == 'heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] + ] + } else if (mode == 'emergency heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":22}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: 60], targetHeatTemperature: [targetValue: "21"]]]] + ] + } + + api('thermostat_mode', args) { + mode = mode == 'range' ? 'auto' : mode + runIn(4, poll) + } +} + +def poll() { + log.debug "Executing 'poll'" + api('status', []) { + data.nodes = it.data.nodes + + //Construct status message + def statusMsg = "Currently" + + //Boost button label + def boostLabel = "Start\nBoost" + + // get temperature status + def temperature = data.nodes.attributes.temperature.reportedValue[0] + def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + temperature = String.format("%2.1f",temperature) + heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 1) + + // convert temperature reading of 1 degree to 7 as Hive app does + if (heatingSetpoint == "1.0") { + heatingSetpoint = "7.0" + } + + sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") + sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") + sendEvent(name: 'thermostatSetpoint', value: heatingSetpoint, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) + + // determine hive operating mode + def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] + def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] + + log.debug "activeHeatCoolMode: $activeHeatCoolMode" + log.debug "activeScheduleLock: $activeScheduleLock" + + def mode = 'auto' + + if (activeHeatCoolMode == "OFF") { + mode = 'off' + statusMsg = statusMsg + " set to OFF" + } + else if (activeHeatCoolMode == "BOOST") { + mode = 'emergency heat' + statusMsg = statusMsg + " set to BOOST" + def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] + boostLabel = "Boosting for \n" + boostTime + " mins" + sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") + } + else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { + mode = 'heat' + statusMsg = statusMsg + " set to MANUAL" + } + else { + statusMsg = statusMsg + " set to SCHEDULE" + } + sendEvent(name: 'thermostatMode', value: mode) + + // determine if Hive heating relay is on + def stateHeatingRelay = data.nodes.attributes.stateHeatingRelay.reportedValue[0] + + log.debug "stateHeatingRelay: $stateHeatingRelay" + + if (stateHeatingRelay == "ON") { + sendEvent(name: 'thermostatOperatingState', value: "heating") + statusMsg = statusMsg + " and is HEATING" + } + else { + sendEvent(name: 'thermostatOperatingState', value: "idle") + statusMsg = statusMsg + " and is IDLE" + } + + sendEvent("name":"hiveHeating", "value": statusMsg, displayed: false) + sendEvent("name":"boostLabel", "value": boostLabel, displayed: false) + } +} + +def refresh() { + log.debug "Executing 'refresh'" + poll() +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + if(!isLoggedIn()) { + log.debug "Need to login" + login(method, args, success) + return + } + log.debug "Using node id: $state.nodeid" + def methods = [ + 'status': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'get'], + 'temperature': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'], + 'thermostat_mode': [uri: "https://api.prod.bgchprod.info:443/omnia/nodes/${state.nodeid}", type: 'put'] + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + doRequest(request.uri, args, request.type, success) +} + +// Need to be logged in before this is called. So don't call this. Call api. +def doRequest(uri, args, type, success) { + log.debug "Calling doRequest()" + log.debug "Calling $type : $uri : $args" + + def params = [ + uri: uri, + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Hive Web Dashboard', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ], + body: args + ] + + log.debug params + + def postRequest = { response -> + success.call(response) + } + + + if (type == 'post') { + httpPostJson(params, postRequest) + } else if (type == 'put') { + httpPutJson(params, postRequest) + } else if (type == 'get') { + httpGet(params, postRequest) + } + +} + +def getThermostatId() { + log.debug "Calling getThermostatId()" + //get parent thermostat node id + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Hive Web Dashboard', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ] + ] + + httpGet(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + + response.data.nodes.each { + log.debug "node name $it.name" + if (it.name == settings.thermostat) + { + state.parentNodeId = it.id + } + } + + if (state.parentNodeId == '') + { + log.error "No thermostat found with name $settings.thermostat. Please check settings. Attempting to use default thermostat." + } + + log.debug "parentNodeId: $state.parentNodeId" + } +} + +def getNodeId () { + log.debug "Calling getNodeId()" + //get node id + log.debug "Using session id, $data.auth.sessions[0].id" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Hive Web Dashboard', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ] + ] + + state.nodeid = '' + httpGet(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + + response.data.nodes.each { + if (((state.parentNodeId == '') || (it.parentNodeId == state.parentNodeId)) && (it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == false)) + { + state.nodeid = it.id + } + } + + if (state.nodeid == '') + { + log.error "No node found to create device type with. Please check settings." + } + + log.debug "nodeid: $state.nodeid" + } +} + +def login(method = null, args = [], success = {}) { + log.debug "Calling login()" + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/auth/sessions', + contentType: 'application/json', + headers: [ + 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Hive Web Dashboard', + ], + body: [ + sessions: [ [username: settings.username, + password: settings.password, + caller: 'smartthings']] + ] + ] + + state.cookie = '' + + httpPostJson(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + data.auth = response.data + + // set the expiration to 5 minutes + data.auth.expires_at = new Date().getTime() + 10000; + + state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) + log.debug "Adding cookie to collection: $cookie" + log.debug "auth: $data.auth" + log.debug "cookie: $state.cookie" + log.debug "sessionid: $data.auth.sessions[0].id" + + state.parentNodeId = '' + + //Get thermostat id for multi zone systems. + if (settings.thermostat != null && settings.thermostat != '') + { + getThermostatId() + } + + getNodeId() + + api(method, args, success) + + } +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $data.auth" + if(!data.auth) { + log.debug "No data.auth" + return false + } + + def now = new Date().getTime(); + return data.auth.expires_at > now +} \ No newline at end of file From fad616d83821707415c3804202bf68471f62beef Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 4 Jan 2016 16:14:03 +0000 Subject: [PATCH 077/685] Minor fixes --- .../hive-active-heating.groovy | 65 +++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 2804fa63800..34d8a0c35c0 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -75,10 +75,14 @@ * * 01.12.2015 * v1.10.5 - Handle event of thermostat being set to 'cool'. Changed API client name. + * + * 04.01.2016 + * v1.11 - Support for multi zone systems with new thermostat name attribute. Changes to multi attribute tile to attempt to unify android UI with iOS. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") input("password", "password", title: "Password", description: "Your Hive password") + input("thermostat", "text", title: "Thermostat Name", description: "Multi Zone Hive Systems Only. The name of the thermostat controlling the zone (i.e, Ground Floor)") } metadata { @@ -104,7 +108,7 @@ metadata { tiles(scale: 2) { - multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"thermostat") { + multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"lighting") { tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ // Celsius Color Range @@ -316,7 +320,7 @@ def setThermostatMode(mode) { } else if (mode == 'emergency heat') { //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":22}}}]} args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: 60], targetHeatTemperature: [targetValue: "22"]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: 60], targetHeatTemperature: [targetValue: "21"]]]] ] } @@ -463,9 +467,46 @@ def doRequest(uri, args, type, success) { } +def getThermostatId() { + log.debug "Calling getThermostatId()" + //get parent thermostat node id + def params = [ + uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', + contentType: 'application/json', + headers: [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Hive Web Dashboard', + 'X-Omnia-Access-Token': "${data.auth.sessions[0].id}" + ] + ] + + httpGet(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + + response.data.nodes.each { + log.debug "node name $it.name" + if (it.name == settings.thermostat) + { + state.parentNodeId = it.id + } + } + + if (state.parentNodeId == '') + { + log.error "No thermostat found with name $settings.thermostat. Please check settings. Attempting to use default thermostat." + } + + log.debug "parentNodeId: $state.parentNodeId" + } +} + def getNodeId () { log.debug "Calling getNodeId()" - //get thermostat node id + //get node id log.debug "Using session id, $data.auth.sessions[0].id" def params = [ uri: 'https://api.prod.bgchprod.info:443/omnia/nodes', @@ -486,13 +527,17 @@ def getNodeId () { log.debug response.headers response.data.nodes.each { - log.debug "node name $it.name" - if ((it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == false)) + if (((state.parentNodeId == '') || (it.parentNodeId == state.parentNodeId)) && (it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == false)) { state.nodeid = it.id } } + if (state.nodeid == '') + { + log.error "No node found to create device type with. Please check settings." + } + log.debug "nodeid: $state.nodeid" } } @@ -523,7 +568,7 @@ def login(method = null, args = [], success = {}) { data.auth = response.data // set the expiration to 5 minutes - data.auth.expires_at = new Date().getTime() + 300000; + data.auth.expires_at = new Date().getTime() + 10000; state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) log.debug "Adding cookie to collection: $cookie" @@ -531,6 +576,14 @@ def login(method = null, args = [], success = {}) { log.debug "cookie: $state.cookie" log.debug "sessionid: $data.auth.sessions[0].id" + state.parentNodeId = '' + + //Get thermostat id for multi zone systems. + if (settings.thermostat != null && settings.thermostat != '') + { + getThermostatId() + } + getNodeId() api(method, args, success) From cb2ccc609df1ef8f1772661ae227d10b36dac13f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 4 Jan 2016 16:17:27 +0000 Subject: [PATCH 078/685] Minor fixes --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 34d8a0c35c0..f6091033172 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -556,7 +556,7 @@ def login(method = null, args = [], success = {}) { body: [ sessions: [ [username: settings.username, password: settings.password, - caller: 'smartthings']] + caller: 'Hive Web Dashboard']] ] ] From 33fa3771c914b9e5f9b6893382b44e8331a02b32 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 4 Jan 2016 17:56:34 +0000 Subject: [PATCH 079/685] Fixes --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index f6091033172..2606182f867 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -568,7 +568,7 @@ def login(method = null, args = [], success = {}) { data.auth = response.data // set the expiration to 5 minutes - data.auth.expires_at = new Date().getTime() + 10000; + data.auth.expires_at = new Date().getTime() + 300000; state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) log.debug "Adding cookie to collection: $cookie" From 5e3e6aa4e0d8dcfc74257e4f62d5add2663e07aa Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 4 Jan 2016 21:12:51 +0000 Subject: [PATCH 080/685] Code cleanup --- .../hive-active-heating.groovy | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 2606182f867..c9d2f2dca11 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -32,9 +32,7 @@ * Click on the new device to see the details. * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. - * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 205 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 213 before publishing. + * For Multi Zone Hive Systems, fill in the thermostat name of the zone. * * 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: @@ -201,22 +199,7 @@ metadata { } main(["thermostat_main"]) - - // ============================================================ - // iOS TILES - // To expose iOS optimised tiles, comment out the details line in Android Tiles section and uncomment details line below. - - details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "refresh"]) - - // ============================================================ - - // ============================================================ - // ANDROID TILES - // To expose Android optimised tiles, comment out the details line in iOS Tiles section and uncomment details line below. - - //details(["thermostat_small", "thermostatOperatingState", "thermostatMode", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "refresh"]) - - // ============================================================ + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "refresh"]) } } From 1f015dac3345fd4485aff606ea08c3be110e9996 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 13:54:37 +0000 Subject: [PATCH 081/685] Remove the requirement for Pollster smart app --- .../hive-active-heating.groovy | 21 +++++++++++++++ .../hive-active-hot-water.groovy | 26 ++++++++++++++++--- .../ovo-energy-meter.groovy | 22 +++++++++++++++- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index c9d2f2dca11..a3ec5789077 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -76,6 +76,9 @@ * * 04.01.2016 * v1.11 - Support for multi zone systems with new thermostat name attribute. Changes to multi attribute tile to attempt to unify android UI with iOS. + * + * 05.01.2016 + * v1.11.1 - Removed the need for Pollster. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -213,6 +216,24 @@ def parse(String description) { // TODO: handle 'thermostatOperatingState' attribute } +def installed() { + log.debug "Executing 'installed'" + // execute handlerMethod every 10 minutes. + schedule("0 0/10 * * * ?", poll) +} + +def updated() { + log.debug "Executing 'updated'" + // execute handlerMethod every 10 minutes. + unschedule(poll) + schedule("0 0/10 * * * ?", poll) +} + +def uninstalled() { + log.debug "Executing 'unsinstalled'" + unschedule(poll) +} + // handle commands def setHeatingSetpoint(temp) { log.debug "Executing 'setHeatingSetpoint with temp $temp'" diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 73550f2dae2..74f35179f9d 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -27,8 +27,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 133 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 141 before publishing. + * 4. ANDROID USERS - You have to comment out the iOS details line at line 130 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 138 before publishing. * * 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: @@ -53,6 +53,9 @@ * * 01.12.2015 * v1.1.3 - Handle 'cool' mode. Change API client id. + * + * 05.01.2016 + * v1.1.4 - Removed the need for Pollster. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -153,6 +156,24 @@ def parse(String description) { } +def installed() { + log.debug "Executing 'installed'" + // execute handlerMethod every 10 minutes. + schedule("0 0/10 * * * ?", poll) +} + +def updated() { + log.debug "Executing 'updated'" + // execute handlerMethod every 10 minutes. + unschedule(poll) + schedule("0 0/10 * * * ?", poll) +} + +def uninstalled() { + log.debug "Executing 'unsinstalled'" + unschedule(poll) +} + // handle commands def setHeatingSetpoint(temp) { //Not implemented @@ -436,4 +457,3 @@ def isLoggedIn() { def now = new Date().getTime(); return data.auth.expires_at > now } - diff --git a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy index c4196af7d42..477b6896b35 100644 --- a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy +++ b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy @@ -36,6 +36,9 @@ * v1.0.1 - Tile layout update. Ability to change icon!!! * v1.0.2 - SmartTiles compatability as Power Meter * v1.1 - Calculates cumulative daily costs without OVO API + * + * 05.01.2016 + * v1.1.1 - Remove requirement for Pollster app. */ preferences { input("username", "text", title: "Username", description: "Your OVO username (usually an email address)") @@ -93,6 +96,24 @@ def parse(String description) { } +def installed() { + log.debug "Executing 'installed'" + // execute handlerMethod every 10 minutes. + schedule("0 0/1 * * * ?", poll) +} + +def updated() { + log.debug "Executing 'updated'" + // execute handlerMethod every 10 minutes. + unschedule(poll) + schedule("0 0/1 * * * ?", poll) +} + +def uninstalled() { + log.debug "Executing 'unsinstalled'" + unschedule(poll) +} + // handle commands def poll() { log.debug "Executing 'poll'" @@ -315,4 +336,3 @@ def isLoggedIn() { def now = new Date().getTime(); return data.auth.expires_at > now } - From b10bcabac32754d46990c89a068cdcb6ac437a10 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 14:04:37 +0000 Subject: [PATCH 082/685] Minor fixes. --- .../hive-active-hot-water.groovy | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 74f35179f9d..df57b212275 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -79,7 +79,7 @@ metadata { tiles(scale: 2) { - multiAttributeTile(name: "hotWaterRelay", width: 6, height: 4, type:"generic") { + multiAttributeTile(name: "hotWaterRelay", width: 6, height: 4, type:"lighting") { tileAttribute("device.thermostatOperatingState", key:"PRIMARY_CONTROL"){ attributeState "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05" attributeState "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff" @@ -127,23 +127,8 @@ metadata { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - main(["hotWaterRelay_main"]) - - // ============================================================ - // iOS TILES - // To expose iOS optimised tiles, comment out the details line in Android Tiles section and uncomment details line below. - + main(["hotWaterRelay_main"]) details(["hotWaterRelay", "mode_auto", "mode_manual", "mode_off", "boost", "refresh"]) - - // ============================================================ - - // ============================================================ - // ANDROID TILES - // To expose Android optimised tiles, comment out the details line in iOS Tiles section and uncomment details line below. - - //details(["hotWaterRelay_small", "thermostatMode", "mode_auto", "mode_manual", "mode_off", "boost", "refresh"]) - - // ============================================================ } } From 7af43cc304301856b28834c433e69d848531624a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 14:08:48 +0000 Subject: [PATCH 083/685] Minor fixes --- .../hive-active-hot-water.src/hive-active-hot-water.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index df57b212275..fdd7ccfbf0a 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -55,7 +55,7 @@ * v1.1.3 - Handle 'cool' mode. Change API client id. * * 05.01.2016 - * v1.1.4 - Removed the need for Pollster. + * v1.1.4 - Removed the need for Pollster. Improved tile layout for Android UI. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") From bc4e7c8ac302888139169ed5d56a4b4748854814 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 14:14:44 +0000 Subject: [PATCH 084/685] Instruction update --- .../hive-active-hot-water.src/hive-active-hot-water.groovy | 2 -- 1 file changed, 2 deletions(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index fdd7ccfbf0a..fd20c47d57e 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -27,8 +27,6 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * - * 4. ANDROID USERS - You have to comment out the iOS details line at line 130 by adding "//" - * and uncomment the Android details line by removing the preceding "//" at line 138 before publishing. * * 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: From 6fc7d1c6e230d1cf1e4bbd2356fd52cabcf08c2d Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 18:15:52 +0000 Subject: [PATCH 085/685] v1.11.2 - Added extra checks to ensure discovered node is active --- .../alyc100/hive-active-heating.src/hive-active-heating.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index a3ec5789077..e7bd28c6b3b 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -79,6 +79,7 @@ * * 05.01.2016 * v1.11.1 - Removed the need for Pollster. + * v1.11.2 - Added extra checks to ensure discovered node is active */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -531,7 +532,7 @@ def getNodeId () { log.debug response.headers response.data.nodes.each { - if (((state.parentNodeId == '') || (it.parentNodeId == state.parentNodeId)) && (it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == false)) + if (((state.parentNodeId == '') || (it.parentNodeId == state.parentNodeId)) && (it.attributes.supportsHotWater != null) && (it.attributes.supportsHotWater.reportedValue == false) && (it.attributes.temperature != null)) { state.nodeid = it.id } From 9a91da366ba822f011973dd3443c48ee13ba6492 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 19:26:42 +0000 Subject: [PATCH 086/685] Device schedular improvements. --- .../hive-active-heating.src/hive-active-heating.groovy | 5 +++-- .../hive-active-hot-water.groovy | 10 +++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index e7bd28c6b3b..3a8154c5b06 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -80,6 +80,7 @@ * 05.01.2016 * v1.11.1 - Removed the need for Pollster. * v1.11.2 - Added extra checks to ensure discovered node is active + * v1.11.3 - Improved scheduler reliability without Pollster. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -262,7 +263,7 @@ def setHeatingSetpoint(temp) { nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] ] api('temperature', args) { - runIn(4, poll) + runIn(4, refresh) } } @@ -331,7 +332,7 @@ def setThermostatMode(mode) { api('thermostat_mode', args) { mode = mode == 'range' ? 'auto' : mode - runIn(4, poll) + runIn(4, refresh) } } diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index fd20c47d57e..0c8346345bd 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -27,6 +27,8 @@ * Click the edit button next to Preferences * Fill in your your Hive user name, Hive password. * + * 4. ANDROID USERS - You have to comment out the iOS details line at line 130 by adding "//" + * and uncomment the Android details line by removing the preceding "//" at line 138 before publishing. * * 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: @@ -53,7 +55,8 @@ * v1.1.3 - Handle 'cool' mode. Change API client id. * * 05.01.2016 - * v1.1.4 - Removed the need for Pollster. Improved tile layout for Android UI. + * v1.1.4 - Removed the need for Pollster. Unify Android tile UI to match iOS. + * v1.1.5 - Improved scheduler reliability without Pollster. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -226,7 +229,7 @@ def setThermostatMode(mode) { api('thermostat_mode', args) { mode = mode == 'range' ? 'auto' : mode - runIn(3, poll) + runIn(3, refresh) } } @@ -296,7 +299,7 @@ log.debug "Executing 'poll'" def refresh() { log.debug "Executing 'refresh'" - // TODO: handle 'refresh' command + poll() } def api(method, args = [], success = {}) { @@ -440,3 +443,4 @@ def isLoggedIn() { def now = new Date().getTime(); return data.auth.expires_at > now } + From f240fdc21869588be9a46ffe6c87eddedd7d560c Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 21:38:29 +0000 Subject: [PATCH 087/685] Added boost length slider control. --- .../hive-active-heating.groovy | 40 +++++++++++++++++-- .../hive-active-hot-water.groovy | 37 +++++++++++++++-- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 3a8154c5b06..fd6c6bfab58 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -81,6 +81,7 @@ * v1.11.1 - Removed the need for Pollster. * v1.11.2 - Added extra checks to ensure discovered node is active * v1.11.3 - Improved scheduler reliability without Pollster. + * v1.12 - Added option to adjust boost length. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -103,6 +104,7 @@ metadata { command "heatingSetpointDown" command "setThermostatMode" command "setHeatingSetpoint" + command "setBoostLength" } simulator { @@ -150,6 +152,10 @@ metadata { state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" } + controlTile("boostSliderControl", "device.boostLength", "slider", height: 2, width: 4, inactiveLabel: false, range:"(10..240)") { + state "setBoostLength", label:'Set boost length to', action:"setBoostLength" + } + standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" } @@ -204,7 +210,7 @@ metadata { } main(["thermostat_main"]) - details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "refresh"]) + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "boostSliderControl", "refresh"]) } } @@ -221,6 +227,7 @@ def parse(String description) { def installed() { log.debug "Executing 'installed'" // execute handlerMethod every 10 minutes. + state.boostLength = 60 schedule("0 0/10 * * * ?", poll) } @@ -267,6 +274,21 @@ def setHeatingSetpoint(temp) { } } +def setBoostLength(minutes) { + log.debug "Executing 'setBoostLength with length $minutes minutes'" + if (minutes < 10) { + minutes = 10 + } + if (minutes > 240) { + minutes = 240 + } + state.boostLength = minutes + sendEvent("name":"boostLength", "value": state.boostLength, displayed: true) + + refresh() + +} + def heatingSetpointUp(){ log.debug "Executing 'heatingSetpointUp'" int newSetpoint = device.currentValue("heatingSetpoint") + 1 @@ -323,10 +345,15 @@ def setThermostatMode(mode) { args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] ] - } else if (mode == 'emergency heat') { + } else if (mode == 'emergency heat') { + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":22}}}]} args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: 60], targetHeatTemperature: [targetValue: "21"]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: state.boostLength], targetHeatTemperature: [targetValue: "22"]]]] ] } @@ -345,7 +372,12 @@ def poll() { def statusMsg = "Currently" //Boost button label - def boostLabel = "Start\nBoost" + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + def boostLabel = "Start\n$state.boostLength Min Boost" // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 0c8346345bd..1cd2e80d015 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -57,6 +57,7 @@ * 05.01.2016 * v1.1.4 - Removed the need for Pollster. Unify Android tile UI to match iOS. * v1.1.5 - Improved scheduler reliability without Pollster. + * v1.2 - Added option to adjust boost length. */ preferences { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)") @@ -72,6 +73,7 @@ metadata { capability "Thermostat Mode" command "setThermostatMode" + command "setBoostLength" } simulator { @@ -116,6 +118,10 @@ metadata { state("default", label:'${currentValue}', action:"emergencyHeat") } + controlTile("boostSliderControl", "device.boostLength", "slider", height: 2, width: 4, inactiveLabel: false, range:"(10..240)") { + state "setBoostLength", label:'Set boost length to', action:"setBoostLength" + } + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" } @@ -129,7 +135,7 @@ metadata { } main(["hotWaterRelay_main"]) - details(["hotWaterRelay", "mode_auto", "mode_manual", "mode_off", "boost", "refresh"]) + details(["hotWaterRelay", "mode_auto", "mode_manual", "mode_off", "boost", "boostSliderControl", "refresh"]) } } @@ -144,6 +150,7 @@ def parse(String description) { def installed() { log.debug "Executing 'installed'" + state.boostLength = 60 // execute handlerMethod every 10 minutes. schedule("0 0/10 * * * ?", poll) } @@ -165,6 +172,20 @@ def setHeatingSetpoint(temp) { //Not implemented } +def setBoostLength(minutes) { + log.debug "Executing 'setBoostLength with length $minutes minutes'" + if (minutes < 10) { + minutes = 10 + } + if (minutes > 240) { + minutes = 240 + } + state.boostLength = minutes + sendEvent("name":"boostLength", "value": state.boostLength, displayed: true) + + refresh() +} + def heatingSetpointUp(){ //Not implemented } @@ -221,9 +242,14 @@ def setThermostatMode(mode) { nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true], targetHeatTemperature: [targetValue: "99"]]]] ] } else if (mode == 'emergency heat') { + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":99}}}]} args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: 60], targetHeatTemperature: [targetValue: "99"]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: state.boostLength], targetHeatTemperature: [targetValue: "99"]]]] ] } @@ -242,7 +268,12 @@ log.debug "Executing 'poll'" def statusMsg = "Currently" //Boost button label - def boostLabel = "Start\nBoost" + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + def boostLabel = "Start\n$state.boostLength Min Boost" // determine hive hot water operating mode def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] From a49f29813a7264d67a9c74faf70623a8eb5a1817 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 21:40:15 +0000 Subject: [PATCH 088/685] Minor label changes --- .../thermostat-mode-automation.groovy | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index b7ee62aa279..140122a940d 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -36,9 +36,10 @@ * v1.2 - Extra Boost handling capabilities. * Fixed bug where no reset was specified and app doesn't reset variable 'state.thermostatAltered'. * v1.2.1 - Bug fixes. - * v1.3 - Option added to set mode of thermostat after boost action if reset mode is set to 'Boost for 60 minutes'. + * v1.3 - Option added to set mode of thermostat after boost action if reset mode is set to 'Boost'. * v1.3.1 - Bug fixes. * v1.3.2 - Stop possible infinite loop when handlers create events themselves. + * v1.3.3 - Label change for Boost */ definition( @@ -88,7 +89,7 @@ def configurePage() { section { input ("alteredThermostatMode", "enum", multiple: false, title: "Set thermostats to this mode", - options: ["Set To Schedule", "Boost for 60 minutes", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Turn Off') + options: ["Set To Schedule", "Boost", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Turn Off') } section { @@ -99,10 +100,10 @@ def configurePage() { if (resetThermostats == "true") { section { input ("resumedThermostatMode", "enum", multiple: false, title: "Reset thermostats back to this mode", submitOnChange: true, - options: ["Set To Schedule", "Boost for 60 minutes", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule') + options: ["Set To Schedule", "Boost", "Turn Off", "Set to Manual"], required: true, defaultValue: 'Set To Schedule') } - if (resumedThermostatMode == "Boost for 60 minutes") { + 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') @@ -224,7 +225,7 @@ def takeActionForSwitch(switchState) { 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 for 60 minutes") { + if (resumedThermostatMode == "Boost") { state.boostingReset = true } @@ -281,7 +282,7 @@ def takeActionForMode(mode) { 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 for 60 minutes") { + if (resumedThermostatMode == "Boost") { state.boostingReset = true } @@ -306,7 +307,7 @@ def thermostateventHandler(evt) { 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 for 60 minutes") { + if (alteredThermostatMode == "Boost") { state.internalSwitchEvent = true if (evt.value != "emergency heat") { //Switching the switch to off should trigger an event that resets app state @@ -352,7 +353,7 @@ private changeAllThermostatsModes(thermostats, newThermostatMode, reason) { else if (newThermostatMode == "Turn Off") { thermostat.off() } - else if (newThermostatMode == "Boost for 60 minutes") { + else if (newThermostatMode == "Boost") { thermostat.emergencyHeat() } else { From 48f21790831093826831c3334f091f5b08bb713f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 22:10:59 +0000 Subject: [PATCH 089/685] Allow changing of boost length during boost. --- .../hive-active-heating.groovy | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index fd6c6bfab58..0ab3e7c3bb3 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -285,7 +285,17 @@ def setBoostLength(minutes) { state.boostLength = minutes sendEvent("name":"boostLength", "value": state.boostLength, displayed: true) - refresh() + def latestThermostatMode = device.latestState('thermostatMode') + + //If already in BOOST mode, send updated boost length to Hive. + if (latestThermostatMode.stringValue == 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + refresh() + } + + } From 892d893f197299d88f2265e29bd45e94fe176d27 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 5 Jan 2016 22:13:27 +0000 Subject: [PATCH 090/685] Add ability to alter boost length during boost. --- .../hive-active-hot-water.groovy | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index 1cd2e80d015..c426994a9a7 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -183,7 +183,15 @@ def setBoostLength(minutes) { state.boostLength = minutes sendEvent("name":"boostLength", "value": state.boostLength, displayed: true) - refresh() + def latestThermostatMode = device.latestState('thermostatMode') + + //If already in BOOST mode, send updated boost length to Hive. + if (latestThermostatMode.stringValue == 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + refresh() + } } def heatingSetpointUp(){ From 0316156f7606e7b93524c3a52395c64de67ccfa9 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 9 Jan 2016 21:30:09 +0000 Subject: [PATCH 091/685] Minor fixes --- .../hive-active-heating.src/hive-active-heating.groovy | 4 ++-- .../hive-active-hot-water.src/hive-active-hot-water.groovy | 4 ++-- .../alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 0ab3e7c3bb3..1166f6db6a7 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -234,13 +234,13 @@ def installed() { def updated() { log.debug "Executing 'updated'" // execute handlerMethod every 10 minutes. - unschedule(poll) + unschedule('poll') schedule("0 0/10 * * * ?", poll) } def uninstalled() { log.debug "Executing 'unsinstalled'" - unschedule(poll) + unschedule('poll') } // handle commands diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index c426994a9a7..a04b6ff8a94 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -158,13 +158,13 @@ def installed() { def updated() { log.debug "Executing 'updated'" // execute handlerMethod every 10 minutes. - unschedule(poll) + unschedule('poll') schedule("0 0/10 * * * ?", poll) } def uninstalled() { log.debug "Executing 'unsinstalled'" - unschedule(poll) + unschedule('poll') } // handle commands diff --git a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy index 477b6896b35..887374df29a 100644 --- a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy +++ b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy @@ -105,13 +105,13 @@ def installed() { def updated() { log.debug "Executing 'updated'" // execute handlerMethod every 10 minutes. - unschedule(poll) + unschedule('poll') schedule("0 0/1 * * * ?", poll) } def uninstalled() { log.debug "Executing 'unsinstalled'" - unschedule(poll) + unschedule('poll') } // handle commands @@ -336,3 +336,4 @@ def isLoggedIn() { def now = new Date().getTime(); return data.auth.expires_at > now } + From 4e8153673f76e1ce51f8dbaa08f84e863a98ec1f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 9 Jan 2016 21:55:23 +0000 Subject: [PATCH 092/685] Initial Release --- .../mihome-etrv-connect.groovy | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 smartapps/alyc100/mihome-etrv-connect.src/mihome-etrv-connect.groovy diff --git a/smartapps/alyc100/mihome-etrv-connect.src/mihome-etrv-connect.groovy b/smartapps/alyc100/mihome-etrv-connect.src/mihome-etrv-connect.groovy new file mode 100644 index 00000000000..481479c9857 --- /dev/null +++ b/smartapps/alyc100/mihome-etrv-connect.src/mihome-etrv-connect.groovy @@ -0,0 +1,214 @@ +/** + * MiHome eTRV (Connect) + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 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 + * 09.01.2016 + * v1.0 - Initial Release + */ +definition( + name: "MiHome eTRV (Connect)", + namespace: "alyc100", + author: "Alex Lee Yuk Cheung", + description: "Connect your MiHome eTRV to SmartThings.", + iconUrl: "https://mihome4u.co.uk/assets/homepage/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", + iconX2Url: "https://mihome4u.co.uk/assets/homepage/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", + singleInstance: true +) + +preferences { + page(name:"firstPage", title:"MiHome Device Setup", content:"firstPage", install: true) +} + +def apiURL(path = '/') { return "https://mihome4u.co.uk/api/v1${path}" } + +def firstPage() { + log.debug "firstPage" + if (username == null || username == '' || password == null || password == '') { + return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + section { + input("username", "text", title: "Username", description: "Your MiHome username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your MiHome password", required: true, submitOnChange: true) + } + } + } + else + { + log.debug "next phase" + getMiHomeAccessToken() + updateDevices() + return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + section { + input("username", "text", title: "Username", description: "Your MiHome username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your MiHome password", required: true, submitOnChange: true) + } + } + } +} + +// App lifecycle hooks + +def installed() { + initialize() + // Check for new devices and remove old ones every 3 hours + runEvery3Hours('updateDevices') + // execute handlerMethod every 10 minutes. + schedule("0 0/10 * * * ?", refreshDevices) +} + +// called after settings are changed +def updated() { + initialize() + unschedule('refreshDevices') + schedule("0 0/10 * * * ?", refreshDevices) +} + +def uninstalled() { + log.info("Uninstalling, removing child devices...") + unschedule('updateDevices') + unschedule('refreshDevices') + removeChildDevices(getChildDevices()) +} + +private removeChildDevices(devices) { + devices.each { + deleteChildDevice(it.deviceNetworkId) // 'it' is default + } +} + +// called after Done is hit after selecting a Location +def initialize() { + log.debug "initialize" + updateDevices() +} + + +def updateDevices() { + if (!state.devices) { + state.devices = [:] + } + def devices = devicesList() + def selectors = [] + devices.each { device -> + def childDevice = getChildDevice("${device.id}") + selectors.add("${device.id}") + if (!childDevice) { + log.info("Adding device ${device.id}: ${device.device_type}: ${device.label}: ${device.target_temperature}: ${device.last_temperature}: ${device.voltage}") + if (device.device_type == 'etrv') + { + def data = [ + name: device.label + " eTRV", + label: device.label + " eTRV", + temperature: device.last_temperature, + heatingSetpoint: device.target_temperature, + switch: device.target_temperature == 12 ? "off" : "on" + ] + childDevice = addChildDevice(app.namespace, "MiHome eTRV", "$device.id", null, data) + } + } + } + getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { + log.info("Deleting ${it.deviceNetworkId}") + deleteChildDevice(it.deviceNetworkId) + } + runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block +} + +def refreshDevices() { + log.info("Refreshing all devices...") + getChildDevices().each { device -> + device.refresh() + } +} + +def devicesList() { + logErrors([]) { + def resp = apiGET("/subdevices/list") + if (resp.status == 200) { + return resp.data.data + } else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + return [] + } + } +} + +def getMiHomeAccessToken() { + def resp = apiGET("/users/profile") + if (resp.status == 200) { + state.miHomeAccessToken = resp.data.data.api_key + log.debug "miHomeAccessToken: $resp.data.data.api_key" + } else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + return [] + } +} + +def apiGET(path) { + try { + httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +def apiPOST(path, body = [:]) { + try { + log.debug("Beginning API POST: ${path}, ${body}") + httpGet(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders() ) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +Map apiRequestHeaders() { + def userpassascii = "${username}:${password}" + if (state.miHomeAccessToken != null && state.miHomeAccessToken != '') { + userpassascii = "${username}:${state.miHomeAccessToken}" + } + def userpass = "Basic " + userpassascii.encodeAsBase64().toString() + log.debug userpassascii + + return ["User-Agent": "SmartThings Integration", + "Authorization": "$userpass" + ] +} + +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) { + options.logObject.error("got error: ${e}, body: ${e.getResponse().getData()}") + if (e.statusCode == 401) { // token is expired + state.remove("lifxAccessToken") + options.logObject.warn "Access token is not valid" + } + return options.errorReturn + } catch (java.net.SocketTimeoutException e) { + options.logObject.warn "Connection timed out, not much we can do here" + return options.errorReturn + } +} \ No newline at end of file From 9cc3b1133582d02bc0f7f5d9621cc1adf4be2104 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 9 Jan 2016 21:58:12 +0000 Subject: [PATCH 093/685] Initial Release --- .../mihome-connect.src/mihome-connect.groovy | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 smartapps/alyc100/mihome-connect.src/mihome-connect.groovy diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy new file mode 100644 index 00000000000..58f7381e5eb --- /dev/null +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -0,0 +1,215 @@ +/** + * MiHome (Connect) + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 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 + * 09.01.2016 + * v1.0 - Initial Release + */ +definition( + name: "MiHome (Connect)", + namespace: "alyc100", + author: "Alex Lee Yuk Cheung", + description: "Connect your MiHome devices to SmartThings.", + iconUrl: "https://mihome4u.co.uk/assets/homepage/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", + iconX2Url: "https://mihome4u.co.uk/assets/homepage/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", + singleInstance: true +) + +preferences { + page(name:"firstPage", title:"MiHome Device Setup", content:"firstPage", install: true) +} + +def apiURL(path = '/') { return "https://mihome4u.co.uk/api/v1${path}" } + +def firstPage() { + log.debug "firstPage" + if (username == null || username == '' || password == null || password == '') { + return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + section { + input("username", "text", title: "Username", description: "Your MiHome username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your MiHome password", required: true, submitOnChange: true) + } + } + } + else + { + log.debug "next phase" + getMiHomeAccessToken() + updateDevices() + return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + section { + input("username", "text", title: "Username", description: "Your MiHome username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your MiHome password", required: true, submitOnChange: true) + } + } + } +} + +// App lifecycle hooks + +def installed() { + initialize() + // Check for new devices and remove old ones every 3 hours + runEvery3Hours('updateDevices') + // execute handlerMethod every 10 minutes. + schedule("0 0/10 * * * ?", refreshDevices) +} + +// called after settings are changed +def updated() { + initialize() + unschedule('refreshDevices') + schedule("0 0/10 * * * ?", refreshDevices) +} + +def uninstalled() { + log.info("Uninstalling, removing child devices...") + unschedule('updateDevices') + unschedule('refreshDevices') + removeChildDevices(getChildDevices()) +} + +private removeChildDevices(devices) { + devices.each { + deleteChildDevice(it.deviceNetworkId) // 'it' is default + } +} + +// called after Done is hit after selecting a Location +def initialize() { + log.debug "initialize" + updateDevices() +} + + +def updateDevices() { + if (!state.devices) { + state.devices = [:] + } + def devices = devicesList() + def selectors = [] + devices.each { device -> + def childDevice = getChildDevice("${device.id}") + selectors.add("${device.id}") + if (!childDevice) { + log.info("Adding device ${device.id}: ${device.device_type}: ${device.label}: ${device.target_temperature}: ${device.last_temperature}: ${device.voltage}") + if (device.device_type == 'etrv') + { + def data = [ + name: device.label + " eTRV", + label: device.label + " eTRV", + temperature: device.last_temperature, + heatingSetpoint: device.target_temperature, + switch: device.target_temperature == 12 ? "off" : "on" + ] + childDevice = addChildDevice(app.namespace, "MiHome eTRV", "$device.id", null, data) + } + //Support for further miHome device types can be added here. + } + } + getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { + log.info("Deleting ${it.deviceNetworkId}") + deleteChildDevice(it.deviceNetworkId) + } + runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block +} + +def refreshDevices() { + log.info("Refreshing all devices...") + getChildDevices().each { device -> + device.refresh() + } +} + +def devicesList() { + logErrors([]) { + def resp = apiGET("/subdevices/list") + if (resp.status == 200) { + return resp.data.data + } else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + return [] + } + } +} + +def getMiHomeAccessToken() { + def resp = apiGET("/users/profile") + if (resp.status == 200) { + state.miHomeAccessToken = resp.data.data.api_key + log.debug "miHomeAccessToken: $resp.data.data.api_key" + } else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + return [] + } +} + +def apiGET(path) { + try { + httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +def apiPOST(path, body = [:]) { + try { + log.debug("Beginning API POST: ${path}, ${body}") + httpGet(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders() ) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +Map apiRequestHeaders() { + def userpassascii = "${username}:${password}" + if (state.miHomeAccessToken != null && state.miHomeAccessToken != '') { + userpassascii = "${username}:${state.miHomeAccessToken}" + } + def userpass = "Basic " + userpassascii.encodeAsBase64().toString() + log.debug userpassascii + + return ["User-Agent": "SmartThings Integration", + "Authorization": "$userpass" + ] +} + +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) { + options.logObject.error("got error: ${e}, body: ${e.getResponse().getData()}") + if (e.statusCode == 401) { // token is expired + state.remove("lifxAccessToken") + options.logObject.warn "Access token is not valid" + } + return options.errorReturn + } catch (java.net.SocketTimeoutException e) { + options.logObject.warn "Connection timed out, not much we can do here" + return options.errorReturn + } +} \ No newline at end of file From 08fb5644ec71bb336e6d39b4f3e3cd8fd13e6c04 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 9 Jan 2016 21:58:56 +0000 Subject: [PATCH 094/685] Initial Release --- .../mihome-etrv.src/mihome-etrv.groovy | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy new file mode 100644 index 00000000000..6f5360adef2 --- /dev/null +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -0,0 +1,212 @@ +/** + * MiHome eTRV + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 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 + * 09.01.2016 + * v1.0 - Initial Release + */ + +metadata { + definition (name: "MiHome eTRV", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Temperature Measurement" + capability "Thermostat" + capability "Thermostat Heating Setpoint" + capability "Switch" + + command "heatingSetpointUp" + command "heatingSetpointDown" + command "setHeatingSetpoint" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"lightin") { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ + attributeState "default", label: '${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#153591"], + [value: 10, color: "#1e9cbb"], + [value: 13, color: "#90d2a7"], + [value: 17, color: "#44b621"], + [value: 20, color: "#f1d801"], + [value: 25, color: "#d04e00"], + [value: 29, color: "#bc2323"] + ] + } + tileAttribute ("voltage", key: "SECONDARY_CONTROL") { + attributeState "voltage", label:'${currentValue}' + } + } + + valueTile("thermostat_small", "device.temperature", width: 4, height: 4) { + state "default", label:'${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#153591"], + [value: 10, color: "#1e9cbb"], + [value: 13, color: "#90d2a7"], + [value: 17, color: "#44b621"], + [value: 20, color: "#f1d801"], + [value: 25, color: "#d04e00"], + [value: 29, color: "#bc2323"] + ] + } + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { + state "default", label:'${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#153591"], + [value: 10, color: "#1e9cbb"], + [value: 13, color: "#90d2a7"], + [value: 17, color: "#44b621"], + [value: 20, color: "#f1d801"], + [value: 25, color: "#d04e00"], + [value: 29, color: "#bc2323"] + ] + } + + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(12..30)") { + state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" + } + + standardTile("switch", "device.switch", decoration: "flat", height: 2, width: 2, inactiveLabel: false) { + state "on", label:'${name}', action:"switch.off", icon:"st.Home.home1", backgroundColor:"#f1d801" + state "off", label:'${name}', action:"switch.on", icon:"st.Home.home1", backgroundColor:"#ffffff" + } + + main(["thermostat_small"]) + details(["thermostat", "heatingSetpoint", "heatSliderControl", "switch", "refresh"]) + } +} + +def uninstalled() { + unschedule() +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'temperature' attribute + // TODO: handle 'heatingSetpoint' attribute + // TODO: handle 'thermostatSetpoint' attribute + // TODO: handle 'thermostatMode' attribute + // TODO: handle 'thermostatOperatingState' attribute +} + +// handle commands +def setHeatingSetpoint(temp) { + log.debug "Executing 'setHeatingSetpoint with temp $temp'" + def latestThermostatMode = device.latestState('thermostatMode') + + if (temp < 12) { + temp = 12 + } + if (temp > 30) { + temp = 30 + } + + def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: temp]).toString())) + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + } + else { + runIn(1, refresh) + } +} + +def heatingSetpointUp(){ + log.debug "Executing 'heatingSetpointUp'" + int newSetpoint = device.currentValue("heatingSetpoint") + 1 + log.debug "Setting heat set point up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def heatingSetpointDown(){ + log.debug "Executing 'heatingSetpointDown'" + int newSetpoint = device.currentValue("heatingSetpoint") - 1 + log.debug "Setting heat set point down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def off() { + setThermostatMode('off') + +} + +def on() { + def lastHeatingSetPoint = !state.lastHeatingSetPoint ? 21 : state.lastHeatingSetPoint + setHeatingSetPoint(lastHeatingSetPoint) +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + setThermostatMode('heat') +} + +def auto() { + setThermostatMode('heat') +} + +def setThermostatMode(mode) { + mode = mode == 'cool' ? 'heat' : mode + log.debug "Executing 'setThermostatMode with mode $mode'" + + if (mode == 'off') { + state.lastHeatingSetPoint = device.currentValue('heatingSetpoint') + setHeatingSetPoint(12) + } else { + on() + } + +} + +def poll() { + log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" + + def resp = parent.apiGET("/subdevices/show?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger()]).toString())) + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + + sendEvent(name: "temperature", value: resp.data.data.last_temperature, unit: "C", state: "heat") + sendEvent(name: "heatingSetpoint", value: resp.data.data.target_temperature, unit: "C", state: "heat") + sendEvent(name: "thermostatMode", value: resp.data.data.target_temperature == 12 ? "off" : "heat") + sendEvent(name: 'thermostatOperatingState', value: resp.data.data.target_temperature == 12 ? "idle" : "heating") + sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) + sendEvent(name: "switch", value: resp.data.data.target_temperature == 12 ? "off" : "on") + sendEvent(name: "voltage", value: "Battery Voltage is " + resp.data.data.voltage + "V") + + return [] + +} + +def refresh() { + log.debug "Executing 'refresh'" + poll() +} \ No newline at end of file From 526b86444a68d8d3fffbbc2137e940bff7b5cb16 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 9 Jan 2016 23:41:51 +0000 Subject: [PATCH 095/685] Minor fixes --- .../hive-active-heating.src/hive-active-heating.groovy | 4 ++-- .../hive-active-hot-water.src/hive-active-hot-water.groovy | 4 ++-- .../alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy index 1166f6db6a7..0ab3e7c3bb3 100644 --- a/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy +++ b/devicetypes/alyc100/hive-active-heating.src/hive-active-heating.groovy @@ -234,13 +234,13 @@ def installed() { def updated() { log.debug "Executing 'updated'" // execute handlerMethod every 10 minutes. - unschedule('poll') + unschedule(poll) schedule("0 0/10 * * * ?", poll) } def uninstalled() { log.debug "Executing 'unsinstalled'" - unschedule('poll') + unschedule(poll) } // handle commands diff --git a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy index a04b6ff8a94..c426994a9a7 100644 --- a/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy +++ b/devicetypes/alyc100/hive-active-hot-water.src/hive-active-hot-water.groovy @@ -158,13 +158,13 @@ def installed() { def updated() { log.debug "Executing 'updated'" // execute handlerMethod every 10 minutes. - unschedule('poll') + unschedule(poll) schedule("0 0/10 * * * ?", poll) } def uninstalled() { log.debug "Executing 'unsinstalled'" - unschedule('poll') + unschedule(poll) } // handle commands diff --git a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy index 887374df29a..4598a168a49 100644 --- a/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy +++ b/devicetypes/alyc100/ovo-energy-meter.src/ovo-energy-meter.groovy @@ -105,13 +105,13 @@ def installed() { def updated() { log.debug "Executing 'updated'" // execute handlerMethod every 10 minutes. - unschedule('poll') + unschedule(poll) schedule("0 0/1 * * * ?", poll) } def uninstalled() { log.debug "Executing 'unsinstalled'" - unschedule('poll') + unschedule(poll) } // handle commands From 3b223803813c0e3ef2cf2751c32d7b50dad2d4a9 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 9 Jan 2016 23:43:50 +0000 Subject: [PATCH 096/685] Minor Fixes --- smartapps/alyc100/mihome-connect.src/mihome-connect.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index 58f7381e5eb..d7f38c3baf1 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -69,14 +69,14 @@ def installed() { // called after settings are changed def updated() { initialize() - unschedule('refreshDevices') + unschedule(refreshDevices) schedule("0 0/10 * * * ?", refreshDevices) } def uninstalled() { log.info("Uninstalling, removing child devices...") - unschedule('updateDevices') - unschedule('refreshDevices') + unschedule(updateDevices) + unschedule(refreshDevices) removeChildDevices(getChildDevices()) } From bac50179f7342743a4d618d2fb9521afcf6c3730 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 10 Jan 2016 00:21:21 +0000 Subject: [PATCH 097/685] v1.1 - Boost Mode implementation --- .../mihome-etrv.src/mihome-etrv.groovy | 121 ++++++++++++++++-- 1 file changed, 111 insertions(+), 10 deletions(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index 6f5360adef2..1b33a178467 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -16,6 +16,7 @@ * VERSION HISTORY * 09.01.2016 * v1.0 - Initial Release + * v1.1 - Added BETA Boost Capability */ metadata { @@ -31,6 +32,7 @@ metadata { command "heatingSetpointUp" command "heatingSetpointDown" command "setHeatingSetpoint" + command "setBoostLength" } simulator { @@ -91,16 +93,29 @@ metadata { state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" } + controlTile("boostSliderControl", "device.boostLength", "slider", height: 2, width: 4, inactiveLabel: false, range:"(60..300)") { + state ("setBoostLength", label:'Set boost length to', action:"setBoostLength") + } + standardTile("switch", "device.switch", decoration: "flat", height: 2, width: 2, inactiveLabel: false) { state "on", label:'${name}', action:"switch.off", icon:"st.Home.home1", backgroundColor:"#f1d801" state "off", label:'${name}', action:"switch.on", icon:"st.Home.home1", backgroundColor:"#ffffff" } + valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'${currentValue}', action:"emergencyHeat") + } + main(["thermostat_small"]) - details(["thermostat", "heatingSetpoint", "heatSliderControl", "switch", "refresh"]) + details(["thermostat", "heatingSetpoint", "heatSliderControl", "boost", "boostSliderControl", "switch", "refresh"]) } } +def installed() { + log.debug "Executing 'installed'" + state.boostLength = 60 +} + def uninstalled() { unschedule() } @@ -126,7 +141,7 @@ def setHeatingSetpoint(temp) { if (temp > 30) { temp = 30 } - + state.boost = "off" def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: temp]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") @@ -136,6 +151,33 @@ def setHeatingSetpoint(temp) { } } +def setBoostLength(minutes) { + log.debug "Executing 'setBoostLength with length $minutes minutes'" + if (minutes < 60) { + minutes = 60 + } + if (minutes > 300) { + minutes = 300 + } + state.boostLength = minutes + sendEvent(name:"boostLength", value: state.boostLength, displayed: true) + + def latestThermostatMode = device.latestState('thermostatMode') + + //If already in BOOST mode, send updated boost length. + if (latestThermostatMode.stringValue == 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + refresh() + } +} + +def stopBoost() { + state.boost = "off" + on() +} + def heatingSetpointUp(){ log.debug "Executing 'heatingSetpointUp'" int newSetpoint = device.currentValue("heatingSetpoint") + 1 @@ -150,14 +192,21 @@ def heatingSetpointDown(){ setHeatingSetpoint(newSetpoint) } +def setLastHeatingSetpoint(temp) { + //Don't store set point if it is 12. + if (temp > 12) { + state.lastHeatingSetPoint = temp + } +} + def off() { setThermostatMode('off') } def on() { - def lastHeatingSetPoint = !state.lastHeatingSetPoint ? 21 : state.lastHeatingSetPoint - setHeatingSetPoint(lastHeatingSetPoint) + setThermostatMode('heat') + } def heat() { @@ -165,7 +214,17 @@ def heat() { } def emergencyHeat() { - setThermostatMode('heat') + log.debug "Executing 'boost'" + + def latestThermostatMode = device.latestState('thermostatMode') + + //Don't do if already in BOOST mode. + if (latestThermostatMode.stringValue != 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + log.debug "Already in boost mode." + } } def auto() { @@ -177,10 +236,36 @@ def setThermostatMode(mode) { log.debug "Executing 'setThermostatMode with mode $mode'" if (mode == 'off') { - state.lastHeatingSetPoint = device.currentValue('heatingSetpoint') - setHeatingSetPoint(12) + unschedule(stopBoost) + state.boost = "off" + setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) + setHeatingSetpoint(12) + } else if (mode == 'emergency heat') { + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) + state.boost = "on" + def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 21]).toString())) + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + } + else { + runIn(1, refresh) + } + //Schedule boost switch off + schedule(now() + (state.boostLength * 60000), stopBoost) } else { - on() + unschedule(stopBoost) + state.boost = "off" + def lastHeatingSetPoint = 21 + if (state.lastHeatingSetPoint != null && state.lastHeatingSetPoint > 12) + { + lastHeatingSetPoint = state.lastHeatingSetPoint + } + setHeatingSetpoint(lastHeatingSetPoint) } } @@ -193,14 +278,30 @@ def poll() { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") return [] } - + + //Boost button label + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + def boostLabel = "" + sendEvent(name: "temperature", value: resp.data.data.last_temperature, unit: "C", state: "heat") sendEvent(name: "heatingSetpoint", value: resp.data.data.target_temperature, unit: "C", state: "heat") - sendEvent(name: "thermostatMode", value: resp.data.data.target_temperature == 12 ? "off" : "heat") + if (state.boost != null && state.boost == "on") { + sendEvent(name: "thermostatMode", value: "emergency heat") + boostLabel = "Boosting" + } + else { + sendEvent(name: "thermostatMode", value: resp.data.data.target_temperature == 12 ? "off" : "heat") + boostLabel = "Start\n$state.boostLength Min Boost" + } sendEvent(name: 'thermostatOperatingState', value: resp.data.data.target_temperature == 12 ? "idle" : "heating") sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) sendEvent(name: "switch", value: resp.data.data.target_temperature == 12 ? "off" : "on") sendEvent(name: "voltage", value: "Battery Voltage is " + resp.data.data.voltage + "V") + sendEvent(name: "boostLabel", value: boostLabel, displayed: false) return [] From a610dfe77b1b2eba602f9174af6f8b04ae7a194f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 10 Jan 2016 00:24:05 +0000 Subject: [PATCH 098/685] Mode icons added --- devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index 1b33a178467..034a6a6e924 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -102,6 +102,13 @@ metadata { state "off", label:'${name}', action:"switch.on", icon:"st.Home.home1", backgroundColor:"#ffffff" } + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("auto", label: "SCHEDULED", icon:"st.Office.office7") + state("off", icon:"st.thermostat.heating-cooling-off") + state("heat", label: "MANUAL", icon:"st.Weather.weather2") + state("emergency heat", label: "BOOST", icon:"st.Health & Wellness.health7") + } + valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state("default", label:'${currentValue}', action:"emergencyHeat") } From 5ca4fbcb636697fb01176c3b3b47dd8bedd12d7f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 10 Jan 2016 01:09:43 +0000 Subject: [PATCH 099/685] Minor changes to battery voltage display --- devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index 034a6a6e924..d064f0c8429 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -54,8 +54,8 @@ metadata { [value: 29, color: "#bc2323"] ] } - tileAttribute ("voltage", key: "SECONDARY_CONTROL") { - attributeState "voltage", label:'${currentValue}' + tileAttribute ("batteryVoltage", key: "SECONDARY_CONTROL") { + attributeState "batteryVoltage", label:'Battery Voltage Is ${currentValue}' } } @@ -307,7 +307,7 @@ def poll() { sendEvent(name: 'thermostatOperatingState', value: resp.data.data.target_temperature == 12 ? "idle" : "heating") sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) sendEvent(name: "switch", value: resp.data.data.target_temperature == 12 ? "off" : "on") - sendEvent(name: "voltage", value: "Battery Voltage is " + resp.data.data.voltage + "V") + sendEvent(name: "batteryVoltage", value: resp.data.data.voltage + "V") sendEvent(name: "boostLabel", value: boostLabel, displayed: false) return [] From b52926fe4c0bd8876c9ee90a77955e2e9bd010b6 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 10 Jan 2016 14:23:51 +0000 Subject: [PATCH 100/685] v1.1.1 - Fixed stopBoost always returning to 'on' mode. --- .../alyc100/mihome-etrv.src/mihome-etrv.groovy | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index d064f0c8429..bacf50177c9 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -17,6 +17,9 @@ * 09.01.2016 * v1.0 - Initial Release * v1.1 - Added BETA Boost Capability + * + * 10.01.2016 + * v1.1.1 - Fixed stopBoost always returning to 'on' mode. */ metadata { @@ -181,8 +184,14 @@ def setBoostLength(minutes) { } def stopBoost() { + log.debug "Executing 'stopBoost'" state.boost = "off" - on() + if (state.lastThermostatMode == 'off') { + off() + } + else { + on() + } } def heatingSetpointUp(){ @@ -253,6 +262,7 @@ def setThermostatMode(mode) { state.boostLength = 60 sendEvent("name":"boostLength", "value": 60, displayed: true) } + state.lastThermostatMode = device.latestState('thermostatMode') setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) state.boost = "on" def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 21]).toString())) From be9c5779839bc35121214940e724608ad69ad54a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 10 Jan 2016 19:57:56 +0000 Subject: [PATCH 101/685] Minor fixes --- devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index bacf50177c9..1f694d70109 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -317,7 +317,7 @@ def poll() { sendEvent(name: 'thermostatOperatingState', value: resp.data.data.target_temperature == 12 ? "idle" : "heating") sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) sendEvent(name: "switch", value: resp.data.data.target_temperature == 12 ? "off" : "on") - sendEvent(name: "batteryVoltage", value: resp.data.data.voltage + "V") + sendEvent(name: "batteryVoltage", value: resp.data.data.voltage == null ? "Not Available" : "$resp.data.data.voltageV") sendEvent(name: "boostLabel", value: boostLabel, displayed: false) return [] From 0a08f325bf9653038c25eadbfaa6509eb64d0f69 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 10 Jan 2016 22:00:50 +0000 Subject: [PATCH 102/685] Minor fixes for voltage reporting --- devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index 1f694d70109..cbd09881d6a 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -317,7 +317,7 @@ def poll() { sendEvent(name: 'thermostatOperatingState', value: resp.data.data.target_temperature == 12 ? "idle" : "heating") sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) sendEvent(name: "switch", value: resp.data.data.target_temperature == 12 ? "off" : "on") - sendEvent(name: "batteryVoltage", value: resp.data.data.voltage == null ? "Not Available" : "$resp.data.data.voltageV") + sendEvent(name: "batteryVoltage", value: resp.data.data.voltage == null ? "Not Available" : resp.data.data.voltage + "V") sendEvent(name: "boostLabel", value: boostLabel, displayed: false) return [] From 048460ae2b71094bad4adc3e63ce61da87355583 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 10 Jan 2016 22:07:20 +0000 Subject: [PATCH 103/685] Change boost temperature --- devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index cbd09881d6a..696d789a9e4 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -265,7 +265,7 @@ def setThermostatMode(mode) { state.lastThermostatMode = device.latestState('thermostatMode') setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) state.boost = "on" - def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 21]).toString())) + def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 23]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") } From e3465e9c76ea919279336e2fe4e9384a4b78397c Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 10 Jan 2016 22:20:03 +0000 Subject: [PATCH 104/685] v1.0.1 - Improve messaging on MiHome connect app --- .../alyc100/mihome-connect.src/mihome-connect.groovy | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index d7f38c3baf1..fce9095c26f 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -15,6 +15,9 @@ * VERSION HISTORY * 09.01.2016 * v1.0 - Initial Release + * + * 10.01.2016 + * v1.0.1 - Improve messaging for connection process. */ definition( name: "MiHome (Connect)", @@ -51,7 +54,14 @@ def firstPage() { section { input("username", "text", title: "Username", description: "Your MiHome username (usually an email address)", required: true) input("password", "password", title: "Password", description: "Your MiHome password", required: true, submitOnChange: true) - } + } + if (state.miHomeAccessToken != null && state.miHomeAccessToken != '') { + section { + paragraph "You have successfully connected to MiHome. Press 'Done' and your devices should have been added automatically." + } + } else { + paragraph "There was a problem connecting to MiHome. Check your user credential and error logs in SmartThings web console." + } } } } From faa53b6dbadb29c120f5816b21383de6a1c4167d Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 13 Jan 2016 15:17:43 +0000 Subject: [PATCH 105/685] v1.1.2 - Bug fix to Boost mode --- devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index 696d789a9e4..d2fd8ba7080 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -20,6 +20,7 @@ * * 10.01.2016 * v1.1.1 - Fixed stopBoost always returning to 'on' mode. + * v1.1.2 - Bug fix to Boost mode not executing. */ metadata { @@ -265,12 +266,12 @@ def setThermostatMode(mode) { state.lastThermostatMode = device.latestState('thermostatMode') setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) state.boost = "on" - def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 23]).toString())) + def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 22]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") } else { - runIn(1, refresh) + refresh() } //Schedule boost switch off schedule(now() + (state.boostLength * 60000), stopBoost) From e45d5be7002240c069c65f070857e2417dce5571 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 13 Jan 2016 16:06:38 +0000 Subject: [PATCH 106/685] More boost bug fixes --- .../alyc100/mihome-etrv.src/mihome-etrv.groovy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index d2fd8ba7080..a1758b5642e 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -152,7 +152,7 @@ def setHeatingSetpoint(temp) { if (temp > 30) { temp = 30 } - state.boost = "off" + atomicState.boost = "off" def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: temp]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") @@ -186,7 +186,7 @@ def setBoostLength(minutes) { def stopBoost() { log.debug "Executing 'stopBoost'" - state.boost = "off" + atomicState.boost = "off" if (state.lastThermostatMode == 'off') { off() } @@ -254,7 +254,7 @@ def setThermostatMode(mode) { if (mode == 'off') { unschedule(stopBoost) - state.boost = "off" + atomicState.boost = "off" setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) setHeatingSetpoint(12) } else if (mode == 'emergency heat') { @@ -265,7 +265,7 @@ def setThermostatMode(mode) { } state.lastThermostatMode = device.latestState('thermostatMode') setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) - state.boost = "on" + atomicState.boost = "on" def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 22]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") @@ -277,7 +277,7 @@ def setThermostatMode(mode) { schedule(now() + (state.boostLength * 60000), stopBoost) } else { unschedule(stopBoost) - state.boost = "off" + atomicState.boost = "off" def lastHeatingSetPoint = 21 if (state.lastHeatingSetPoint != null && state.lastHeatingSetPoint > 12) { @@ -307,7 +307,7 @@ def poll() { sendEvent(name: "temperature", value: resp.data.data.last_temperature, unit: "C", state: "heat") sendEvent(name: "heatingSetpoint", value: resp.data.data.target_temperature, unit: "C", state: "heat") - if (state.boost != null && state.boost == "on") { + if (atomicState.boost != null && atomicState.boost == "on") { sendEvent(name: "thermostatMode", value: "emergency heat") boostLabel = "Boosting" } From 086854f655f7d35b3a78c1162de72b2e3097529a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 13 Jan 2016 16:08:33 +0000 Subject: [PATCH 107/685] Undo bug fix --- .../alyc100/mihome-etrv.src/mihome-etrv.groovy | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index a1758b5642e..007e80df03d 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -152,7 +152,7 @@ def setHeatingSetpoint(temp) { if (temp > 30) { temp = 30 } - atomicState.boost = "off" + state.boost = "off" def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: temp]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") @@ -186,7 +186,7 @@ def setBoostLength(minutes) { def stopBoost() { log.debug "Executing 'stopBoost'" - atomicState.boost = "off" + state.boost = "off" if (state.lastThermostatMode == 'off') { off() } @@ -254,7 +254,7 @@ def setThermostatMode(mode) { if (mode == 'off') { unschedule(stopBoost) - atomicState.boost = "off" + state.boost = "off" setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) setHeatingSetpoint(12) } else if (mode == 'emergency heat') { @@ -265,19 +265,20 @@ def setThermostatMode(mode) { } state.lastThermostatMode = device.latestState('thermostatMode') setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) - atomicState.boost = "on" + state.boost = "on" def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 22]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") } else { + sendEvent(name: "thermostatMode", value: "emergency heat") refresh() } //Schedule boost switch off schedule(now() + (state.boostLength * 60000), stopBoost) } else { unschedule(stopBoost) - atomicState.boost = "off" + state.boost = "off" def lastHeatingSetPoint = 21 if (state.lastHeatingSetPoint != null && state.lastHeatingSetPoint > 12) { @@ -307,7 +308,7 @@ def poll() { sendEvent(name: "temperature", value: resp.data.data.last_temperature, unit: "C", state: "heat") sendEvent(name: "heatingSetpoint", value: resp.data.data.target_temperature, unit: "C", state: "heat") - if (atomicState.boost != null && atomicState.boost == "on") { + if (state.boost != null && state.boost == "on") { sendEvent(name: "thermostatMode", value: "emergency heat") boostLabel = "Boosting" } From 8e5c952f2be88b8f1225888592aded4cb28fb891 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 13 Jan 2016 16:19:38 +0000 Subject: [PATCH 108/685] boost mode fixes. Third try! --- .../alyc100/mihome-etrv.src/mihome-etrv.groovy | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy index 007e80df03d..31856b172cb 100644 --- a/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy +++ b/devicetypes/alyc100/mihome-etrv.src/mihome-etrv.groovy @@ -152,7 +152,7 @@ def setHeatingSetpoint(temp) { if (temp > 30) { temp = 30 } - state.boost = "off" + sendEvent(name: "boostSwitch", value: "off", displayed: false) def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: temp]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") @@ -186,7 +186,7 @@ def setBoostLength(minutes) { def stopBoost() { log.debug "Executing 'stopBoost'" - state.boost = "off" + sendEvent(name: "boostSwitch", value: "off", displayed: false) if (state.lastThermostatMode == 'off') { off() } @@ -254,7 +254,7 @@ def setThermostatMode(mode) { if (mode == 'off') { unschedule(stopBoost) - state.boost = "off" + sendEvent(name: "boostSwitch", value: "off", displayed: false) setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) setHeatingSetpoint(12) } else if (mode == 'emergency heat') { @@ -265,20 +265,19 @@ def setThermostatMode(mode) { } state.lastThermostatMode = device.latestState('thermostatMode') setLastHeatingSetpoint(device.currentValue('heatingSetpoint')) - state.boost = "on" + sendEvent(name: "boostSwitch", value: "on", displayed: false) def resp = parent.apiGET("/subdevices/set_target_temperature?params=" + URLEncoder.encode(new groovy.json.JsonBuilder([id: device.deviceNetworkId.toInteger(), temperature: 22]).toString())) if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") } else { - sendEvent(name: "thermostatMode", value: "emergency heat") refresh() } //Schedule boost switch off schedule(now() + (state.boostLength * 60000), stopBoost) } else { unschedule(stopBoost) - state.boost = "off" + sendEvent(name: "boostSwitch", value: "off", displayed: false) def lastHeatingSetPoint = 21 if (state.lastHeatingSetPoint != null && state.lastHeatingSetPoint > 12) { @@ -308,7 +307,9 @@ def poll() { sendEvent(name: "temperature", value: resp.data.data.last_temperature, unit: "C", state: "heat") sendEvent(name: "heatingSetpoint", value: resp.data.data.target_temperature, unit: "C", state: "heat") - if (state.boost != null && state.boost == "on") { + def boostSwitch = device.currentValue("boostSwitch") + log.debug "boostSwitch: $boostSwitch" + if (boostSwitch != null && boostSwitch == "on") { sendEvent(name: "thermostatMode", value: "emergency heat") boostLabel = "Boosting" } From 9daf2e0505c4c27cb36f4b0867707f9283cc32fd Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 17 Jan 2016 09:45:20 +0000 Subject: [PATCH 109/685] v1.0.2 - Bug fix when MiHome device has been manually deleted --- .../alyc100/mihome-connect.src/mihome-connect.groovy | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index fce9095c26f..958dd7848b4 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -18,6 +18,9 @@ * * 10.01.2016 * v1.0.1 - Improve messaging for connection process. + * + * 17.01.2016 + * v1.0.2 - Bug fix when device has been manually deleted. */ definition( name: "MiHome (Connect)", @@ -130,7 +133,11 @@ def updateDevices() { } getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}") - deleteChildDevice(it.deviceNetworkId) + try { + deleteChildDevice(it.deviceNetworkId) + } catch (physicalgraph.exception.NotFoundException e) { + log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") + } } runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block } From 331dac144c1bed529803c0849493116375a40a1a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 20 Jan 2016 20:06:18 +0000 Subject: [PATCH 110/685] Light Switch Force Sync Initial Release --- .../light-switch-force-sync.groovy | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 smartapps/alyc100/light-switch-force-sync.src/light-switch-force-sync.groovy diff --git a/smartapps/alyc100/light-switch-force-sync.src/light-switch-force-sync.groovy b/smartapps/alyc100/light-switch-force-sync.src/light-switch-force-sync.groovy new file mode 100644 index 00000000000..f8756285331 --- /dev/null +++ b/smartapps/alyc100/light-switch-force-sync.src/light-switch-force-sync.groovy @@ -0,0 +1,90 @@ +/** + * Light Switch Force Sync + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * Created because of Osram bulbs turning on randomly. If Osram bulbs are grouped and controlled via a master virtual switch, + * this smart app enables the individual bulbs to be checked and the bulb state (on/off) synchronised with the state of the master virtual switch. + * + * 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 + * 20.01.2016 + * v1.0 - Initial Release + */ + +definition( + name: "Light Switch Force Sync", + namespace: "alyc100", + author: "Alex Lee Yuk Cheung", + description: "If you have a set of lights activated on a master switch. Poll to ensure states are updated.", + category: "My Apps", + 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" +) + +preferences { + page(name: "configurePage") + +} + +def configurePage() { + dynamicPage(name: "configurePage", title:"Setup", install: true, uninstall: true) { + section { + input("lights", "capability.switch", title: "For these lights", multiple: true, required: true) + } + + section { + input("masterSwitch", "capability.switch", title: "Synchronise to this master switch", multiple: false, required: true) + } + } +} + +// App lifecycle hooks + +def installed() { + // Check for new devices and remove old ones every 3 hours + // execute handlerMethod every 10 minutes. + schedule("0 0/5 * * * ?", syncLightsToSwitch) +} + +// called after settings are changed +def updated() { + log.debug "Executing 'updated()'" + unschedule(syncLightsToSwitch) + schedule("0 0/5 * * * ?", syncLightsToSwitch) +} + +def uninstalled() { + log.info("Uninstalling, removing child devices...") + unschedule(syncLightsToSwitch) +} + +def syncLightsToSwitch() { + log.debug "Executing 'syncLightsToSwitch()'" + lights.refresh() + runIn(10, updateLightState) +} + +def updateLightState() { + log.debug "masterSwitchState: $masterSwitch.currentSwitch" + for (light in lights) { + if (masterSwitch.currentSwitch == "on") { + if (light.currentSwitch == "off") { + light.on() + } + } else { + if (light.currentSwitch == "on") { + light.off() + } + } + } +} \ No newline at end of file From b884d44dad8a2c67ea0a5c95ef840c3dc880e3fb Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 31 Jan 2016 20:18:24 +0000 Subject: [PATCH 111/685] v1.0.3 - Bug fix to refresh schedule job --- .../mihome-connect.src/mihome-connect.groovy | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index 958dd7848b4..2daa2a29a0c 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -21,6 +21,9 @@ * * 17.01.2016 * v1.0.2 - Bug fix when device has been manually deleted. + * + * 31.01.2016 + * v1.0.3 - Bug fix to refresh schedule job. */ definition( name: "MiHome (Connect)", @@ -72,24 +75,26 @@ def firstPage() { // App lifecycle hooks def installed() { + log.debug "installed" initialize() // Check for new devices and remove old ones every 3 hours runEvery3Hours('updateDevices') // execute handlerMethod every 10 minutes. - schedule("0 0/10 * * * ?", refreshDevices) + runEvery5Minutes('refreshDevices') } // called after settings are changed def updated() { + log.debug "updated" initialize() - unschedule(refreshDevices) - schedule("0 0/10 * * * ?", refreshDevices) + unschedule('refreshDevices') + runEvery5Minutes('refreshDevices') } def uninstalled() { log.info("Uninstalling, removing child devices...") - unschedule(updateDevices) - unschedule(refreshDevices) + unschedule('updateDevices') + unschedule('refreshDevices') removeChildDevices(getChildDevices()) } From 581fdd4c280a1b83cb3751fc54f51328a4a9ac35 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 1 Feb 2016 12:26:05 +0000 Subject: [PATCH 112/685] Minor Bug Fixes --- smartapps/alyc100/mihome-connect.src/mihome-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index 2daa2a29a0c..6eff0bf4e66 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -144,7 +144,7 @@ def updateDevices() { log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") } } - runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block + //runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block } def refreshDevices() { From 60b8c5aba8dd9c27ad02eef30e1afc04bbff5deb Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 24 Feb 2016 14:01:43 +0000 Subject: [PATCH 113/685] Minor fixes --- smartapps/alyc100/mihome-connect.src/mihome-connect.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index 6eff0bf4e66..b7ffec9bfb3 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -80,7 +80,7 @@ def installed() { // Check for new devices and remove old ones every 3 hours runEvery3Hours('updateDevices') // execute handlerMethod every 10 minutes. - runEvery5Minutes('refreshDevices') + runEvery10Minutes('refreshDevices') } // called after settings are changed @@ -88,7 +88,7 @@ def updated() { log.debug "updated" initialize() unschedule('refreshDevices') - runEvery5Minutes('refreshDevices') + runEvery10Minutes('refreshDevices') } def uninstalled() { @@ -226,7 +226,7 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { } catch (groovyx.net.http.HttpResponseException e) { options.logObject.error("got error: ${e}, body: ${e.getResponse().getData()}") if (e.statusCode == 401) { // token is expired - state.remove("lifxAccessToken") + state.remove("miHomeAccessToken") options.logObject.warn "Access token is not valid" } return options.errorReturn From 615482d34ec45e1d8270950fa6deb18d64fa4092 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 25 Feb 2016 17:28:47 +0000 Subject: [PATCH 114/685] v1.0.4 - View connected devices in MiHome Connect app --- .../mihome-connect.src/mihome-connect.groovy | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index b7ffec9bfb3..ad77fc5504e 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -24,6 +24,9 @@ * * 31.01.2016 * v1.0.3 - Bug fix to refresh schedule job. + * + * 25.02.2016 + * v1.0.4 - View detected devices in Connect App */ definition( name: "MiHome (Connect)", @@ -65,6 +68,12 @@ def firstPage() { section { paragraph "You have successfully connected to MiHome. Press 'Done' and your devices should have been added automatically." } + section("Devices Discovered And Automatically Added...") { + state.devices.each {devices -> + paragraph devices.trim() + } + + } } else { paragraph "There was a problem connecting to MiHome. Check your user credential and error logs in SmartThings web console." } @@ -112,13 +121,12 @@ def initialize() { def updateDevices() { - if (!state.devices) { - state.devices = [:] - } + state.devices = [] def devices = devicesList() def selectors = [] devices.each { device -> def childDevice = getChildDevice("${device.id}") + state.devices.add("${device.label} eTRV") selectors.add("${device.id}") if (!childDevice) { log.info("Adding device ${device.id}: ${device.device_type}: ${device.label}: ${device.target_temperature}: ${device.last_temperature}: ${device.voltage}") From 319946b858892e2267bdb61c17031735aaaae811 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 25 Feb 2016 17:40:49 +0000 Subject: [PATCH 115/685] Hive (Connect) SmartApp Initial Release --- .../hive-connect.src/hive-connect.groovy | 348 ++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 smartapps/alyc100/hive-connect.src/hive-connect.groovy diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy new file mode 100644 index 00000000000..cb27c2a01b9 --- /dev/null +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -0,0 +1,348 @@ +/** + * Hive (Connect) + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 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 + * + */ +definition( + name: "Hive (Connect)", + namespace: "alyc100", + author: "Alex Lee Yuk Cheung", + description: "Connect your Hive devices to SmartThings.", + iconUrl: "https://www.hivehome.com/attachment/46/download/hive_logo_low_res.png", + iconX2Url: "https://www.hivehome.com/attachment/46/download/hive_logo_low_res.png", + singleInstance: true +) + +preferences { + page(name:"firstPage", title:"Hive Device Setup", content:"firstPage", install: true) +} + +def apiURL(path = '/') { return "https://api.prod.bgchprod.info:443/omnia${path}" } + +def firstPage() { + log.debug "firstPage" + if (username == null || username == '' || password == null || password == '') { + return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + section { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) + } + } + } + else + { + log.debug "next phase" + getHiveAccessToken() + updateDevices() + return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + section { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) + } + if (state.hiveAccessToken != null && state.hiveAccessToken != '') { + section { + paragraph "You have successfully connected to Hive. Press 'Done' and your devices should be discovered." + } + section("Select a device...") { + input "selectedHeating", "enum", required:false, title:"Select Hive Heating Devices \n(${state.hiveHeatingDevices.size() ?: 0} found)", multiple:true, options:state.hiveHeatingDevices + input "selectedHotWater", "enum", required:false, title:"Select Hive Hot Water Devices \n(${state.hiveHotWaterDevices.size() ?: 0} found)", multiple:true, options:state.hiveHotWaterDevices + + } + } else { + section { + paragraph "There was a problem connecting to Hive. Check your user credential and error logs in SmartThings web console." + } + } + } + } +} + +// App lifecycle hooks + +def installed() { + 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 updated() { + log.debug "updated" + initialize() + unschedule('refreshDevices') + runEvery10Minutes('refreshDevices') +} + +def uninstalled() { + log.info("Uninstalling, removing child devices...") + unschedule() + removeChildDevices(getChildDevices()) +} + +private removeChildDevices(devices) { + devices.each { + deleteChildDevice(it.deviceNetworkId) // 'it' is default + } +} + +// called after Done is hit after selecting a Location +def initialize() { + log.debug "initialize" + if (selectedHeating) + addHeating() + + if (selectedHotWater) + addHotWater() + + runIn(10, 'refreshDevices') // Asynchronously refresh devices so we don't block +} + + +def updateDevices() { + if (!state.devices) { + state.devices = [:] + } + def devices = devicesList() + state.hiveHeatingDevices = [:] + state.hiveHotWaterDevices = [:] + devices.each { device -> + if (device.attributes.capabilities != null && device.attributes.capabilities.reportedValue[0] == "THERMOSTAT") { + def parentNode = devices.find { d -> d.id == device.parentNodeId } + if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == false) && (device.attributes.temperature != null)) { + def value = "${parentNode.name} Hive Heating" + def key = device.id + state.hiveHeatingDevices["${key}"] = value + } + else if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == true)) { + def value = "${parentNode.name} Hive Hot Water" + def key = device.id + state.hiveHotWaterDevices["${key}"] = value + } + // Support for more Hive Device Types can be added here in the future. + } + } +} + +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(app.namespace, "Hive Heating V2.0", "$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(app.namespace, "Hive Hot Water V2.0", "$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 refreshDevices() { + log.info("Refreshing all devices...") + getChildDevices().each { device -> + device.refresh() + } +} + +def devicesList() { + logErrors([]) { + def resp = apiGET("/nodes") + if (resp.status == 200) { + return resp.data.nodes + } else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + return [] + } + } +} + +def apiGET(path, body = [:]) { + try { + if(!isLoggedIn()) { + log.debug "Need to login" + getHiveAccessToken() + } + log.debug("Beginning API GET: ${apiURL(path)}, ${apiRequestHeaders()}") + + httpGet(uri: apiURL(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" + getHiveAccessToken() + } + log.debug("Beginning API POST: ${path}, ${body}") + + httpPostJson(uri: apiURL(path), body: body, headers: apiRequestHeaders() ) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +def apiPUT(path, body = [:]) { + try { + if(!isLoggedIn()) { + log.debug "Need to login" + getHiveAccessToken() + } + log.debug("Beginning API POST: ${path}, ${body}") + + httpPutJson(uri: apiURL(path), body: body, headers: apiRequestHeaders() ) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +def getHiveAccessToken() { + try { + def params = [ + uri: apiURL('/auth/sessions'), + contentType: 'application/json', + headers: [ + 'Content-Type': 'application/vnd.alertme.zoo-6.1+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Hive Web Dashboard', + ], + body: [ + sessions: [ [username: settings.username, + password: settings.password, + caller: 'Hive Web Dashboard']] + ] + ] + + state.cookie = '' + + httpPostJson(params) {response -> + log.debug "Request was successful, $response.status" + log.debug response.headers + state.hiveAccessToken = response.data + + 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 "sessionid: ${response.data.sessions[0].id}" + + state.hiveAccessToken = response.data.sessions[0].id + // set the expiration to 5 minutes + state.hiveAccessToken_expires_at = new Date().getTime() + 300000; + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +Map apiRequestHeaders() { + return [ + 'Cookie': state.cookie, + 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', + 'Accept': 'application/vnd.alertme.zoo-6.2+json', + 'Content-Type': 'application/*+json', + 'X-AlertMe-Client': 'Hive Web Dashboard', + 'X-Omnia-Access-Token': "${state.hiveAccessToken}" + ] +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $state.hiveAccessToken" + if(!state.hiveAccessToken) { + log.debug "No state.hiveAccessToken" + return false + } + + def now = new Date().getTime(); + return state.hiveAccessToken_expires_at > now +} + +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) { + options.logObject.error("got error: ${e}, body: ${e.getResponse().getData()}") + if (e.statusCode == 401) { // token is expired + state.remove("hiveAccessToken") + options.logObject.warn "Access token is not valid" + } + return options.errorReturn + } catch (java.net.SocketTimeoutException e) { + options.logObject.warn "Connection timed out, not much we can do here" + return options.errorReturn + } +} \ No newline at end of file From 94cf46ff6f5577e1b8a4f4cb58da1ae036388652 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 25 Feb 2016 17:43:08 +0000 Subject: [PATCH 116/685] New Hive SmartThings Devices to support new Hive (Connect) smart app --- .../hive-heating-v2-0.groovy | 394 ++++++++++++++++++ .../hive-hot-water-v2-0.groovy | 290 +++++++++++++ 2 files changed, 684 insertions(+) create mode 100644 devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy create mode 100644 devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy new file mode 100644 index 00000000000..3bbaeb2a919 --- /dev/null +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -0,0 +1,394 @@ +/** + * Hive Heating V2.0 + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 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 + * 25.02.2016 + * v2.0 BETA - Initial Release + */ + +metadata { + definition (name: "Hive Heating V2.0", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Temperature Measurement" + capability "Thermostat" + capability "Thermostat Heating Setpoint" + capability "Thermostat Mode" + capability "Thermostat Operating State" + + command "heatingSetpointUp" + command "heatingSetpointDown" + command "setThermostatMode" + command "setHeatingSetpoint" + command "setBoostLength" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"lighting") { + tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ + attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ + // Celsius Color Range + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ]} + tileAttribute ("hiveHeating", key: "SECONDARY_CONTROL") { + attributeState "hiveHeating", label:'${currentValue}' + } + } + + valueTile("thermostat_small", "device.temperature", width: 4, height: 4) { + state "default", label:'${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ] + } + + standardTile("thermostat_main", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state "idle", label:'${currentValue}', icon: "st.Weather.weather2" + state "heating", label:'${currentValue}', icon: "st.Weather.weather2", backgroundColor:"#EC6E05" + } + + controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { + state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" + } + + controlTile("boostSliderControl", "device.boostLength", "slider", height: 2, width: 4, inactiveLabel: false, range:"(10..240)") { + state "setBoostLength", label:'Set boost length to', action:"setBoostLength" + } + + standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" + } + + standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" + } + + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { + state "default", label:'${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ] + } + + standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" + state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#EC6E05" + } + + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("auto", label: "SCHEDULED", icon:"st.Office.office7") + state("off", label: "OFF", icon:"st.thermostat.heating-cooling-off") + state("heat", label: "MANUAL", icon:"st.Weather.weather2") + state("emergency heat", label: "BOOST", icon:"st.Health & Wellness.health7") + } + + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'${currentValue}', action:"emergencyHeat") + } + + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" + } + + standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"heat", label:'Manual', icon:"st.Weather.weather2" + } + + standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"off", icon:"st.thermostat.heating-cooling-off" + } + + main(["thermostat_main"]) + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "boostSliderControl", "refresh"]) + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'temperature' attribute + // TODO: handle 'heatingSetpoint' attribute + // TODO: handle 'thermostatSetpoint' attribute + // TODO: handle 'thermostatMode' attribute + // TODO: handle 'thermostatOperatingState' attribute +} + +def installed() { + log.debug "Executing 'installed'" + // execute handlerMethod every 10 minutes. + state.boostLength = 60 +} + +// handle commands +def setHeatingSetpoint(temp) { + log.debug "Executing 'setHeatingSetpoint with temp $temp'" + def latestThermostatMode = device.latestState('thermostatMode') + + if (temp < 5) { + temp = 5 + } + if (temp > 32) { + temp = 32 + } + + + //if thermostat is off, set to manual + if (latestThermostatMode.stringValue == 'off') { + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + } + + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} + def args = [ + nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] + ] + + def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + else { + runIn(4, refresh) + } +} + +def setBoostLength(minutes) { + log.debug "Executing 'setBoostLength with length $minutes minutes'" + if (minutes < 10) { + minutes = 10 + } + if (minutes > 240) { + minutes = 240 + } + state.boostLength = minutes + sendEvent("name":"boostLength", "value": state.boostLength, displayed: true) + + def latestThermostatMode = device.latestState('thermostatMode') + + //If already in BOOST mode, send updated boost length to Hive. + if (latestThermostatMode.stringValue == 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + refresh() + } + + + +} + +def heatingSetpointUp(){ + log.debug "Executing 'heatingSetpointUp'" + int newSetpoint = device.currentValue("heatingSetpoint") + 1 + log.debug "Setting heat set point up to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def heatingSetpointDown(){ + log.debug "Executing 'heatingSetpointDown'" + int newSetpoint = device.currentValue("heatingSetpoint") - 1 + log.debug "Setting heat set point down to: ${newSetpoint}" + setHeatingSetpoint(newSetpoint) +} + +def off() { + setThermostatMode('off') +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + log.debug "Executing 'boost'" + + def latestThermostatMode = device.latestState('thermostatMode') + + //Don't do if already in BOOST mode. + if (latestThermostatMode.stringValue != 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + log.debug "Already in boost mode." + } + +} + +def auto() { + setThermostatMode('auto') +} + +def setThermostatMode(mode) { + mode = mode == 'cool' ? 'heat' : mode + log.debug "Executing 'setThermostatMode with mode $mode'" + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: false]]]] + ] + if (mode == 'off') { + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] + ] + } else if (mode == 'heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] + ] + } else if (mode == 'emergency heat') { + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":22}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: state.boostLength], targetHeatTemperature: [targetValue: "22"]]]] + ] + } + + def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + else { + mode = mode == 'range' ? 'auto' : mode + runIn(4, refresh) + } +} + +def poll() { + log.debug "Executing 'poll'" + def resp = parent.apiGET("/nodes/${device.deviceNetworkId}") + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + + data.nodes = resp.data.nodes + + //Construct status message + def statusMsg = "Currently" + + //Boost button label + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + def boostLabel = "Start\n$state.boostLength Min Boost" + + // get temperature status + def temperature = data.nodes.attributes.temperature.reportedValue[0] + def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] + temperature = String.format("%2.1f",temperature) + heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 1) + + // convert temperature reading of 1 degree to 7 as Hive app does + if (heatingSetpoint == "1.0") { + heatingSetpoint = "7.0" + } + + sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") + sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") + sendEvent(name: 'thermostatSetpoint', value: heatingSetpoint, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) + + // determine hive operating mode + def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] + def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] + + log.debug "activeHeatCoolMode: $activeHeatCoolMode" + log.debug "activeScheduleLock: $activeScheduleLock" + + def mode = 'auto' + + if (activeHeatCoolMode == "OFF") { + mode = 'off' + statusMsg = statusMsg + " set to OFF" + } + else if (activeHeatCoolMode == "BOOST") { + mode = 'emergency heat' + statusMsg = statusMsg + " set to BOOST" + def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] + boostLabel = "Boosting for \n" + boostTime + " mins" + sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") + } + else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { + mode = 'heat' + statusMsg = statusMsg + " set to MANUAL" + } + else { + statusMsg = statusMsg + " set to SCHEDULE" + } + sendEvent(name: 'thermostatMode', value: mode) + + // determine if Hive heating relay is on + def stateHeatingRelay = data.nodes.attributes.stateHeatingRelay.reportedValue[0] + + log.debug "stateHeatingRelay: $stateHeatingRelay" + + if (stateHeatingRelay == "ON") { + sendEvent(name: 'thermostatOperatingState', value: "heating") + statusMsg = statusMsg + " and is HEATING" + } + else { + sendEvent(name: 'thermostatOperatingState', value: "idle") + statusMsg = statusMsg + " and is IDLE" + } + + sendEvent("name":"hiveHeating", "value": statusMsg, displayed: false) + sendEvent("name":"boostLabel", "value": boostLabel, displayed: false) + +} + +def refresh() { + log.debug "Executing 'refresh'" + poll() +} \ No newline at end of file diff --git a/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy b/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy new file mode 100644 index 00000000000..f50f0621a11 --- /dev/null +++ b/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy @@ -0,0 +1,290 @@ +/** + * Hive Hot Water V2.0 + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 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 + * 25.02.2016 + * v2.0 BETA - Initial Release + */ + +metadata { + definition (name: "Hive Hot Water V2.0", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Actuator" + capability "Polling" + capability "Refresh" + capability "Thermostat" + capability "Thermostat Mode" + + command "setThermostatMode" + command "setBoostLength" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + + multiAttributeTile(name: "hotWaterRelay", width: 6, height: 4, type:"lighting") { + tileAttribute("device.thermostatOperatingState", key:"PRIMARY_CONTROL"){ + attributeState "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05" + attributeState "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff" + } + tileAttribute ("hiveHotWater", key: "SECONDARY_CONTROL") { + attributeState "hiveHotWater", label:'${currentValue}' + } + } + + standardTile("hotWaterRelay_main", "device.thermostatOperatingState", inactiveLabel: true, width: 3, height: 3) { + state( "heating", label:'${currentValue}', icon: "st.Bath.bath6", backgroundColor: "#EC6E05") + state( "idle", label:'${currentValue}', icon: "st.Bath.bath6", backgroundColor: "#ffffff") + } + + standardTile("hotWaterRelay_small", "device.thermostatOperatingState", inactiveLabel: true, width: 3, height: 3) { + state( "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05") + state( "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff") + } + + standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 3, height: 3) { + state("auto", label: "SCHEDULED", action:"heat", icon:"st.Bath.bath6") + state("off", label: "OFF", action:"auto", icon:"st.Bath.bath6") + state("heat", label: "ON", action:"off", icon:"st.Bath.bath6") + state("emergency heat", label: "BOOST", action:"auto", icon:"st.Bath.bath6") + } + + + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") + } + + valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'${currentValue}', action:"emergencyHeat") + } + + controlTile("boostSliderControl", "device.boostLength", "slider", height: 2, width: 4, inactiveLabel: false, range:"(10..240)") { + state "setBoostLength", label:'Set boost length to', action:"setBoostLength" + } + + standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"auto", label:'Schedule', icon:"st.Office.office7" + } + + standardTile("mode_manual", "device.mode_manual", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"heat", label:'On', icon:"st.Weather.weather2" + } + + standardTile("mode_off", "device.mode_off", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"off", icon:"st.thermostat.heating-cooling-off" + } + + main(["hotWaterRelay_main"]) + details(["hotWaterRelay", "mode_auto", "mode_manual", "mode_off", "boost", "boostSliderControl", "refresh"]) + + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'switch' attribute + // TODO: handle 'thermostatMode' attribute + +} + +def installed() { + log.debug "Executing 'installed'" + state.boostLength = 60 +} + +// handle commands +def setHeatingSetpoint(temp) { + //Not implemented +} + +def setBoostLength(minutes) { + log.debug "Executing 'setBoostLength with length $minutes minutes'" + if (minutes < 10) { + minutes = 10 + } + if (minutes > 240) { + minutes = 240 + } + state.boostLength = minutes + sendEvent("name":"boostLength", "value": state.boostLength, displayed: true) + + def latestThermostatMode = device.latestState('thermostatMode') + + //If already in BOOST mode, send updated boost length to Hive. + if (latestThermostatMode.stringValue == 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + refresh() + } +} + +def heatingSetpointUp(){ + //Not implemented +} + +def heatingSetpointDown(){ + //Not implemented +} + +def on() { + log.debug "Executing 'on'" + setThermostatMode('heat') +} + +def off() { + setThermostatMode('off') +} + +def heat() { + setThermostatMode('heat') +} + +def emergencyHeat() { + log.debug "Executing 'boost'" + + def latestThermostatMode = device.latestState('thermostatMode') + + //Don't do if already in BOOST mode. + if (latestThermostatMode.stringValue != 'emergency heat') { + setThermostatMode('emergency heat') + } + else { + log.debug "Already in boost mode." + } + + +} + +def auto() { + setThermostatMode('auto') +} + +def setThermostatMode(mode) { + mode = mode == 'cool' ? 'heat' : mode + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: false]]]] + ] + if (mode == 'off') { + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"]]]] + ] + } else if (mode == 'heat') { + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true},"targetHeatTemperature":{"targetValue":99}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true], targetHeatTemperature: [targetValue: "99"]]]] + ] + } else if (mode == 'emergency heat') { + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":99}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: state.boostLength], targetHeatTemperature: [targetValue: "99"]]]] + ] + } + + def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + else { + mode = mode == 'range' ? 'auto' : mode + runIn(3, refresh) + } +} + +def poll() { + log.debug "Executing 'poll'" + def resp = parent.apiGET("/nodes/${device.deviceNetworkId}") + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + data.nodes = resp.data.nodes + + //Construct status message + def statusMsg = "Currently" + + //Boost button label + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + def boostLabel = "Start\n$state.boostLength Min Boost" + + // determine hive hot water operating mode + def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] + def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] + + log.debug "activeHeatCoolMode: $activeHeatCoolMode" + log.debug "activeScheduleLock: $activeScheduleLock" + + def mode = 'auto' + + if (activeHeatCoolMode == "OFF") { + mode = 'off' + statusMsg = statusMsg + " set to OFF" + } + else if (activeHeatCoolMode == "BOOST") { + mode = 'emergency heat' + statusMsg = statusMsg + " set to BOOST" + def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] + boostLabel = "Boosting for \n" + boostTime + " mins" + sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") + } + else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { + mode = 'heat' + statusMsg = statusMsg + " set to ON" + } + else { + statusMsg = statusMsg + " set to SCHEDULE" + } + + sendEvent(name: 'thermostatMode', value: mode) + + // determine if Hive hot water relay is on + def stateHotWaterRelay = data.nodes.attributes.stateHotWaterRelay.reportedValue[0] + + log.debug "stateHotWaterRelay: $stateHotWaterRelay" + + if (stateHotWaterRelay == "ON") { + sendEvent(name: 'temperature', value: 99, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'heatingSetpoint', value: 99, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'thermostatOperatingState', value: "heating") + statusMsg = statusMsg + " and is HEATING" + } + else { + sendEvent(name: 'temperature', value: 0, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'heatingSetpoint', value: 0, unit: "C", state: "heat", displayed: false) + sendEvent(name: 'thermostatOperatingState', value: "idle") + statusMsg = statusMsg + " and is IDLE" + } + sendEvent("name":"hiveHotWater", "value":statusMsg, displayed: false) + sendEvent("name":"boostLabel", "value": boostLabel, displayed: false) + + +} + +def refresh() { + log.debug "Executing 'refresh'" + poll() +} \ No newline at end of file From 516a5fec5a3cfc171c6dab260d6da707a4e87dd2 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 25 Feb 2016 17:45:20 +0000 Subject: [PATCH 117/685] Minor fixes --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index cb27c2a01b9..1d33d5dc3bc 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -55,7 +55,7 @@ def firstPage() { } if (state.hiveAccessToken != null && state.hiveAccessToken != '') { section { - paragraph "You have successfully connected to Hive. Press 'Done' and your devices should be discovered." + paragraph "You have successfully connected to Hive. Your devices should be discovered and selectable below." } section("Select a device...") { input "selectedHeating", "enum", required:false, title:"Select Hive Heating Devices \n(${state.hiveHeatingDevices.size() ?: 0} found)", multiple:true, options:state.hiveHeatingDevices From e4f4de9ef6433ed17b9de6e82131b9251de02351 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 25 Feb 2016 20:10:59 +0000 Subject: [PATCH 118/685] Update device names from Hive API --- .../hive-connect.src/hive-connect.groovy | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 1d33d5dc3bc..7ad17ea5d0e 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -122,22 +122,56 @@ def updateDevices() { def devices = devicesList() state.hiveHeatingDevices = [:] state.hiveHotWaterDevices = [:] + def selectors = [] devices.each { device -> + selectors.add("${device.id}") if (device.attributes.capabilities != null && device.attributes.capabilities.reportedValue[0] == "THERMOSTAT") { def parentNode = devices.find { d -> d.id == device.parentNodeId } if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == false) && (device.attributes.temperature != null)) { def value = "${parentNode.name} Hive Heating" def key = device.id state.hiveHeatingDevices["${key}"] = value + + //Update names of devices + def childDevice = getChildDevice("${device.id}") + if (childDevice) { + //Update name of device if different. + if(childDevice.name != parentNode.name + " Hive Heating") { + childDevice.name = parentNode.name + " Hive Heating" + childDevice.label = parentNode.name + " Hive Heating" + log.debug "Device's name has changed." + } + } } else if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == true)) { def value = "${parentNode.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 != parentNode.name + " Hive Hot Water") { + childDevice.name = parentNode.name + " Hive Hot Water" + childDevice.label = parentNode.name + " Hive Hot Water" + log.debug "Device's name has changed." + } + } } // Support for more Hive Device Types can be added here in the future. } - } + } + + //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 (physicalgraph.exception.NotFoundException e) { + log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") + } + } } def addHeating() { From 05ccaa30cef5fa6e6bd548fc0a0e4db4f4c7f2cd Mon Sep 17 00:00:00 2001 From: alyc100 Date: Thu, 25 Feb 2016 22:13:05 +0000 Subject: [PATCH 119/685] Removed auto update device name --- .../hive-connect.src/hive-connect.groovy | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 7ad17ea5d0e..fee0b115a08 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -132,32 +132,12 @@ def updateDevices() { def key = device.id state.hiveHeatingDevices["${key}"] = value - //Update names of devices - def childDevice = getChildDevice("${device.id}") - if (childDevice) { - //Update name of device if different. - if(childDevice.name != parentNode.name + " Hive Heating") { - childDevice.name = parentNode.name + " Hive Heating" - childDevice.label = parentNode.name + " Hive Heating" - log.debug "Device's name has changed." - } - } } else if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == true)) { def value = "${parentNode.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 != parentNode.name + " Hive Hot Water") { - childDevice.name = parentNode.name + " Hive Hot Water" - childDevice.label = parentNode.name + " Hive Hot Water" - log.debug "Device's name has changed." - } - } } // Support for more Hive Device Types can be added here in the future. } @@ -379,4 +359,4 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { options.logObject.warn "Connection timed out, not much we can do here" return options.errorReturn } -} \ No newline at end of file +} From d2d09393905170dffb44698bcef9afa6611d56f9 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 25 Feb 2016 22:53:50 +0000 Subject: [PATCH 120/685] Update logo for Hive Connect smart app --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index fee0b115a08..30b7240dc4d 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -22,8 +22,8 @@ definition( namespace: "alyc100", author: "Alex Lee Yuk Cheung", description: "Connect your Hive devices to SmartThings.", - iconUrl: "https://www.hivehome.com/attachment/46/download/hive_logo_low_res.png", - iconX2Url: "https://www.hivehome.com/attachment/46/download/hive_logo_low_res.png", + iconUrl: "https://scontent-lhr3-1.xx.fbcdn.net/hphotos-xfp1/v/t1.0-9/10457773_334250273417145_3395772416845089626_n.png?oh=fb96583154582526d24409597fbccc18&oe=57248CDA", + iconX2Url: "https://scontent-lhr3-1.xx.fbcdn.net/hphotos-xfp1/v/t1.0-9/10457773_334250273417145_3395772416845089626_n.png?oh=fb96583154582526d24409597fbccc18&oe=57248CDA", singleInstance: true ) @@ -359,4 +359,4 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { options.logObject.warn "Connection timed out, not much we can do here" return options.errorReturn } -} +} \ No newline at end of file From 14f26ce81a76c5eef9de460f8b7afc30c1aff27c Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 26 Feb 2016 00:03:05 +0000 Subject: [PATCH 121/685] Reinstated automatic device name sync --- .../hive-connect.src/hive-connect.groovy | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 30b7240dc4d..fbeae65a0d0 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -132,12 +132,32 @@ def updateDevices() { def key = device.id state.hiveHeatingDevices["${key}"] = value + //Update names of devices with Hive + def childDevice = getChildDevice("${device.id}") + if (childDevice) { + //Update name of device if different. + if(childDevice.name != parentNode.name + " Hive Heating") { + childDevice.name = parentNode.name + " Hive Heating" + log.debug "Device's name has changed." + } + } + } else if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == true)) { def value = "${parentNode.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 != parentNode.name + " Hive Hot Water") { + childDevice.name = parentNode.name + " Hive Hot Water" + log.debug "Device's name has changed." + } + } + } // Support for more Hive Device Types can be added here in the future. } From 411399862a2a8373eea72d3ef68a49d5b792e22d Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 26 Feb 2016 10:12:33 +0000 Subject: [PATCH 122/685] Neato Botvac Device initial release --- .../neato-botvac-connected.groovy | 355 ++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy diff --git a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy new file mode 100644 index 00000000000..5004f0b9658 --- /dev/null +++ b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy @@ -0,0 +1,355 @@ +/** + * Neato BotVac Connected + * + * Smartthings Devicetype + * + * Copyright 2015 Sidney Johnson + * If you like this device, please support the developer via PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XKDRYZ3RUNR9Y + * + * 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: 1.0 - Initial Version + * Version: 1.1 - Fixed installed and updated functions + * Version: 1.2 - Added error tracking, and better icons, link state + * Version: 1.3 - Better error tracking, error correction and the ability to change the default port (thx to sidhartha100), fix a bug that prevented auto population of deviceNetworkId + * Version: 1.4 - Added bin status and code clean up + * Version: 1.4.1 - Added poll on inilizition, better error handling, inproved clean button + * Version: 1.4.2 - Fixed poll not working, changed battery icon, and code clean up + * + * Modified 2016 by Alex Lee Yuk Cheung for Neato BotVac Compatibility. Requires https://github.com/kangguru/botvac web server running. + * Neato Version: 1.0 - Initial Version + */ +import groovy.json.JsonSlurper + +metadata { + definition (name: "Neato Botvac Connected", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Battery" + capability "Polling" + capability "Refresh" + capability "Switch" + + command "refresh" + command "dock" + command "enableSchedule" + command "disableSchedule" + + attribute "network","string" + attribute "bin","string" + } + + preferences { + input("ip", "text", title: "IP Address", description: "Your Botvac API Server Address", required: true, displayDuringSetup: true) + input("port", "number", title: "Port Number", description: "YourBotvac API Server Port Number", required: true, displayDuringSetup: true) + } + + tiles(scale: 2) { + multiAttributeTile(name: "clean", width: 6, height: 4, type:"lighting") { + tileAttribute("device.switch", key:"PRIMARY_CONTROL", canChangeBackground: true){ + attributeState("off", label: 'STOPPED', action: "on", icon: "st.Appliances.appliances13", backgroundColor: "#cccccc") + attributeState("on", label: 'CLEANING', action: "off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821") + } + tileAttribute ("statusMsg", key: "SECONDARY_CONTROL") { + attributeState "statusMsg", label:'${currentValue}' + } + } + + valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { + state "battery", label:'${currentValue}% battery', unit:"" + } + standardTile("charging", "device.charging", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'Charging', icon: "st.samsung.da.RC_ic_charge", backgroundColor: "#E5E500") + state ("false", label:'', icon: "st.samsung.da.RC_ic_charge") + } + standardTile("bin", "device.bin", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") + state ("empty", label:'Bin Empty', icon: "st.Office.office10",backgroundColor: "#79b821") + state ("full", label:'Bin Full', icon: "st.Office.office10", backgroundColor: "#bc2323") + } + /*standardTile("clean", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { + state("on", label: 'dock', action: "switch.off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821", nextState:"off") + state("off", label: 'clean', action: "switch.on", icon: "st.Appliances.appliances13", backgroundColor: "#79b821", nextState:"on") + }*/ + standardTile("network", "device.network", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") + state ("Connected", label:'Link Good', icon: "st.Health & Wellness.health9", backgroundColor: "#79b821") + state ("Not Connected", label:'Link Bad', icon: "st.Health & Wellness.health9", backgroundColor: "#bc2323") + } + standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { + state("default", label:'refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon") + } + standardTile("status", "device.status", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("unknown", label:'${currentValue}', icon: "st.unknown.unknown.unknown") + state ("cleaning", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#79b821") + state ("ready", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#79b821") + state ("error", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#bc2323") + state ("paused", label:'${currentValue}', icon: "st.Appliances.appliances13") + } + + standardTile("dockStatus", "device.dockStatus", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("docked", label:'Docked', icon: "st.Transportation.transportation13", backgroundColor: "#79b821") + state ("dockable", label:'Send To Dock', action: "dock", icon: "st.Transportation.transportation2", backgroundColor: "#E5E500") + state ("undocked", label:'Undocked', icon: "st.Transportation.transportation13", backgroundColor: "#E5E500") + } + + standardTile("scheduled", "device.scheduled", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'Sched', action: "disableSchedule", icon:"st.Office.office7") + state ("false", label:'Manual', action: "enableSchedule", icon: "st.Appliances.appliances13") + } + + standardTile("dockHasBeenSeen", "device.dockHasBeenSeen", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'SEEN', backgroundColor: "#79b821", icon:"st.Transportation.transportation13") + state ("false", label:'SEARCHING', backgroundColor: "#E5E500", icon:"st.Transportation.transportation13") + } + + main("clean") + details(["clean","status","battery","bin","network", "dockStatus", "charging", "dockHasBeenSeen", "scheduled", "refresh"]) + } +} + +def parse(String description) { + def map + def headerString + def bodyString + def slurper + def result + def statusMsg = "" + def binFullFlag = false + map = stringToMap(description) + headerString = new String(map.headers.decodeBase64()) + if (headerString.contains("200 OK")) { + + sendEvent(name: 'network', value: "Connected" as String) + bodyString = new String(map.body.decodeBase64()) + slurper = new JsonSlurper() + result = slurper.parseText(bodyString) + log.debug result + + if (result.containsKey("state")) { + //state 1 - Ready to clean + //state 2 - Cleaning + //state 3 - Paused + //state 4 - Error + switch (result.state) { + case "1": + sendEvent(name: 'status', value: "ready" as String) + sendEvent(name: 'switch', value: "off" as String) + statusMsg += 'READY TO CLEAN' + break; + case "2": + sendEvent(name: 'status', value: "cleaning" as String) + sendEvent(name: 'switch', value: "on" as String) + statusMsg += 'CURRENTLY CLEANING' + break; + case "3": + sendEvent(name: 'status', value: "paused" as String) + sendEvent(name: 'switch', value: "off" as String) + statusMsg += 'PAUSED' + break; + case "4": + sendEvent(name: 'status', value: "error" as String) + statusMsg += 'HAS A PROBLEM' + break; + default: + sendEvent(name: 'status', value: "unknown" as String) + statusMsg += 'UNKNOWN' + break; + } + } + if (result.containsKey("error")) { + switch (result.error) { + case "ui_alert_dust_bin_full": + binFullFlag = true + break; + case "ui_error_picked_up": + statusMsg += ' - Picked Up!' + break; + case "ui_error_brush_stuck": + statusMsg += ' - Brush Stuck!' + break; + case "ui_error_stuck": + statusMsg += ' - I\'m Stuck!' + break; + case "ui_error_dust_bin_missing": + statusMsg += ' - Dust Bin Is Missing!' + break + //More error detail messages here as discovered + + } + } + if (result.containsKey("details")) { + if (result.details.isDocked) { + sendEvent(name: 'dockStatus', value: "docked" as String) + } else { + sendEvent(name: 'dockStatus', value: "undocked" as String) + } + sendEvent(name: 'charging', value: result.details.isCharging as String) + sendEvent(name: 'scheduled', value: result.details.isScheduleEnabled as String) + sendEvent(name: 'dockHasBeenSeen', value: result.details.dockHasBeenSeen as String) + sendEvent(name: 'battery', value: result.details.charge as Integer) + } + if (result.containsKey("availableCommands")) { + if (result.availableCommands.goToBase) { + sendEvent(name: 'dockStatus', value: "dockable") + } + } + + if (binFullFlag) { + sendEvent(name: 'bin', value: "full" as String) + } else { + sendEvent(name: 'bin', value: "empty" as String) + } + + } + else { + sendEvent(name: 'status', value: "error" as String) + sendEvent(name: 'network', value: "Not Connected" as String) + statusMsg += 'Not Connected To Neato' + log.debug headerString + } + sendEvent(name: 'statusMsg', value: statusMsg, displayed: false) +} + +// handle commands + +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + initialize() +} + +def initialize() { + log.info "Neato Botvac Connected ${textVersion()} ${textCopyright()}" + poll() +} + +def on() { + log.debug "Executing 'on'" + if (device.latestState('status').stringValue == 'paused') { + api('resume') + } + else { + api('on') + } +} + +def off() { + log.debug "Executing 'off'" + api('pause') +} + +def dock() { + log.debug "Executing 'dock'" + if (device.latestState('status').stringValue == 'paused') { + api('dock') + } +} + +def enableSchedule() { + log.debug "Executing 'enableSchedule'" + api('auto') +} + +def disableSchedule() { + log.debug "Executing 'disableSchedule'" + api('manual') +} + +def poll() { + log.debug "Executing 'poll'" + + if (device.deviceNetworkId != null) { + api('refresh') + } + else { + sendEvent(name: 'status', value: "error" as String) + sendEvent(name: 'network', value: "Not Connected" as String) + log.debug "DNI: Not set" + } +} + +def refresh() { + log.debug "Executing 'refresh'" + ipSetup() + api('refresh') +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + def methods = [ + 'on': [uri: "/start_cleaning", type: 'get'], + 'pause': [uri: "/pause_cleaning", type: 'get'], + 'resume': [uri: "/resume_cleaning", type: 'get'], + 'dock': [uri: "/send_to_base", type: 'get'], + 'refresh' : [uri: "/get_robot_state", type: 'get'], + 'manual': [uri: "/disable_schedule", type: 'get'], + 'auto': [uri: "/enable_schedule", type: 'get'] + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + postAction(request.uri) +} + +private postAction(uri){ + ipSetup() + + def hubAction = new physicalgraph.device.HubAction( + method: "GET", + path: uri, + headers: headers + ) //, delayAction(1000), refresh()] + log.debug("Executing hubAction on " + getHostAddress()) + log.debug hubAction + hubAction +} + +def ipSetup() { + def hosthex + def porthex + if (settings.ip) { + hosthex = convertIPtoHex(settings.ip) + } + if (settings.port) { + porthex = convertPortToHex(settings.port) + } + if (settings.ip && settings.port) { + device.deviceNetworkId = "$hosthex:$porthex" + } +} + +private getHostAddress() { + return "${ip}:${port}" +} + +private String convertIPtoHex(ip) { + String hexip = ip.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() + return hexip +} +private String convertPortToHex(port) { + String hexport = port.toString().format( '%04x', port.toInteger() ) + return hexport +} +private delayAction(long time) { + new physicalgraph.device.HubAction("delay $time") +} + +private def textVersion() { + def text = "Version 1.4.2. Neato Version 1.0" +} + +private def textCopyright() { + def text = "Copyright © 2015 Sidjohn1. Modified by Alex Lee Yuk Cheung" +} \ No newline at end of file From f85702a3efe035a4f0b9cb5471b11ea39b54215c Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 26 Feb 2016 10:13:07 +0000 Subject: [PATCH 123/685] Thinking Cleanerer Neato Botvac Edition Initial Release --- ...king-cleanerer-neato-botvac-edition.groovy | 264 ++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy diff --git a/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy b/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy new file mode 100644 index 00000000000..46d205eac37 --- /dev/null +++ b/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy @@ -0,0 +1,264 @@ +/** + * Thinking Cleanerer Neato Botvac Edition + * Smartthings SmartApp + * Copyright 2014 Sidney Johnson + * If you like this app, please support the developer via PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XKDRYZ3RUNR9Y + * + * 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: 1.0 - Initial Version + * Version: 1.2 - Added error push notifcation, and better icons + * Version: 1.3 - New interface, better polling, and logging. Added sms notifcations + * Version: 1.4 - Added bin full notifcations + * Version: 1.4.1 - Fixed SMS send issue + * Version: 1.4.2 - Fixed No such property: currentSwitch issue, added poll on initialize, locked to single instance + * Version: 1.5 - More robust polling, auto set Smart Home Monitor + * + * Modified by Alex Lee Yuk Cheung for Neato Botvac Devices + * Version: 1.0 - Initial Version - Added Auto Dock On Pause, Force Clean Option, Changes to Polling behaviour + * Version: 1.1 - Fix to setting SHM state when error has occured whilst cleaning + */ + +definition( + name: "Thinking Cleanerer Neato Botvac Edition", + namespace: "sidjohn1", + author: "Sidney Johnson", + description: "Handles polling and job notification for Neato Botvac", + category: "Convenience", + iconUrl: "http://cdn.device-icons.smartthings.com/Appliances/appliances13-icn.png", + iconX2Url: "http://cdn.device-icons.smartthings.com/Appliances/appliances13-icn@2x.png", + iconX3Url: "http://cdn.device-icons.smartthings.com/Appliances/appliances13-icn@3x.png", + singleInstance: true) + +preferences { + page name:"pageInfo" +} + +def pageInfo() { + return dynamicPage(name: "pageInfo", title: "Thinking Cleanerer Neato Botvac Edition", install: true, uninstall: true) { + section("About") { + paragraph "Thinking Cleaner(Botvac) smartapp for Smartthings. This app monitors your Botvac and provides job notifacation" + paragraph "${textVersion()}\n${textCopyright()}" + } + section("Select Botvac(s) to monitor..."){ + input "switch1", "device.NeatoBotvacConnected", title: "Monitored Botvac", required: true, multiple: true, submitOnChange: true + } + def roombaList = "" + settings.switch1.each() { + try { + roombaList += "$it.displayName is $it.currentStatus. Battery is $it.currentBattery%\n" + } + catch (e) { + log.trace "Error checking status." + log.trace e + } + } + if (roombaList) { + section("Botvac Status:") { + paragraph roombaList.trim() + } + } + section("Preferences"){ + input "forceClean", "bool", title: "Force clean after elapsed time?", required: false, defaultValue: false, submitOnChange: true + if (forceClean) { + input ("forceCleanDelay", "number", title: "Number of days before force clean (in days)", required: false, defaultValue: 7) + } + input "autoDock", "bool", title: "Auto dock Botvac after pause?", required: false, defaultValue: true, submitOnChange: true + if (autoDock) { + input ("autoDockDelay", "number", title: "Auto dock delay after pause (in seconds)", required: false, defaultValue: 60) + } + } + section(hideable: true, hidden: true, "Auto Smart Home Monitor..."){ + input "autoSHM", "bool", title: "Auto Set Smart Home Monitor?", required: true, multiple: true, defaultValue: false, submitOnChange: true + paragraph"Auto Set Smart Home Monitor to Arm(Stay) when cleaning and Arm(Away) when done." + } + section(hideable: true, hidden: true, "Event Notifications..."){ + input "sendPush", "bool", title: "Send as Push?", required: false, defaultValue: true + input "sendSMS", "phone", title: "Send as SMS?", required: false, defaultValue: null + input "sendRoombaOn", "bool", title: "Notify when on?", required: false, defaultValue: false + input "sendRoombaOff", "bool", title: "Notify when off?", required: false, defaultValue: false + input "sendRoombaError", "bool", title: "Notify on error?", required: false, defaultValue: true + input "sendRoombaBin", "bool", title: "Notify on full bin?", required: false, defaultValue: true + } + } +} + +def installed() { + log.trace "Installed with settings: ${settings}" + runEvery5Minutes('pollOn') + state.lastClean = [:] + initialize() +} + +def updated() { + log.trace "Updated with settings: ${settings}" + unschedule() + unsubscribe() + runEvery5Minutes('pollOn') + initialize() +} + +def initialize() { + log.info "Thinking Cleanerer Neaeto Botvac Edition ${textVersion()} ${textCopyright()}" + subscribe(switch1, "status.cleaning", eventHandler) + subscribe(switch1, "status.ready", eventHandler) + subscribe(switch1, "status.error", eventHandler) + subscribe(switch1, "status.paused", eventHandler) + subscribe(switch1, "bin.full", eventHandler) +} + +def uninstalled() { + unschedule() +} + +def eventHandler(evt) { + def msg + if (evt.value == "paused") { + log.trace "Setting auto dock for ${evt.displayName}" + //If configured, set to dock automatically after one minute. + if (autoDock == true) { + runIn(autoDockDelay, scheduleAutoDock) + } + } + else if (evt.value == "error") { + unschedule() + runEvery5Minutes('pollOn') + sendEvent(linkText:app.label, name:"${evt.displayName}", value:"error",descriptionText:"${evt.displayName} has an error", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "${evt.displayName} has an error" + msg = "${evt.displayName} has an error" + if (sendRoombaError == true) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } + } + } + else if (evt.value == "cleaning") { + unschedule() + //Increase poll interval during cleaning + schedule("0 0/1 * * * ?", pollOn) + if (state.lastClean == null) { + state.lastClean = [:] + } + //Record last cleaning time for device + state.lastClean[evt.displayName] = now() + sendEvent(linkText:app.label, name:"${evt.displayName}", value:"on",descriptionText:"${evt.displayName} is on", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "${evt.displayName} is on" + msg = "${evt.displayName} is on" + if (sendRoombaOn == true) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } + } + if (settings.autoSHM.contains('true') ) { + if (location.currentState("alarmSystemStatus")?.value == "away") { + sendEvent(linkText:app.label, name:"Smart Home Monitor", value:"stay",descriptionText:"Smart Home Monitor was set to stay", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "Smart Home Monitor is set to stay" + sendLocationEvent(name: "alarmSystemStatus", value: "stay") + state.autoSHMchange = "y" + sendPush("Smart Home Monitor is set to stay as ${evt.displayName} is on") + } + } + } + else if (evt.value == "full") { + unschedule('pollOn') + runEvery5Minutes('pollOn') + sendEvent(linkText:app.label, name:"${evt.displayName}", value:"bin full",descriptionText:"${evt.displayName} bin is full", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "${evt.displayName} bin is full" + msg = "${evt.displayName} bin is full" + if (sendRoombaBin == true) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } + } + } + else if (evt.value == "ready") { + unschedule() + runEvery5Minutes('pollOn') + sendEvent(linkText:app.label, name:"${evt.displayName}", value:"off",descriptionText:"${evt.displayName} is off", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "${evt.displayName} is off" + msg = "${evt.displayName} is off" + if (sendRoombaOff == true) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } + } + } +} + +def scheduleAutoDock() { + settings.switch1.each() { + if (it.latestState('status').stringValue == 'paused') { + it.dock() + } + } +} + +def pollOn() { + log.debug "Executing 'pollOn'" + settings.switch1.each() { + state.pollState = now() + it.poll() + //Force on if last clean was a long time ago + if (it.currentSwitch == "off" && forceClean && state.lastClean != null && state.lastClean[it.displayName] != null) { + def t = now() - state.lastClean[it.displayName] + log.debug "$it.displayName last cleaned at " + state.lastClean[it.displayName] + ". $t milliseconds has elapsed since." + if (t > (forceCleanDelay * 86400000)) { + log.debug "Force clean activated as $t milliseconds has elapsed" + sendPush(it.displayName + " has not cleaned for " + forceCleanDelay + " days. Forcing a clean.") + it.on() + } + } + } + + def activeCleaners = false + + for (cleaner in switch1) { + if (cleaner.latestState('status').stringValue == 'cleaning') { + activeCleaners = true + } + } + + if (!activeCleaners) { + if (settings.autoSHM.contains('true') ) { + if (location.currentState("alarmSystemStatus")?.value == "stay" && state.autoSHMchange == "y"){ + sendEvent(linkText:app.label, name:"Smart Home Monitor", value:"away",descriptionText:"Smart Home Monitor was set back to away", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "Smart Home Monitor is set back to away" + sendLocationEvent(name: "alarmSystemStatus", value: "away") + state.autoSHMchange = "n" + sendPush("Smart Home Monitor is set to away as all cleaners are off") + } + } + } + + //If SHM is disarmed because of external event, then disable auto SHM mode + if (location.currentState("alarmSystemStatus")?.value == "off") { + state.autoSHMchange = "n" + } +} + +private def textVersion() { + def text = "Version 1.5" +} + +private def textCopyright() { + def text = "Copyright © 2015 Sidjohn1. Modified by Alex Lee Yuk Cheung" +} \ No newline at end of file From c6b886916d4b422afd1ceade8c7b8719614f5629 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 26 Feb 2016 14:46:52 +0000 Subject: [PATCH 124/685] v2.0.1 BETA - Fix bug for accounts that do not have capabilities attribute against thermostat nodes. --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index fbeae65a0d0..49b85d012f2 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -15,6 +15,7 @@ * 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 nodes. * */ definition( @@ -125,7 +126,7 @@ def updateDevices() { def selectors = [] devices.each { device -> selectors.add("${device.id}") - if (device.attributes.capabilities != null && device.attributes.capabilities.reportedValue[0] == "THERMOSTAT") { + if (device.attributes.activeHeatCoolMode != null) { def parentNode = devices.find { d -> d.id == device.parentNodeId } if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == false) && (device.attributes.temperature != null)) { def value = "${parentNode.name} Hive Heating" @@ -170,6 +171,8 @@ def updateDevices() { deleteChildDevice(it.deviceNetworkId) } catch (physicalgraph.exception.NotFoundException e) { log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") + } catch (physicalgraph.exception.ConflictException ce) { + log.info("Device ${it.deviceNetworkId} in use. Please manually delete.") } } } From 1c500a215706dcc63503e732a687dcd937932ed2 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 27 Feb 2016 23:05:40 +0000 Subject: [PATCH 125/685] Neato Version: 1.0.1 - Improved Botvac connection detection --- .../neato-botvac-connected.groovy | 715 +++++++++--------- 1 file changed, 361 insertions(+), 354 deletions(-) diff --git a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy index 5004f0b9658..240f8be3562 100644 --- a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy +++ b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy @@ -1,355 +1,362 @@ -/** - * Neato BotVac Connected - * - * Smartthings Devicetype - * - * Copyright 2015 Sidney Johnson - * If you like this device, please support the developer via PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XKDRYZ3RUNR9Y - * - * 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: 1.0 - Initial Version - * Version: 1.1 - Fixed installed and updated functions - * Version: 1.2 - Added error tracking, and better icons, link state - * Version: 1.3 - Better error tracking, error correction and the ability to change the default port (thx to sidhartha100), fix a bug that prevented auto population of deviceNetworkId - * Version: 1.4 - Added bin status and code clean up - * Version: 1.4.1 - Added poll on inilizition, better error handling, inproved clean button - * Version: 1.4.2 - Fixed poll not working, changed battery icon, and code clean up - * - * Modified 2016 by Alex Lee Yuk Cheung for Neato BotVac Compatibility. Requires https://github.com/kangguru/botvac web server running. - * Neato Version: 1.0 - Initial Version - */ -import groovy.json.JsonSlurper - -metadata { - definition (name: "Neato Botvac Connected", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { - capability "Battery" - capability "Polling" - capability "Refresh" - capability "Switch" - - command "refresh" - command "dock" - command "enableSchedule" - command "disableSchedule" - - attribute "network","string" - attribute "bin","string" - } - - preferences { - input("ip", "text", title: "IP Address", description: "Your Botvac API Server Address", required: true, displayDuringSetup: true) - input("port", "number", title: "Port Number", description: "YourBotvac API Server Port Number", required: true, displayDuringSetup: true) - } - - tiles(scale: 2) { - multiAttributeTile(name: "clean", width: 6, height: 4, type:"lighting") { - tileAttribute("device.switch", key:"PRIMARY_CONTROL", canChangeBackground: true){ - attributeState("off", label: 'STOPPED', action: "on", icon: "st.Appliances.appliances13", backgroundColor: "#cccccc") - attributeState("on", label: 'CLEANING', action: "off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821") - } - tileAttribute ("statusMsg", key: "SECONDARY_CONTROL") { - attributeState "statusMsg", label:'${currentValue}' - } - } - - valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { - state "battery", label:'${currentValue}% battery', unit:"" - } - standardTile("charging", "device.charging", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("true", label:'Charging', icon: "st.samsung.da.RC_ic_charge", backgroundColor: "#E5E500") - state ("false", label:'', icon: "st.samsung.da.RC_ic_charge") - } - standardTile("bin", "device.bin", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") - state ("empty", label:'Bin Empty', icon: "st.Office.office10",backgroundColor: "#79b821") - state ("full", label:'Bin Full', icon: "st.Office.office10", backgroundColor: "#bc2323") - } - /*standardTile("clean", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { - state("on", label: 'dock', action: "switch.off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821", nextState:"off") - state("off", label: 'clean', action: "switch.on", icon: "st.Appliances.appliances13", backgroundColor: "#79b821", nextState:"on") - }*/ - standardTile("network", "device.network", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") - state ("Connected", label:'Link Good', icon: "st.Health & Wellness.health9", backgroundColor: "#79b821") - state ("Not Connected", label:'Link Bad', icon: "st.Health & Wellness.health9", backgroundColor: "#bc2323") - } - standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { - state("default", label:'refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon") - } - standardTile("status", "device.status", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("unknown", label:'${currentValue}', icon: "st.unknown.unknown.unknown") - state ("cleaning", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#79b821") - state ("ready", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#79b821") - state ("error", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#bc2323") - state ("paused", label:'${currentValue}', icon: "st.Appliances.appliances13") - } - - standardTile("dockStatus", "device.dockStatus", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("docked", label:'Docked', icon: "st.Transportation.transportation13", backgroundColor: "#79b821") - state ("dockable", label:'Send To Dock', action: "dock", icon: "st.Transportation.transportation2", backgroundColor: "#E5E500") - state ("undocked", label:'Undocked', icon: "st.Transportation.transportation13", backgroundColor: "#E5E500") - } - - standardTile("scheduled", "device.scheduled", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("true", label:'Sched', action: "disableSchedule", icon:"st.Office.office7") - state ("false", label:'Manual', action: "enableSchedule", icon: "st.Appliances.appliances13") - } - - standardTile("dockHasBeenSeen", "device.dockHasBeenSeen", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("true", label:'SEEN', backgroundColor: "#79b821", icon:"st.Transportation.transportation13") - state ("false", label:'SEARCHING', backgroundColor: "#E5E500", icon:"st.Transportation.transportation13") - } - - main("clean") - details(["clean","status","battery","bin","network", "dockStatus", "charging", "dockHasBeenSeen", "scheduled", "refresh"]) - } -} - -def parse(String description) { - def map - def headerString - def bodyString - def slurper - def result - def statusMsg = "" - def binFullFlag = false - map = stringToMap(description) - headerString = new String(map.headers.decodeBase64()) - if (headerString.contains("200 OK")) { - - sendEvent(name: 'network', value: "Connected" as String) - bodyString = new String(map.body.decodeBase64()) - slurper = new JsonSlurper() - result = slurper.parseText(bodyString) - log.debug result - - if (result.containsKey("state")) { - //state 1 - Ready to clean - //state 2 - Cleaning - //state 3 - Paused - //state 4 - Error - switch (result.state) { - case "1": - sendEvent(name: 'status', value: "ready" as String) - sendEvent(name: 'switch', value: "off" as String) - statusMsg += 'READY TO CLEAN' - break; - case "2": - sendEvent(name: 'status', value: "cleaning" as String) - sendEvent(name: 'switch', value: "on" as String) - statusMsg += 'CURRENTLY CLEANING' - break; - case "3": - sendEvent(name: 'status', value: "paused" as String) - sendEvent(name: 'switch', value: "off" as String) - statusMsg += 'PAUSED' - break; - case "4": - sendEvent(name: 'status', value: "error" as String) - statusMsg += 'HAS A PROBLEM' - break; - default: - sendEvent(name: 'status', value: "unknown" as String) - statusMsg += 'UNKNOWN' - break; - } - } - if (result.containsKey("error")) { - switch (result.error) { - case "ui_alert_dust_bin_full": - binFullFlag = true - break; - case "ui_error_picked_up": - statusMsg += ' - Picked Up!' - break; - case "ui_error_brush_stuck": - statusMsg += ' - Brush Stuck!' - break; - case "ui_error_stuck": - statusMsg += ' - I\'m Stuck!' - break; - case "ui_error_dust_bin_missing": - statusMsg += ' - Dust Bin Is Missing!' - break - //More error detail messages here as discovered - - } - } - if (result.containsKey("details")) { - if (result.details.isDocked) { - sendEvent(name: 'dockStatus', value: "docked" as String) - } else { - sendEvent(name: 'dockStatus', value: "undocked" as String) - } - sendEvent(name: 'charging', value: result.details.isCharging as String) - sendEvent(name: 'scheduled', value: result.details.isScheduleEnabled as String) - sendEvent(name: 'dockHasBeenSeen', value: result.details.dockHasBeenSeen as String) - sendEvent(name: 'battery', value: result.details.charge as Integer) - } - if (result.containsKey("availableCommands")) { - if (result.availableCommands.goToBase) { - sendEvent(name: 'dockStatus', value: "dockable") - } - } - - if (binFullFlag) { - sendEvent(name: 'bin', value: "full" as String) - } else { - sendEvent(name: 'bin', value: "empty" as String) - } - - } - else { - sendEvent(name: 'status', value: "error" as String) - sendEvent(name: 'network', value: "Not Connected" as String) - statusMsg += 'Not Connected To Neato' - log.debug headerString - } - sendEvent(name: 'statusMsg', value: statusMsg, displayed: false) -} - -// handle commands - -def installed() { - log.debug "Installed with settings: ${settings}" - initialize() -} - -def updated() { - log.debug "Updated with settings: ${settings}" - initialize() -} - -def initialize() { - log.info "Neato Botvac Connected ${textVersion()} ${textCopyright()}" - poll() -} - -def on() { - log.debug "Executing 'on'" - if (device.latestState('status').stringValue == 'paused') { - api('resume') - } - else { - api('on') - } -} - -def off() { - log.debug "Executing 'off'" - api('pause') -} - -def dock() { - log.debug "Executing 'dock'" - if (device.latestState('status').stringValue == 'paused') { - api('dock') - } -} - -def enableSchedule() { - log.debug "Executing 'enableSchedule'" - api('auto') -} - -def disableSchedule() { - log.debug "Executing 'disableSchedule'" - api('manual') -} - -def poll() { - log.debug "Executing 'poll'" - - if (device.deviceNetworkId != null) { - api('refresh') - } - else { - sendEvent(name: 'status', value: "error" as String) - sendEvent(name: 'network', value: "Not Connected" as String) - log.debug "DNI: Not set" - } -} - -def refresh() { - log.debug "Executing 'refresh'" - ipSetup() - api('refresh') -} - -def api(method, args = [], success = {}) { - log.debug "Executing 'api'" - - def methods = [ - 'on': [uri: "/start_cleaning", type: 'get'], - 'pause': [uri: "/pause_cleaning", type: 'get'], - 'resume': [uri: "/resume_cleaning", type: 'get'], - 'dock': [uri: "/send_to_base", type: 'get'], - 'refresh' : [uri: "/get_robot_state", type: 'get'], - 'manual': [uri: "/disable_schedule", type: 'get'], - 'auto': [uri: "/enable_schedule", type: 'get'] - ] - - def request = methods.getAt(method) - - log.debug "Starting $method : $args" - postAction(request.uri) -} - -private postAction(uri){ - ipSetup() - - def hubAction = new physicalgraph.device.HubAction( - method: "GET", - path: uri, - headers: headers - ) //, delayAction(1000), refresh()] - log.debug("Executing hubAction on " + getHostAddress()) - log.debug hubAction - hubAction -} - -def ipSetup() { - def hosthex - def porthex - if (settings.ip) { - hosthex = convertIPtoHex(settings.ip) - } - if (settings.port) { - porthex = convertPortToHex(settings.port) - } - if (settings.ip && settings.port) { - device.deviceNetworkId = "$hosthex:$porthex" - } -} - -private getHostAddress() { - return "${ip}:${port}" -} - -private String convertIPtoHex(ip) { - String hexip = ip.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() - return hexip -} -private String convertPortToHex(port) { - String hexport = port.toString().format( '%04x', port.toInteger() ) - return hexport -} -private delayAction(long time) { - new physicalgraph.device.HubAction("delay $time") -} - -private def textVersion() { - def text = "Version 1.4.2. Neato Version 1.0" -} - -private def textCopyright() { - def text = "Copyright © 2015 Sidjohn1. Modified by Alex Lee Yuk Cheung" +/** + * Neato BotVac Connected + * + * Smartthings Devicetype + * + * Copyright 2015 Sidney Johnson + * If you like this device, please support the developer via PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XKDRYZ3RUNR9Y + * + * 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: 1.0 - Initial Version + * Version: 1.1 - Fixed installed and updated functions + * Version: 1.2 - Added error tracking, and better icons, link state + * Version: 1.3 - Better error tracking, error correction and the ability to change the default port (thx to sidhartha100), fix a bug that prevented auto population of deviceNetworkId + * Version: 1.4 - Added bin status and code clean up + * Version: 1.4.1 - Added poll on inilizition, better error handling, inproved clean button + * Version: 1.4.2 - Fixed poll not working, changed battery icon, and code clean up + * + * Modified 2016 by Alex Lee Yuk Cheung for Neato BotVac Compatibility. Requires https://github.com/kangguru/botvac web server running. + * Neato Version: 1.0 - Initial Version + * Neato Version: 1.0.1 - Improved Botvac connection detection + */ +import groovy.json.JsonSlurper + +metadata { + definition (name: "Neato Botvac Connected", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Battery" + capability "Polling" + capability "Refresh" + capability "Switch" + + command "refresh" + command "dock" + command "enableSchedule" + command "disableSchedule" + + attribute "network","string" + attribute "bin","string" + } + + preferences { + input("ip", "text", title: "IP Address", description: "Your Botvac API Server Address", required: true, displayDuringSetup: true) + input("port", "number", title: "Port Number", description: "YourBotvac API Server Port Number", required: true, displayDuringSetup: true) + } + + tiles(scale: 2) { + multiAttributeTile(name: "clean", width: 6, height: 4, type:"lighting") { + tileAttribute("device.switch", key:"PRIMARY_CONTROL", canChangeBackground: true){ + attributeState("off", label: 'STOPPED', action: "on", icon: "st.Appliances.appliances13", backgroundColor: "#cccccc") + attributeState("on", label: 'CLEANING', action: "off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821") + } + tileAttribute ("statusMsg", key: "SECONDARY_CONTROL") { + attributeState "statusMsg", label:'${currentValue}' + } + } + + valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { + state "battery", label:'${currentValue}% battery', unit:"" + } + standardTile("charging", "device.charging", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'Charging', icon: "st.samsung.da.RC_ic_charge", backgroundColor: "#E5E500") + state ("false", label:'', icon: "st.samsung.da.RC_ic_charge") + } + standardTile("bin", "device.bin", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") + state ("empty", label:'Bin Empty', icon: "st.Office.office10",backgroundColor: "#79b821") + state ("full", label:'Bin Full', icon: "st.Office.office10", backgroundColor: "#bc2323") + } + /*standardTile("clean", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { + state("on", label: 'dock', action: "switch.off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821", nextState:"off") + state("off", label: 'clean', action: "switch.on", icon: "st.Appliances.appliances13", backgroundColor: "#79b821", nextState:"on") + }*/ + standardTile("network", "device.network", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") + state ("Connected", label:'Link Good', icon: "st.Health & Wellness.health9", backgroundColor: "#79b821") + state ("Not Connected", label:'Link Bad', icon: "st.Health & Wellness.health9", backgroundColor: "#bc2323") + } + standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { + state("default", label:'refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon") + } + standardTile("status", "device.status", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("unknown", label:'${currentValue}', icon: "st.unknown.unknown.unknown") + state ("cleaning", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#79b821") + state ("ready", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#79b821") + state ("error", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#bc2323") + state ("paused", label:'${currentValue}', icon: "st.Appliances.appliances13") + } + + standardTile("dockStatus", "device.dockStatus", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("docked", label:'Docked', icon: "st.Transportation.transportation13", backgroundColor: "#79b821") + state ("dockable", label:'Send To Dock', action: "dock", icon: "st.Transportation.transportation2", backgroundColor: "#E5E500") + state ("undocked", label:'Undocked', icon: "st.Transportation.transportation13", backgroundColor: "#E5E500") + } + + standardTile("scheduled", "device.scheduled", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'Sched', action: "disableSchedule", icon:"st.Office.office7") + state ("false", label:'Manual', action: "enableSchedule", icon: "st.Appliances.appliances13") + } + + standardTile("dockHasBeenSeen", "device.dockHasBeenSeen", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'SEEN', backgroundColor: "#79b821", icon:"st.Transportation.transportation13") + state ("false", label:'SEARCHING', backgroundColor: "#E5E500", icon:"st.Transportation.transportation13") + } + + main("clean") + details(["clean","status","battery","bin","network", "dockStatus", "charging", "dockHasBeenSeen", "scheduled", "refresh"]) + } +} + +def parse(String description) { + def map + def headerString + def bodyString + def slurper + def result + def statusMsg = "" + def binFullFlag = false + unschedule('setOffline') + map = stringToMap(description) + headerString = new String(map.headers.decodeBase64()) + if (headerString.contains("200 OK")) { + + sendEvent(name: 'network', value: "Connected" as String) + bodyString = new String(map.body.decodeBase64()) + slurper = new JsonSlurper() + result = slurper.parseText(bodyString) + log.debug result + + if (result.containsKey("state")) { + //state 1 - Ready to clean + //state 2 - Cleaning + //state 3 - Paused + //state 4 - Error + switch (result.state) { + case "1": + sendEvent(name: 'status', value: "ready" as String) + sendEvent(name: 'switch', value: "off" as String) + statusMsg += 'READY TO CLEAN' + break; + case "2": + sendEvent(name: 'status', value: "cleaning" as String) + sendEvent(name: 'switch', value: "on" as String) + statusMsg += 'CURRENTLY CLEANING' + break; + case "3": + sendEvent(name: 'status', value: "paused" as String) + sendEvent(name: 'switch', value: "off" as String) + statusMsg += 'PAUSED' + break; + case "4": + sendEvent(name: 'status', value: "error" as String) + statusMsg += 'HAS A PROBLEM' + break; + default: + sendEvent(name: 'status', value: "unknown" as String) + statusMsg += 'UNKNOWN' + break; + } + } + if (result.containsKey("error")) { + switch (result.error) { + case "ui_alert_dust_bin_full": + binFullFlag = true + break; + case "ui_error_picked_up": + statusMsg += ' - Picked Up!' + break; + case "ui_error_brush_stuck": + statusMsg += ' - Brush Stuck!' + break; + case "ui_error_stuck": + statusMsg += ' - I\'m Stuck!' + break; + case "ui_error_dust_bin_missing": + statusMsg += ' - Dust Bin Is Missing!' + break + //More error detail messages here as discovered + + } + } + if (result.containsKey("details")) { + if (result.details.isDocked) { + sendEvent(name: 'dockStatus', value: "docked" as String) + } else { + sendEvent(name: 'dockStatus', value: "undocked" as String) + } + sendEvent(name: 'charging', value: result.details.isCharging as String) + sendEvent(name: 'scheduled', value: result.details.isScheduleEnabled as String) + sendEvent(name: 'dockHasBeenSeen', value: result.details.dockHasBeenSeen as String) + sendEvent(name: 'battery', value: result.details.charge as Integer) + } + if (result.containsKey("availableCommands")) { + if (result.availableCommands.goToBase) { + sendEvent(name: 'dockStatus', value: "dockable") + } + } + + if (binFullFlag) { + sendEvent(name: 'bin', value: "full" as String) + } else { + sendEvent(name: 'bin', value: "empty" as String) + } + + } + else { + setOffline() + sendEvent(name: 'status', value: "error" as String) + statusMsg += 'Not Connected To Neato' + log.debug headerString + } + sendEvent(name: 'statusMsg', value: statusMsg, displayed: false) +} + +// handle commands + +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + initialize() +} + +def initialize() { + log.info "Neato Botvac Connected ${textVersion()} ${textCopyright()}" + poll() +} + +def on() { + log.debug "Executing 'on'" + if (device.latestState('status').stringValue == 'paused') { + api('resume') + } + else { + api('on') + } +} + +def off() { + log.debug "Executing 'off'" + api('pause') +} + +def dock() { + log.debug "Executing 'dock'" + if (device.latestState('status').stringValue == 'paused') { + api('dock') + } +} + +def enableSchedule() { + log.debug "Executing 'enableSchedule'" + api('auto') +} + +def disableSchedule() { + log.debug "Executing 'disableSchedule'" + api('manual') +} + +def setOffline() { + sendEvent(name: 'network', value: "Not Connected" as String) +} + +def poll() { + log.debug "Executing 'poll'" + + if (device.deviceNetworkId != null) { + api('refresh') + } + else { + setOffline() + sendEvent(name: 'status', value: "error" as String) + log.debug "DNI: Not set" + } +} + +def refresh() { + log.debug "Executing 'refresh'" + ipSetup() + api('refresh') +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + def methods = [ + 'on': [uri: "/start_cleaning", type: 'get'], + 'pause': [uri: "/pause_cleaning", type: 'get'], + 'resume': [uri: "/resume_cleaning", type: 'get'], + 'dock': [uri: "/send_to_base", type: 'get'], + 'refresh' : [uri: "/get_robot_state", type: 'get'], + 'manual': [uri: "/disable_schedule", type: 'get'], + 'auto': [uri: "/enable_schedule", type: 'get'] + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + postAction(request.uri) +} + +private postAction(uri){ + ipSetup() + + def hubAction = new physicalgraph.device.HubAction( + method: "GET", + path: uri, + headers: headers + ) //, delayAction(1000), refresh()] + log.debug("Executing hubAction on " + getHostAddress()) + log.debug hubAction + hubAction + runIn(30, setOffline) +} + +def ipSetup() { + def hosthex + def porthex + if (settings.ip) { + hosthex = convertIPtoHex(settings.ip) + } + if (settings.port) { + porthex = convertPortToHex(settings.port) + } + if (settings.ip && settings.port) { + device.deviceNetworkId = "$hosthex:$porthex" + } +} + +private getHostAddress() { + return "${ip}:${port}" +} + +private String convertIPtoHex(ip) { + String hexip = ip.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join() + return hexip +} +private String convertPortToHex(port) { + String hexport = port.toString().format( '%04x', port.toInteger() ) + return hexport +} +private delayAction(long time) { + new physicalgraph.device.HubAction("delay $time") +} + +private def textVersion() { + def text = "Version 1.4.2. Neato Version 1.0" +} + +private def textCopyright() { + def text = "Copyright © 2015 Sidjohn1. Modified by Alex Lee Yuk Cheung" } \ No newline at end of file From 502048269f6d125fd2089de41e280b71d69f21d4 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 27 Feb 2016 23:10:15 +0000 Subject: [PATCH 126/685] Minor fixes --- .../neato-botvac-connected.src/neato-botvac-connected.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy index 240f8be3562..dea90f2cb75 100644 --- a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy +++ b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy @@ -312,6 +312,7 @@ def api(method, args = [], success = {}) { private postAction(uri){ ipSetup() + runIn(30, setOffline) def hubAction = new physicalgraph.device.HubAction( method: "GET", path: uri, @@ -320,7 +321,6 @@ private postAction(uri){ log.debug("Executing hubAction on " + getHostAddress()) log.debug hubAction hubAction - runIn(30, setOffline) } def ipSetup() { From 106661efb20f78ceadfe4a45b1568129ca20c726 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 4 Mar 2016 15:51:53 +0000 Subject: [PATCH 127/685] Neato Version: 1.0.2 - Added Please Clear My Path Error message --- .../neato-botvac-connected.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy index dea90f2cb75..16580296ae5 100644 --- a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy +++ b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy @@ -26,6 +26,7 @@ * Modified 2016 by Alex Lee Yuk Cheung for Neato BotVac Compatibility. Requires https://github.com/kangguru/botvac web server running. * Neato Version: 1.0 - Initial Version * Neato Version: 1.0.1 - Improved Botvac connection detection + * Neato Version: 1.0.2 - Added Please Clear My Path Error message */ import groovy.json.JsonSlurper @@ -53,7 +54,7 @@ metadata { tiles(scale: 2) { multiAttributeTile(name: "clean", width: 6, height: 4, type:"lighting") { tileAttribute("device.switch", key:"PRIMARY_CONTROL", canChangeBackground: true){ - attributeState("off", label: 'STOPPED', action: "on", icon: "st.Appliances.appliances13", backgroundColor: "#cccccc") + attributeState("off", label: 'STOPPED', action: "on", icon: "st.Appliances.appliances13", backgroundColor: "#ffffff") attributeState("on", label: 'CLEANING', action: "off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821") } tileAttribute ("statusMsg", key: "SECONDARY_CONTROL") { @@ -181,6 +182,9 @@ def parse(String description) { case "ui_error_dust_bin_missing": statusMsg += ' - Dust Bin Is Missing!' break + case "ui_error_navigation_falling": + statusMsg += ' - Please Clear My Path!' + break //More error detail messages here as discovered } From 93a323ad66bd0eb015e239160f401f044246b780 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 6 Mar 2016 14:37:56 +0000 Subject: [PATCH 128/685] Neato Version: 1.0.3 - Added Navigation No Progress Error message --- .../neato-botvac-connected.src/neato-botvac-connected.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy index 16580296ae5..283fa822e5d 100644 --- a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy +++ b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy @@ -27,6 +27,7 @@ * Neato Version: 1.0 - Initial Version * Neato Version: 1.0.1 - Improved Botvac connection detection * Neato Version: 1.0.2 - Added Please Clear My Path Error message + * Neato Version: 1.0.3 - Added Navigation No Progress Error message */ import groovy.json.JsonSlurper @@ -185,6 +186,10 @@ def parse(String description) { case "ui_error_navigation_falling": statusMsg += ' - Please Clear My Path!' break + case "ui_error_navigation_noprogress": + statusMsg += ' - Please Clear My Path!' + break + //More error detail messages here as discovered } From 0a1c6b0325dbd4ba88f2897b647c65911f4cc953 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 7 Mar 2016 19:38:21 +0000 Subject: [PATCH 129/685] v2.1 - Introducing button temperature control via improved thermostat multi attribute tile. --- .../hive-heating-v2-0.groovy | 187 ++++++++++++------ 1 file changed, 126 insertions(+), 61 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 3bbaeb2a919..dfd3a4e09ff 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -16,6 +16,8 @@ * VERSION HISTORY * 25.02.2016 * v2.0 BETA - Initial Release + * v2.1 - Introducing button temperature control via improved thermostat multi attribute tile. More responsive temperature control. + Improve Boost button behaviour and look. */ metadata { @@ -31,9 +33,14 @@ metadata { command "heatingSetpointUp" command "heatingSetpointDown" + command "boostTimeUp" + command "boostTimeDown" command "setThermostatMode" command "setHeatingSetpoint" + command "setTemperatureForSlider" command "setBoostLength" + command "setTemperature" + command "boostButton" } simulator { @@ -41,8 +48,8 @@ metadata { } tiles(scale: 2) { - - multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"lighting") { + + multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"thermostat") { tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ // Celsius Color Range @@ -54,9 +61,36 @@ metadata { [value: 25, color: "#d9372b"], [value: 29, color: "#b9203b"] ]} + tileAttribute ("hiveHeating", key: "SECONDARY_CONTROL") { attributeState "hiveHeating", label:'${currentValue}' } + tileAttribute("device.temperature", key: "VALUE_CONTROL") { + attributeState("default", action: "setTemperature") + } + tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { + attributeState("idle", backgroundColor:"#ffffff") + attributeState("heating", backgroundColor:"#ec6e05") + } + tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { + attributeState("off", label:'Off') + attributeState("heat", label:'Manual') + attributeState("cool", label:'Manual') + attributeState("auto", label:'Schedule') + attributeState("emergency heat", label:'Boost') + } + tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { + attributeState "default", label: '${currentValue}', backgroundColors: [ + // Celsius Color Range + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ]} + } valueTile("thermostat_small", "device.temperature", width: 4, height: 4) { @@ -77,23 +111,23 @@ metadata { state "heating", label:'${currentValue}', icon: "st.Weather.weather2", backgroundColor:"#EC6E05" } - controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 2, width: 4, inactiveLabel: false, range:"(5..32)") { - state "setHeatingSetpoint", label:'Set temperature to', action:"setHeatingSetpoint" + controlTile("heatSliderControl", "device.desiredHeatSetpoint", "slider", height: 2, width: 3, inactiveLabel: false, range:"(5..32)") { + state "setHeatingSetpoint", label:'Set temperature to', action:"setTemperatureForSlider" } controlTile("boostSliderControl", "device.boostLength", "slider", height: 2, width: 4, inactiveLabel: false, range:"(10..240)") { state "setBoostLength", label:'Set boost length to', action:"setBoostLength" } - standardTile("heatingSetpointUp", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + standardTile("heatingSetpointUp", "device.desiredHeatSetpoint", width: 1, height: 1, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" } - standardTile("heatingSetpointDown", "device.heatingSetpoint", width: 2, height: 2, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + standardTile("heatingSetpointDown", "device.desiredHeatSetpoint", width: 1, height: 1, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" } - valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2) { + valueTile("heatingSetpoint", "device.desiredHeatSetpoint", width: 2, height: 2) { state "default", label:'${currentValue}°', unit:"C", backgroundColors:[ [value: 0, color: "#50b5dd"], @@ -105,6 +139,14 @@ metadata { [value: 29, color: "#b9203b"] ] } + + standardTile("boostTimeUp", "device.boostLength", width: 1, height: 1, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointUp", label:' ', action:"boostTimeUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#ffffff" + } + + standardTile("boostTimeDown", "device.boostLength", width: 1, height: 1, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { + state "heatingSetpointDown", label:' ', action:"boostTimeDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" + } standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" @@ -122,8 +164,8 @@ metadata { state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") } - valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state("default", label:'${currentValue}', action:"emergencyHeat") + valueTile("boost", "device.boostLabel", inactiveLabel: false, decoration: "flat", width: 2, height: 2, wordwrap: true) { + state("default", label:'${currentValue}', action:"boostButton") } standardTile("mode_auto", "device.mode_auto", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { @@ -139,7 +181,10 @@ metadata { } main(["thermostat_main"]) - details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "boostSliderControl", "refresh"]) + details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpointUp", "heatingSetpoint", "boost", "boostTimeUp", "heatingSetpointDown", "boostTimeDown", "refresh"]) + + //Uncomment below for V1 tile layout + //details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpoint", "heatSliderControl", "boost", "boostSliderControl", "refresh"]) } } @@ -155,8 +200,8 @@ def parse(String description) { def installed() { log.debug "Executing 'installed'" - // execute handlerMethod every 10 minutes. state.boostLength = 60 + state.desiredHeatSetpoint = 7 } // handle commands @@ -171,17 +216,13 @@ def setHeatingSetpoint(temp) { temp = 32 } - //if thermostat is off, set to manual if (latestThermostatMode.stringValue == 'off') { def args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] ] def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) - if (resp.status != 200) { - log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") - return [] - } + } // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} @@ -190,13 +231,7 @@ def setHeatingSetpoint(temp) { ] def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) - if (resp.status != 200) { - log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") - return [] - } - else { - runIn(4, refresh) - } + runIn(4, refresh) } def setBoostLength(minutes) { @@ -209,33 +244,60 @@ def setBoostLength(minutes) { } state.boostLength = minutes sendEvent("name":"boostLength", "value": state.boostLength, displayed: true) - - def latestThermostatMode = device.latestState('thermostatMode') - - //If already in BOOST mode, send updated boost length to Hive. - if (latestThermostatMode.stringValue == 'emergency heat') { - setThermostatMode('emergency heat') - } - else { - refresh() - } - - - + refreshBoostLabel() +} + +def boostTimeUp() { + log.debug "Executing 'boostTimeUp'" + setBoostLength(state.boostLength + 10) +} + +def boostTimeDown() { + log.debug "Executing 'boostTimeDown'" + setBoostLength(state.boostLength - 10) +} + +def boostButton() { + log.debug "Executing 'boostButton'" + setThermostatMode('emergency heat') +} + +def setHeatingSetpointToDesired() { + setHeatingSetpoint(state.newSetpoint) +} + +def setNewSetPointValue(newSetPointValue) { + log.debug "Executing 'setNewSetPointValue' with value $newSetPointValue" + unschedule('setHeatingSetpointToDesired') + state.newSetpoint = newSetPointValue + state.desiredHeatSetpoint = state.newSetpoint + sendEvent("name":"desiredHeatSetpoint", "value": state.desiredHeatSetpoint, displayed: false) + log.debug "Setting heat set point up to: ${state.newSetpoint}" + runIn(5, setHeatingSetpointToDesired) } def heatingSetpointUp(){ log.debug "Executing 'heatingSetpointUp'" - int newSetpoint = device.currentValue("heatingSetpoint") + 1 - log.debug "Setting heat set point up to: ${newSetpoint}" - setHeatingSetpoint(newSetpoint) + setNewSetPointValue(getHeatTemp().toInteger() + 1) } def heatingSetpointDown(){ log.debug "Executing 'heatingSetpointDown'" - int newSetpoint = device.currentValue("heatingSetpoint") - 1 - log.debug "Setting heat set point down to: ${newSetpoint}" - setHeatingSetpoint(newSetpoint) + setNewSetPointValue(getHeatTemp().toInteger() - 1) +} + +def setTemperature(value) { + log.debug "Executing 'setTemperature with $value'" + (value == 0) ? (setNewSetPointValue(getHeatTemp().toInteger() - 1)) : (setNewSetPointValue(getHeatTemp().toInteger() + 1)) +} + +def setTemperatureForSlider(value) { + log.debug "Executing 'setTemperatureForSlider with $value'" + setNewSetPointValue(value) +} + +def getHeatTemp() { + return state.desiredHeatSetpoint } def off() { @@ -293,14 +355,17 @@ def setThermostatMode(mode) { } def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) - if (resp.status != 200) { - log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") - return [] - } - else { - mode = mode == 'range' ? 'auto' : mode - runIn(4, refresh) - } + mode = mode == 'range' ? 'auto' : mode + runIn(4, refresh) +} + +def refreshBoostLabel() { + def boostLabel = "Start $state.boostLength Min Boost" + def latestThermostatMode = device.latestState('thermostatMode') + if (latestThermostatMode.stringValue == 'emergency heat' ) { + boostLabel = "Restart $state.boostLength Min Boost" + } + sendEvent("name":"boostLabel", "value": boostLabel, displayed: false) } def poll() { @@ -314,7 +379,7 @@ def poll() { data.nodes = resp.data.nodes //Construct status message - def statusMsg = "Currently" + def statusMsg = "Mode is" //Boost button label if (state.boostLength == null || state.boostLength == '') @@ -322,7 +387,7 @@ def poll() { state.boostLength = 60 sendEvent("name":"boostLength", "value": 60, displayed: true) } - def boostLabel = "Start\n$state.boostLength Min Boost" + def boostLabel = "Start $state.boostLength Min Boost" // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] @@ -334,12 +399,14 @@ def poll() { if (heatingSetpoint == "1.0") { heatingSetpoint = "7.0" } - sendEvent(name: 'temperature', value: temperature, unit: "C", state: "heat") sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: "C", state: "heat") sendEvent(name: 'thermostatSetpoint', value: heatingSetpoint, unit: "C", state: "heat", displayed: false) sendEvent(name: 'thermostatFanMode', value: "off", displayed: false) + state.desiredHeatSetpoint = (int) Double.parseDouble(heatingSetpoint) + sendEvent("name":"desiredHeatSetpoint", "value": state.desiredHeatSetpoint, unit: "C", displayed: false) + // determine hive operating mode def activeHeatCoolMode = data.nodes.attributes.activeHeatCoolMode.reportedValue[0] def activeScheduleLock = data.nodes.attributes.activeScheduleLock.targetValue[0] @@ -351,21 +418,21 @@ def poll() { if (activeHeatCoolMode == "OFF") { mode = 'off' - statusMsg = statusMsg + " set to OFF" + statusMsg = statusMsg + " OFF" } else if (activeHeatCoolMode == "BOOST") { - mode = 'emergency heat' - statusMsg = statusMsg + " set to BOOST" + mode = 'emergency heat' def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] - boostLabel = "Boosting for \n" + boostTime + " mins" + boostLabel = "Restart $state.boostLength Min Boost" + statusMsg = "BOOSTING - " + boostTime + " mins remaining" sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") } else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { mode = 'heat' - statusMsg = statusMsg + " set to MANUAL" + statusMsg = statusMsg + " MANUAL" } else { - statusMsg = statusMsg + " set to SCHEDULE" + statusMsg = statusMsg + " SCHEDULE" } sendEvent(name: 'thermostatMode', value: mode) @@ -376,11 +443,9 @@ def poll() { if (stateHeatingRelay == "ON") { sendEvent(name: 'thermostatOperatingState', value: "heating") - statusMsg = statusMsg + " and is HEATING" } else { sendEvent(name: 'thermostatOperatingState', value: "idle") - statusMsg = statusMsg + " and is IDLE" } sendEvent("name":"hiveHeating", "value": statusMsg, displayed: false) From 98397f3982dfcbd5c09f7db033bcfdb3042809cf Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 7 Mar 2016 20:14:19 +0000 Subject: [PATCH 130/685] Minor change to Boost label --- .../hive-heating-v2-0.src/hive-heating-v2-0.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index dfd3a4e09ff..b9125822028 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -360,10 +360,10 @@ def setThermostatMode(mode) { } def refreshBoostLabel() { - def boostLabel = "Start $state.boostLength Min Boost" + def boostLabel = "Start\n$state.boostLength Min Boost" def latestThermostatMode = device.latestState('thermostatMode') if (latestThermostatMode.stringValue == 'emergency heat' ) { - boostLabel = "Restart $state.boostLength Min Boost" + boostLabel = "Restart\n$state.boostLength Min Boost" } sendEvent("name":"boostLabel", "value": boostLabel, displayed: false) } @@ -387,7 +387,7 @@ def poll() { state.boostLength = 60 sendEvent("name":"boostLength", "value": 60, displayed: true) } - def boostLabel = "Start $state.boostLength Min Boost" + def boostLabel = "Start\n$state.boostLength Min Boost" // get temperature status def temperature = data.nodes.attributes.temperature.reportedValue[0] @@ -423,7 +423,7 @@ def poll() { else if (activeHeatCoolMode == "BOOST") { mode = 'emergency heat' def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] - boostLabel = "Restart $state.boostLength Min Boost" + boostLabel = "Restart\n$state.boostLength Min Boost" statusMsg = "BOOSTING - " + boostTime + " mins remaining" sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") } From b1fa9547ccd80a8b87e960c4d0d27bd973300d6c Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 7 Mar 2016 21:17:31 +0000 Subject: [PATCH 131/685] Change main tile display --- .../alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index b9125822028..76d54103138 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -180,7 +180,7 @@ metadata { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - main(["thermostat_main"]) + main(["thermostat"]) details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpointUp", "heatingSetpoint", "boost", "boostTimeUp", "heatingSetpointDown", "boostTimeDown", "refresh"]) //Uncomment below for V1 tile layout From 1b8aaa4e18556afa5a308a5851692cfbc073a978 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 7 Mar 2016 22:23:56 +0000 Subject: [PATCH 132/685] Tweaks to temperature control response --- .../hive-heating-v2-0.groovy | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 76d54103138..d055ceb801e 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -93,24 +93,6 @@ metadata { } - valueTile("thermostat_small", "device.temperature", width: 4, height: 4) { - state "default", label:'${currentValue}°', unit:"C", - backgroundColors:[ - [value: 0, color: "#50b5dd"], - [value: 10, color: "#43a575"], - [value: 13, color: "#c5d11b"], - [value: 17, color: "#f4961a"], - [value: 20, color: "#e75928"], - [value: 25, color: "#d9372b"], - [value: 29, color: "#b9203b"] - ] - } - - standardTile("thermostat_main", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state "idle", label:'${currentValue}', icon: "st.Weather.weather2" - state "heating", label:'${currentValue}', icon: "st.Weather.weather2", backgroundColor:"#EC6E05" - } - controlTile("heatSliderControl", "device.desiredHeatSetpoint", "slider", height: 2, width: 3, inactiveLabel: false, range:"(5..32)") { state "setHeatingSetpoint", label:'Set temperature to', action:"setTemperatureForSlider" } @@ -273,7 +255,7 @@ def setNewSetPointValue(newSetPointValue) { state.desiredHeatSetpoint = state.newSetpoint sendEvent("name":"desiredHeatSetpoint", "value": state.desiredHeatSetpoint, displayed: false) log.debug "Setting heat set point up to: ${state.newSetpoint}" - runIn(5, setHeatingSetpointToDesired) + runIn(3, setHeatingSetpointToDesired) } def heatingSetpointUp(){ From 8a240b49dabd65a268eb938ca44a5aea1fbe4bb0 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 7 Mar 2016 22:27:21 +0000 Subject: [PATCH 133/685] Comments update --- .../alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index d055ceb801e..a01cdf96561 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -17,7 +17,8 @@ * 25.02.2016 * v2.0 BETA - Initial Release * v2.1 - Introducing button temperature control via improved thermostat multi attribute tile. More responsive temperature control. - Improve Boost button behaviour and look. + * Improve Boost button behaviour and look. + * v2.1.1 - Tweaks to temperature control responsiveness */ metadata { From 02bbca865155994563565eeabbbcbcc65f1e0fc8 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 8 Mar 2016 19:45:56 +0000 Subject: [PATCH 134/685] v2.1 - Improved authentication process and overhaul to UI. Added notification capability. --- .../hive-connect.src/hive-connect.groovy | 239 ++++++++++++++++-- 1 file changed, 221 insertions(+), 18 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 49b85d012f2..41cc9122abf 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -16,6 +16,7 @@ * 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 nodes. + * v2.1 - Improved authentication process and overhaul to UI. Added notification capability. * */ definition( @@ -30,6 +31,9 @@ definition( preferences { page(name:"firstPage", title:"Hive Device Setup", content:"firstPage", install: true) + page(name: "loginPAGE") + page(name: "selectDevicePAGE") + page(name: "preferencesPAGE") } def apiURL(path = '/') { return "https://api.prod.bgchprod.info:443/omnia${path}" } @@ -39,39 +43,157 @@ def firstPage() { if (username == null || username == '' || password == null || password == '') { return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { section { - input("username", "text", title: "Username", description: "Your Hive username (usually an email address)", required: true) - input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) + headerSECTION() + href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter Hive crednentials", state: authenticated()) } } } else { log.debug "next phase" - getHiveAccessToken() - updateDevices() return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { section { - input("username", "text", title: "Username", description: "Your Hive username (usually an email address)", required: true) - input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) + headerSECTION() + href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter Hive credentials", state: authenticated()) } - if (state.hiveAccessToken != null && state.hiveAccessToken != '') { - section { - paragraph "You have successfully connected to Hive. Your devices should be discovered and selectable below." - } - section("Select a device...") { - input "selectedHeating", "enum", required:false, title:"Select Hive Heating Devices \n(${state.hiveHeatingDevices.size() ?: 0} found)", multiple:true, options:state.hiveHeatingDevices - input "selectedHotWater", "enum", required:false, title:"Select Hive Hot Water Devices \n(${state.hiveHotWaterDevices.size() ?: 0} found)", multiple:true, options:state.hiveHotWaterDevices - - } + if (stateTokenPresent()) { + section ("Choose your devices:") { + href("selectDevicePAGE", title: null, description: devicesSelected() ? "Devices: " + getDevicesSelectedString() : "Tap to select devices", state: devicesSelected()) + } + section ("Notifications:") { + href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure notifications", state: preferencesSelected()) + } } else { section { - paragraph "There was a problem connecting to Hive. Check your user credential and error logs in SmartThings web console." + paragraph "There was a problem connecting to Hive. Check your user credentials and error logs in SmartThings web console.\n\n${state.loginerrors}" } } } } } +def headerSECTION() { + return paragraph (image: "https://scontent-lhr3-1.xx.fbcdn.net/hphotos-xfp1/v/t1.0-9/10457773_334250273417145_3395772416845089626_n.png?oh=fb96583154582526d24409597fbccc18&oe=57248CDA", + "Hive (Connect)\nVersion: 2.1\nBuild: 142008032016") +} + +def stateTokenPresent() { + return state.hiveAccessToken != null && state.hiveAccessToken != '' +} + +def authenticated() { + return (state.hiveAccessToken != null && state.hiveAccessToken != '') ? "complete" : null +} + +def devicesSelected() { + return (getChildDevices().size > 0) ? "complete" : null +} + +def preferencesSelected() { + return (sendPush || sendSMS != null) && (maxtemp != null || mintemp != null || sendBoost || sendOff || sendManual || sendSchedule) ? "complete" : null +} + +def getDevicesSelectedString() { + def listString = "" + getChildDevices().each { childDevice -> + if (listString == "") { + listString += childDevice.name + } + else { + listString += "\n" + childDevice.name + } + } + return listString +} + +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 (listString != "") listString = listString.substring(0, listString.length() - 2) + return listString + + +} + +def loginPAGE() { + if (username == null || username == '' || password == null || password == '') { + return dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) { + section { headerSECTION() } + section { paragraph "Enter your Hive credentials below to enable SmartThings and Hive integration." } + section("Hive Credentials:") { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) + } + } + } + else { + getHiveAccessToken() + dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) { + section { headerSECTION() } + section { paragraph "Enter your Hive credentials below to enable SmartThings and Hive integration." } + section("Hive Credentials:") { + input("username", "text", title: "Username", description: "Your Hive username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) + } + + if (stateTokenPresent()) { + section { + paragraph "You have successfully connected to Hive. Click 'Done' to select your Hive devices." + } + } + else { + section { + paragraph "There was a problem connecting to Hive. Check your user credentials 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://www.hivehome.com/assets/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://www.hivehome.com/assets/thermostat-frame-6c75d5394d102f52cb8cf73704855446.png", required:false, title:"Select Hive Hot Water Devices \n(${state.hiveHotWaterDevices.size() ?: 0} found)", multiple:true, options:state.hiveHotWaterDevices + + } + } +} + +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) + } + + } +} + + + // App lifecycle hooks def installed() { @@ -86,6 +208,7 @@ def installed() { // called after settings are changed def updated() { log.debug "updated" + unsubscribe() initialize() unschedule('refreshDevices') runEvery10Minutes('refreshDevices') @@ -113,8 +236,85 @@ def initialize() { addHotWater() runIn(10, 'refreshDevices') // Asynchronously refresh devices so we don't block + + //subscribe to events for notifications + getChildDevices().each { childDevice -> + if (childDevice.typeName == "Hive Heating V2.0" || childDevice.typeName == "Hive Hot Water V2.0") { + subscribe(childDevice, "thermostatMode", modeHandler) + } + if (childDevice.typeName == "Hive Heating V2.0") { + subscribe(childDevice, "temperature", tempHandler) + } + } + state.maxNotificationSent = false + state.minNotificationSent = false + } +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) + } +} + +def generateNotification(msg) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } +} def updateDevices() { if (!state.devices) { @@ -322,7 +522,6 @@ def getHiveAccessToken() { httpPostJson(params) {response -> log.debug "Request was successful, $response.status" log.debug response.headers - state.hiveAccessToken = response.data state.cookie = response?.headers?.'Set-Cookie'?.split(";")?.getAt(0) log.debug "Adding cookie to collection: $cookie" @@ -332,9 +531,13 @@ def getHiveAccessToken() { state.hiveAccessToken = response.data.sessions[0].id // set the expiration to 5 minutes - state.hiveAccessToken_expires_at = new Date().getTime() + 300000; + state.hiveAccessToken_expires_at = new Date().getTime() + 300000 + state.loginerrors = null } } catch (groovyx.net.http.HttpResponseException e) { + state.hiveAccessToken = null + state.hiveAccessToken_expires_at = null + state.loginerrors = "Error: ${e.response.status}: ${e.response.data}" logResponse(e.response) return e.response } From bbb40fb6e5965a29affc3c75aef615026fa7d753 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 8 Mar 2016 20:48:22 +0000 Subject: [PATCH 135/685] Minor icon fixes to Hive Heating v2.0 --- .../alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index a01cdf96561..aae19aa648a 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -132,8 +132,8 @@ metadata { } standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { - state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon" - state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#EC6E05" + state "idle", label:'${currentValue}', icon: "st.Weather.weather2" + state "heating", label:'${currentValue}', icon: "st.Weather.weather2", backgroundColor:"#EC6E05" } standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { From 59f4998415455e1330cf39f102f066a7921fafdd Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 8 Mar 2016 20:56:38 +0000 Subject: [PATCH 136/685] Tweaks to main display --- .../hive-heating-v2-0.src/hive-heating-v2-0.groovy | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index aae19aa648a..ec6b2dab348 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -52,16 +52,8 @@ metadata { multiAttributeTile(name: "thermostat", width: 6, height: 4, type:"thermostat") { tileAttribute("device.temperature", key:"PRIMARY_CONTROL", canChangeBackground: true){ - attributeState "default", label: '${currentValue}°', unit:"C", backgroundColors: [ - // Celsius Color Range - [value: 0, color: "#50b5dd"], - [value: 10, color: "#43a575"], - [value: 13, color: "#c5d11b"], - [value: 17, color: "#f4961a"], - [value: 20, color: "#e75928"], - [value: 25, color: "#d9372b"], - [value: 29, color: "#b9203b"] - ]} + attributeState "default", label: '${currentValue}°', unit:"C", backgroundColor:"#ec6e05" + } tileAttribute ("hiveHeating", key: "SECONDARY_CONTROL") { attributeState "hiveHeating", label:'${currentValue}' @@ -163,7 +155,7 @@ metadata { state "default", action:"off", icon:"st.thermostat.heating-cooling-off" } - main(["thermostat"]) + main(["thermostatOperatingState"]) details(["thermostat", "mode_auto", "mode_manual", "mode_off", "heatingSetpointUp", "heatingSetpoint", "boost", "boostTimeUp", "heatingSetpointDown", "boostTimeDown", "refresh"]) //Uncomment below for V1 tile layout From 0f6f7a82471beb845f35d32c779d4d279a4ccc83 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 8 Mar 2016 20:58:16 +0000 Subject: [PATCH 137/685] v2.1.2 - Minor tweaks to main display --- .../alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index ec6b2dab348..2f3803c7c68 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -19,6 +19,7 @@ * v2.1 - Introducing button temperature control via improved thermostat multi attribute tile. More responsive temperature control. * Improve Boost button behaviour and look. * v2.1.1 - Tweaks to temperature control responsiveness + * v2.1.2 - Minor tweaks to main display */ metadata { From c73fd9a4e19013ad47be7355829242f82f22fa96 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 14:41:22 +0000 Subject: [PATCH 138/685] v2.1.1 - Bug fix when initially selecting devices for the first time. --- .../hive-connect.src/hive-connect.groovy | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 41cc9122abf..82af794e9c6 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -17,6 +17,7 @@ * v2.0 BETA - New Hive Connect App * v2.0.1 BETA - Fix bug for accounts that do not have capabilities attribute against thermostat nodes. * 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. * */ definition( @@ -86,7 +87,7 @@ def authenticated() { } def devicesSelected() { - return (getChildDevices().size > 0) ? "complete" : null + return (selectedHeating || selectedHotWater) ? "complete" : null } def preferencesSelected() { @@ -95,12 +96,20 @@ def preferencesSelected() { def getDevicesSelectedString() { def listString = "" - getChildDevices().each { childDevice -> + selectedHeating.each { childDevice -> if (listString == "") { - listString += childDevice.name + listString += state.hiveHeatingDevices[childDevice] } else { - listString += "\n" + childDevice.name + listString += "\n" + state.hiveHeatingDevices[childDevice] + } + } + selectedHotWater.each { childDevice -> + if (listString == "") { + listString += state.hiveHotWaterDevices[childDevice] + } + else { + listString += "\n" + state.hiveHotWaterDevices[childDevice] } } return listString From d528ee4236c564d97f355bae7b036a11d44bc1a0 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 14:58:39 +0000 Subject: [PATCH 139/685] Add unsubscribe call before removing devices --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 82af794e9c6..36ba8da04c6 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -230,6 +230,7 @@ def uninstalled() { } private removeChildDevices(devices) { + unsubscribe() devices.each { deleteChildDevice(it.deviceNetworkId) // 'it' is default } @@ -377,6 +378,7 @@ def updateDevices() { getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}") try { + unsubscribe(it) deleteChildDevice(it.deviceNetworkId) } catch (physicalgraph.exception.NotFoundException e) { log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") From c0966cb7f3786e8594eaf12b7391ae25f8bb679e Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 15:02:40 +0000 Subject: [PATCH 140/685] Fix deprecation --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 36ba8da04c6..dd86497392c 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -230,7 +230,6 @@ def uninstalled() { } private removeChildDevices(devices) { - unsubscribe() devices.each { deleteChildDevice(it.deviceNetworkId) // 'it' is default } From 99a64b252f0e2ef46a8dfc3fc3e6527c0382620f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 15:05:57 +0000 Subject: [PATCH 141/685] Back out unsubscribe commands --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index dd86497392c..82af794e9c6 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -377,7 +377,6 @@ def updateDevices() { getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}") try { - unsubscribe(it) deleteChildDevice(it.deviceNetworkId) } catch (physicalgraph.exception.NotFoundException e) { log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") From 57945a7db231812e92b5ae772574483ba6102c2e Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 15:37:35 +0000 Subject: [PATCH 142/685] Update app header and subscription framework --- .../hive-connect.src/hive-connect.groovy | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 82af794e9c6..7cc0d64897a 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -75,7 +75,7 @@ def firstPage() { def headerSECTION() { return paragraph (image: "https://scontent-lhr3-1.xx.fbcdn.net/hphotos-xfp1/v/t1.0-9/10457773_334250273417145_3395772416845089626_n.png?oh=fb96583154582526d24409597fbccc18&oe=57248CDA", - "Hive (Connect)\nVersion: 2.1\nBuild: 142008032016") + "Hive (Connect)\nVersion: 2.1.1\nBuild: 153009032016") } def stateTokenPresent() { @@ -246,14 +246,16 @@ def initialize() { runIn(10, 'refreshDevices') // Asynchronously refresh devices so we don't block - //subscribe to events for notifications - getChildDevices().each { childDevice -> - if (childDevice.typeName == "Hive Heating V2.0" || childDevice.typeName == "Hive Hot Water V2.0") { - subscribe(childDevice, "thermostatMode", modeHandler) - } - if (childDevice.typeName == "Hive Heating V2.0") { - subscribe(childDevice, "temperature", tempHandler) - } + //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) + } + if (childDevice.typeName == "Hive Heating V2.0") { + subscribe(childDevice, "temperature", tempHandler) + } + } } state.maxNotificationSent = false state.minNotificationSent = false From 2866161dd17d110cb090bf1cd4be5a061f25d9bc Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 16:31:06 +0000 Subject: [PATCH 143/685] v2.0 - New OVO Connect App --- .../ovo-energy-connect.groovy | 394 ++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy diff --git a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy new file mode 100644 index 00000000000..0c284716e7e --- /dev/null +++ b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy @@ -0,0 +1,394 @@ +/** + * OVO Energy (Connect) + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 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 + * 09.03.2016 + * v2.0 - New OVO Connect App + * + */ +definition( + name: "OVO Energy (Connect)", + namespace: "alyc100", + author: "Alex Lee Yuk Cheung", + description: "Connect your OVO Energy Account to SmartThings. (Requires OVO Smart Gateway)", + iconUrl: "http://a1.mzstatic.com/eu/r30/Purple69/v4/a8/75/b1/a875b13e-a6f1-fe8d-8063-6f36517fc272/icon175x175.jpeg", + iconX2Url: "http://a1.mzstatic.com/eu/r30/Purple69/v4/a8/75/b1/a875b13e-a6f1-fe8d-8063-6f36517fc272/icon175x175.jpeg", + singleInstance: true +) + +preferences { + page(name:"firstPage", title:"OVO Account Setup", content:"firstPage", install: true) + page(name: "loginPAGE") + page(name: "selectDevicePAGE") + page(name: "accountDetailsPAGE") +} + +def firstPage() { + log.debug "firstPage" + if (username == null || username == '' || password == null || password == '') { + return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + section { + headerSECTION() + href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter OVO Energy account crednentials", state: authenticated()) + } + } + } + else + { + log.debug "next phase" + return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + section { + headerSECTION() + href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter OVO Energy account crednentials", state: authenticated()) + } + if (stateTokenPresent()) { + section ("Choose your Smart Meters:") { + href("selectDevicePAGE", title: null, description: devicesSelected() ? "Devices: " + getDevicesSelectedString() : "Tap to select smart meters", state: devicesSelected()) + } + section ("Account Details:") { + href("accountDetailsPAGE", title: null, description: "Tap to view OVO Energy Account Details") + } + } else { + section { + paragraph "There was a problem connecting to OVO Energy. Check your user credentials and error logs in SmartThings web console.\n\n${state.loginerrors}" + } + } + } + } +} + +def headerSECTION() { + return paragraph (image: "http://a1.mzstatic.com/eu/r30/Purple69/v4/a8/75/b1/a875b13e-a6f1-fe8d-8063-6f36517fc272/icon175x175.jpeg", + "OVO Energy (Connect)\nVersion: 2.0\nBuild: 103009032016") +} + +def stateTokenPresent() { + return state.ovoAccessToken != null && state.ovoAccessToken != '' +} + +def authenticated() { + return (state.ovoAccessToken != null && state.ovoAccessToken != '') ? "complete" : null +} + +def devicesSelected() { + return (selectedMeters) ? "complete" : null +} + +def getDevicesSelectedString() { + def listString = "" + selectedMeters.each { childDevice -> + if (listString == "") { + listString += state.smartMeterDevices[childDevice] + } + else { + listString += "\n" + state.smartMeterDevices[childDevice] + } + } + return listString +} + +def loginPAGE() { + if (username == null || username == '' || password == null || password == '') { + return dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) { + section { headerSECTION() } + section { paragraph "Enter your OVO Energy account credentials below to enable SmartThings and OVO Energy integration." } + section("OVO Energy Credentials:") { + input("username", "text", title: "Username", description: "Your OVO username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your OVO password", required: true, submitOnChange: true) + } + } + } + else { + getOVOAccessToken() + dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) { + section { headerSECTION() } + section { paragraph "Enter your OVO Energy account credentials below to enable SmartThings and OVO Energy integration." } + section("OVO Energy Credentials:") { + input("username", "text", title: "Username", description: "Your OVO Energy username (usually an email address)", required: true) + input("password", "password", title: "Password", description: "Your OVO Energy password", required: true, submitOnChange: true) + } + + if (stateTokenPresent()) { + section { + paragraph "You have successfully connected to OVO Energy. Click 'Done' to select your OVO Smart Meter devices." + } + } + else { + section { + paragraph "There was a problem connecting to OVO Energy. Check your user credentials 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 "selectedMeters", "enum", image: "https://www.ovoenergy.com/binaries/content/gallery/ovowebsitessuite/images/ovo-answers/ihd-screens-15.png/ihd-screens-15.png/ovowebsitessuite%3Acarousel", required:false, title:"Select Smart Meter Devices \n(${state.smartMeterDevices.size() ?: 0} found)", multiple:true, options:state.smartMeterDevices + } + } +} + +def accountDetailsPAGE() { + def accountData = updateAccountDetails() + dynamicPage(name: "accountDetailsPAGE", title: "OVO Energy Account Details", uninstall: false, install: false) { + section("Account Holder") { + paragraph "Account ID:\n${accountData.id}\n\nName:\n${accountData.accountHolder}\n\nAddress:\n${accountData.homeAddress.line1}\n${accountData.homeAddress.line2}\n${accountData.homeAddress.town}\n${accountData.homeAddress.county}\n${accountData.homeAddress.postcode}" + } + section("Balance") { + paragraph "${accountData.balance.amount} ${accountData.balance.currency}" + } + section("Direct Debit") { + paragraph "${accountData.directDebit.payment.amount} ${accountData.directDebit.payment.currency}\nNext Payment Date: ${accountData.directDebit.nextPaymentDate}" + } + } +} + +// App lifecycle hooks + +def installed() { + log.debug "installed" + initialize() + // Check for new devices every 3 hours + runEvery3Hours('updateDevices') + // execute handlerMethod every 10 minutes. + schedule("0 0/1 * * * ?", refreshDevices) +} + +// called after settings are changed +def updated() { + log.debug "updated" + unsubscribe() + initialize() + unschedule('refreshDevices') + schedule("0 0/1 * * * ?", refreshDevices) +} + +def uninstalled() { + log.info("Uninstalling, removing child devices...") + unschedule() + removeChildDevices(getChildDevices()) +} + +private removeChildDevices(devices) { + devices.each { + deleteChildDevice(it.deviceNetworkId) // 'it' is default + } +} + +// called after Done is hit after selecting a Location +def initialize() { + log.debug "initialize" + if (selectedMeters) + addMeters() + + runIn(10, 'refreshDevices') // Asynchronously refresh devices so we don't block + +} + +def updateDevices() { + log.debug "Executing 'updateDevices'" + if (!state.devices) { + state.devices = [:] + } + def devices = devicesList() + state.smartMeterDevices = [:] + def selectors = [] + devices.each { device -> + if (device.mpan != null) { + selectors.add("${device.mpan}") + def value + value = (device.utilityType == "GAS") ? "OVO Gas Smart Meter" : "OVO Electricity Smart Meter" + def key = device.mpan + state.smartMeterDevices["${key}"] = value + } + } + log.debug selectors + //Remove devices if does not exist on the OVO platform + getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { + log.info("Deleting ${it.deviceNetworkId}") + try { + deleteChildDevice(it.deviceNetworkId) + } catch (physicalgraph.exception.NotFoundException e) { + log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") + } catch (physicalgraph.exception.ConflictException ce) { + log.info("Device ${it.deviceNetworkId} in use. Please manually delete.") + } + } +} + +def addMeters() { + updateDevices() + + selectedMeters.each { device -> + + def childDevice = getChildDevice("${device}") + + if (!childDevice) { + log.info("Adding Smart Meter device ${device}: ${state.smartMeterDevices[device]}") + + def data = [ + name: state.smartMeterDevices[device], + label: state.smartMeterDevices[device], + ] + childDevice = addChildDevice(app.namespace, "OVO Energy Meter V2.0", "$device", null, data) + childDevice.refresh() + + log.debug "Created ${state.smartMeterDevices[device]} with id: ${device}" + } else { + log.debug "found ${state.smartMeterDevices[device]} with id ${device} already exists" + } + + } +} + +def refreshDevices() { + log.info("Refreshing all devices...") + getChildDevices().each { device -> + device.refresh() + } +} + +def devicesList() { + logErrors([]) { + def resp = apiGET("https://paym.ovoenergy.com/api/paym/accounts") + if (resp.status == 200) { + return resp.data.consumers[0] + } else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + return [] + } + } +} + +def updateAccountDetails() { + logErrors([]) { + def resp = apiGET("https://paym.ovoenergy.com/api/paym/accounts") + if (resp.status == 200) { + return resp.data[0] + } else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + return [] + } + } +} + +def apiGET(path, body = [:]) { + try { + if(!isLoggedIn()) { + log.debug "Need to login" + getOVOAccessToken() + } + log.debug("Beginning API GET: ${path}, ${apiRequestHeaders()}") + + httpGet(uri: path, contentType: 'application/json', headers: apiRequestHeaders()) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +def getOVOAccessToken() { + try { + def params = [ + uri: 'https://my.ovoenergy.com/api/auth/login', + contentType: 'application/json;charset=UTF-8', + headers: [ + 'Accept': 'application/json, text/plain, */*', + 'Content-Type': 'application/json;charset=UTF-8', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36', + 'Origin': 'https://my.ovoenergy.com' + ], + body: [ + username: settings.username, + password: settings.password, + ] + ] + + 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 "sessionid: ${response.data.token}" + + state.ovoAccessToken = response.data.token + // set the expiration to 5 minutes + state.ovoAccessToken_expires_at = new Date().getTime() + 600000 + state.loginerrors = null + } + + } catch (groovyx.net.http.HttpResponseException e) { + state.ovoAccessToken = null + state.ovoAccessToken_expires_at = null + state.loginerrors = "Error: ${e.response.status}: ${e.response.data}" + logResponse(e.response) + return e.response + } +} + +Map apiRequestHeaders() { + return [ + 'Cookie': "${state.cookie}", + 'Accept': 'application/json, text/plain, */*', + 'Content-Type': 'application/json;charset=UTF-8', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36', + 'Origin': 'https://my.ovoenergy.com', + 'Authorization': "${state.ovoAccessToken}" + ] +} + +def isLoggedIn() { + log.debug "Calling isLoggedIn()" + log.debug "isLoggedIn state $state.ovoAccessToken" + if(!state.ovoAccessToken) { + log.debug "No state.ovoAccessToken" + return false + } + + def now = new Date().getTime(); + return state.ovoAccessToken_expires_at > now +} + +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) { + options.logObject.error("got error: ${e}, body: ${e.getResponse().getData()}") + if (e.statusCode == 401) { // token is expired + state.remove("ovoAccessToken") + options.logObject.warn "Access token is not valid" + } + return options.errorReturn + } catch (java.net.SocketTimeoutException e) { + options.logObject.warn "Connection timed out, not much we can do here" + return options.errorReturn + } +} \ No newline at end of file From 060d1eca0354e3d28af4a81a3d6adb01098c966f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 16:31:44 +0000 Subject: [PATCH 144/685] v2.0 - Initial V2.0 Release with OVO Energy (Connect) app --- .../ovo-energy-meter-v2-0.groovy | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy new file mode 100644 index 00000000000..1b87db5351d --- /dev/null +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -0,0 +1,183 @@ +/** + * OVO Energy Meter V2.0 + * + * Copyright 2015 Alex Lee Yuk Cheung + * + * 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 + * 09.03.2016 + * v2.0 - Initial V2.0 Release with OVO Energy (Connect) app + */ + +metadata { + definition (name: "OVO Energy Meter V2.0", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Polling" + capability "Power Meter" + capability "Refresh" + } + + tiles(scale: 2) { + multiAttributeTile(name:"power", type:"generic", width:6, height:4, canChangeIcon: true) { + tileAttribute("device.power", key: "PRIMARY_CONTROL") { + attributeState "default", label: '${currentValue} W', icon:"st.Appliances.appliances17", backgroundColor:"#0a9928" + } + } + + valueTile("consumptionPrice", "device.consumptionPrice", decoration: "flat", width: 3, height: 2) { + state "default", label: 'Curr. Cost:\n${currentValue}/h' + } + valueTile("unitPrice", "device.unitPrice", decoration: "flat", width: 3, height: 2) { + state "default", label: 'Unit Price:\n${currentValue}' + } + + valueTile("totalDemand", "device.averageDailyTotalPower", decoration: "flat", width: 3, height: 2) { + state "default", label: 'Total Power:\n${currentValue} kWh' + } + valueTile("totalConsumptionPrice", "device.currentDailyTotalPowerCost", decoration: "flat", width: 3, height: 2) { + state "default", label: 'Total Price:\n${currentValue}' + } + + valueTile("yesterdayTotalPower", "device.yesterdayTotalPower", decoration: "flat", width: 3, height: 1) { + state "default", label: 'Prev. Daily Power :\n${currentValue} kWh' + } + valueTile("yesterdayTotalPowerCost", "device.yesterdayTotalPowerCost", decoration: "flat", width: 3, height: 1) { + state "default", label: 'Prev. Daily Cost:\n${currentValue}' + } + + standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" + } + main (["power"]) + details(["power", "consumptionPrice", "unitPrice", "totalDemand", "totalConsumptionPrice", "yesterdayTotalPower", "yesterdayTotalPowerCost", "refresh"]) + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'power' attribute + +} + +// handle commands +def poll() { + log.debug "Executing 'poll'" + refreshLiveData() +} + +def refresh() { + log.debug "Executing 'refresh'" + poll() +} + +def refreshLiveData() { + + def resp = parent.apiGET("https://live.ovoenergy.com/api/live/meters/${device.deviceNetworkId}/consumptions/instant") + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + + data.meterlive = resp.data + + // get electricity readings + def demand = ((int)Math.round((data.meterlive.consumption.demand as BigDecimal) * 1000)) + def consumptionPrice = (Math.round((data.meterlive.consumption.consumptionPrice.amount as BigDecimal) * 100))/100 + def consumptionPriceCurrency = data.meterlive.consumption.consumptionPrice.currency + def unitPrice = (Math.round((data.meterlive.consumption.unitPrice.amount as BigDecimal) * 100))/100 + def unitPriceCurrency = data.meterlive.consumption.unitPrice.currency + + //demand = String.format("%4f",demand) + consumptionPrice = String.format("%1.2f",consumptionPrice) + unitPrice = String.format("%1.2f",unitPrice) + + // set local variables + sendEvent(name: 'power', value: "$demand", unit: "W") + sendEvent(name: 'consumptionPrice', value: "£$consumptionPrice", displayed: false) + sendEvent(name: 'unitPrice', value: "£$unitPrice", displayed: false) + + //Calculate power costs manually without need for terrible OVO API. + if (data.dailyPowerHistory == null) + { + data.dailyPowerHistory = [:] + } + //Get current hour + + //data.hour = null + def currentHour = new Date().getAt(Calendar.HOUR_OF_DAY) + if ((data.hour == null) || (data.hour != currentHour)) { + //Reset at midnight or initial call + if ((data.hour == null) || (currentHour == 0)) { + //Store the day's power info as yesterdays + def totalPower = getTotalDailyPower() + data.yesterdayTotalPower = (Math.round((totalPower as BigDecimal) * 1000))/1000 + data.yesterdayTotalPowerCost = (Math.round(((totalPower as BigDecimal) * (data.meterlive.consumption.unitPrice.amount as BigDecimal)) * 100))/100 + sendEvent(name: 'yesterdayTotalPower', value: "$data.yesterdayTotalPower", unit: "KWh", displayed: false) + sendEvent(name: 'yesterdayTotalPowerCost', value: "£$data.yesterdayTotalPowerCost", displayed: false) + + //Reset power history + data.dailyPowerHistory = [:] + } + data.hour = currentHour + data.currentHourPowerTotal = 0 + data.currentHourPowerEntryNumber = 1 + } + else { + data.currentHourPowerEntryNumber = data.currentHourPowerEntryNumber + 1 + } + + data.currentHourPowerTotal = data.currentHourPowerTotal + (data.meterlive.consumption.demand as BigDecimal) + data.dailyPowerHistory["Hour $data.hour"] = ((data.currentHourPowerTotal as BigDecimal) / data.currentHourPowerEntryNumber) + + def totalDailyPower = getTotalDailyPower() + def hourCount = 0 + + def formattedAverageTotalPower = (Math.round((totalDailyPower as BigDecimal) * 1000))/1000 + def formattedCurrentTotalPowerCost = (Math.round(((totalDailyPower as BigDecimal) * (data.meterlive.consumption.unitPrice.amount as BigDecimal)) * 100))/100 + + formattedAverageTotalPower = String.format("%1.2f",formattedAverageTotalPower) + formattedCurrentTotalPowerCost = String.format("%1.2f",formattedCurrentTotalPowerCost) + + sendEvent(name: 'averageDailyTotalPower', value: "$formattedAverageTotalPower", unit: "KWh", displayed: false) + sendEvent(name: 'currentDailyTotalPowerCost', value: "£$formattedCurrentTotalPowerCost", displayed: false) + + log.debug "currentHour: $currentHour, data.hour: $data.hour, data.currentHourPowerTotal: $data.currentHourPowerTotal, data.currentHourPowerEntryNumber: $data.currentHourPowerEntryNumber, data.dailyPowerHistory: $data.dailyPowerHistory" + log.debug "formattedAverageTotalPower: $formattedAverageTotalPower, formattedCurrentTotalPowerCost: $formattedCurrentTotalPowerCost" + +} + +private def getTotalDailyPower() { + def totalDailyPower = 0 + data.dailyPowerHistory.each { hour, averagePower -> + totalDailyPower = totalDailyPower + averagePower + }; + return totalDailyPower +} + +def api(method, args = [], success = {}) { + log.debug "Executing 'api'" + + if(!isLoggedIn()) { + log.debug "Need to login" + login(method, args, success) + return + } + + def methods = [ + 'live': [uri: "https://live.ovoenergy.com/api/live/meters/${settings.meterId}/consumptions/instant", type: 'get'], + 'historical': [uri: "https://live.ovoenergy.com/api/live/meters/${settings.meterId}/consumptions/historical?startTimestamp=${(int)(new Date().getTime())/1000}&period=DAY&dataSource=CAD", type: 'get'], + ] + + def request = methods.getAt(method) + + log.debug "Starting $method : $args" + doRequest(request.uri, args, request.type, success) +} \ No newline at end of file From 9f5b47f941b423d1b39c327121b44064567def55 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 21:19:04 +0000 Subject: [PATCH 145/685] v2.1.3 - Allow changing of boost interval amount in device settings. --- .../hive-heating-v2-0.groovy | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 2f3803c7c68..7ea32464aea 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -20,8 +20,13 @@ * Improve Boost button behaviour and look. * v2.1.1 - Tweaks to temperature control responsiveness * v2.1.2 - Minor tweaks to main display + * v2.1.3 - Allow changing of boost interval amount in device settings. */ - +preferences +{ + input( "boostInterval", "number", title: "Boost Interval (minutes)", description: "Boost interval amount in minutes", required: false, defaultValue: 10 ) +} + metadata { definition (name: "Hive Heating V2.0", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { capability "Actuator" @@ -223,14 +228,27 @@ def setBoostLength(minutes) { refreshBoostLabel() } +def getBoostIntervalValue() { + if (settings.boostInterval == null) { + return 10 + } + return settings.boostInterval.toInteger() +} + def boostTimeUp() { log.debug "Executing 'boostTimeUp'" - setBoostLength(state.boostLength + 10) + //Round down result + int boostIntervalValue = getBoostIntervalValue() + def newBoostLength = (state.boostLength + boostIntervalValue) - (state.boostLength % boostIntervalValue) + setBoostLength(newBoostLength) } def boostTimeDown() { log.debug "Executing 'boostTimeDown'" - setBoostLength(state.boostLength - 10) + //Round down result + int boostIntervalValue = getBoostIntervalValue() + def newBoostLength = (state.boostLength - boostIntervalValue) - (state.boostLength % boostIntervalValue) + setBoostLength(newBoostLength) } def boostButton() { From 3925815fcafe0f451cdce3bcdfaf75479b7a17be Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 9 Mar 2016 23:10:59 +0000 Subject: [PATCH 146/685] Adjustments to tile layout --- .../ovo-energy-meter-v2-0.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index 1b87db5351d..e1f14567c35 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -38,18 +38,18 @@ metadata { state "default", label: 'Unit Price:\n${currentValue}' } - valueTile("totalDemand", "device.averageDailyTotalPower", decoration: "flat", width: 3, height: 2) { + valueTile("totalDemand", "device.averageDailyTotalPower", decoration: "flat", width: 3, height: 1) { state "default", label: 'Total Power:\n${currentValue} kWh' } - valueTile("totalConsumptionPrice", "device.currentDailyTotalPowerCost", decoration: "flat", width: 3, height: 2) { + valueTile("totalConsumptionPrice", "device.currentDailyTotalPowerCost", decoration: "flat", width: 3, height: 1) { state "default", label: 'Total Price:\n${currentValue}' } valueTile("yesterdayTotalPower", "device.yesterdayTotalPower", decoration: "flat", width: 3, height: 1) { - state "default", label: 'Prev. Daily Power :\n${currentValue} kWh' + state "default", label: 'Yesterday Total Power :\n${currentValue} kWh' } valueTile("yesterdayTotalPowerCost", "device.yesterdayTotalPowerCost", decoration: "flat", width: 3, height: 1) { - state "default", label: 'Prev. Daily Cost:\n${currentValue}' + state "default", label: 'Yesterday Total Cost:\n${currentValue}' } standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { From 13741ddc780128567583694d4a8a716f5856ec57 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 10 Mar 2016 09:44:49 +0000 Subject: [PATCH 147/685] OVO Energy Connect account detail page clean up --- .../ovo-energy-connect.src/ovo-energy-connect.groovy | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy index 0c284716e7e..122d0620aa3 100644 --- a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy +++ b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy @@ -147,13 +147,16 @@ def accountDetailsPAGE() { def accountData = updateAccountDetails() dynamicPage(name: "accountDetailsPAGE", title: "OVO Energy Account Details", uninstall: false, install: false) { section("Account Holder") { - paragraph "Account ID:\n${accountData.id}\n\nName:\n${accountData.accountHolder}\n\nAddress:\n${accountData.homeAddress.line1}\n${accountData.homeAddress.line2}\n${accountData.homeAddress.town}\n${accountData.homeAddress.county}\n${accountData.homeAddress.postcode}" + paragraph "Account ID:\n${accountData.id}" + paragraph "Name:\n${accountData.accountHolder}" + paragraph "Address:\n${accountData.homeAddress.line1}\n${accountData.homeAddress.line2}\n${accountData.homeAddress.town}\n${accountData.homeAddress.county}\n${accountData.homeAddress.postcode}" } section("Balance") { - paragraph "${accountData.balance.amount} ${accountData.balance.currency}" + paragraph "${String.format("%1.2f", accountData.balance.amount as BigDecimal)} ${accountData.balance.currency}" } section("Direct Debit") { - paragraph "${accountData.directDebit.payment.amount} ${accountData.directDebit.payment.currency}\nNext Payment Date: ${accountData.directDebit.nextPaymentDate}" + paragraph "${String.format("%1.2f", accountData.directDebit.payment.amount as BigDecimal)} ${accountData.directDebit.payment.currency}" + paragraph "Next Payment Date: ${accountData.directDebit.nextPaymentDate}" } } } From 8cc99977e8261e02055dcf2f1923cbe8dbf80579 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 10 Mar 2016 11:03:32 +0000 Subject: [PATCH 148/685] v2.1.4 - Allow changing of boost temperature in device settings. --- .../hive-heating-v2-0.src/hive-heating-v2-0.groovy | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 7ea32464aea..4b1824a76bc 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -21,10 +21,12 @@ * v2.1.1 - Tweaks to temperature control responsiveness * v2.1.2 - Minor tweaks to main display * v2.1.3 - Allow changing of boost interval amount in device settings. + * v2.1.4 - Allow changing of boost temperature in device settings. */ preferences { input( "boostInterval", "number", title: "Boost Interval (minutes)", description: "Boost interval amount in minutes", required: false, defaultValue: 10 ) + input( "boostTemp", "number", title: "Boost Temperature (°C)", description: "Boost interval amount in Centigrade", required: false, defaultValue: 22 ) } metadata { @@ -235,6 +237,13 @@ def getBoostIntervalValue() { return settings.boostInterval.toInteger() } +def getBoostTempValue() { + if (settings.boostInterval == null) { + return "22" + } + return settings.boostTemp +} + def boostTimeUp() { log.debug "Executing 'boostTimeUp'" //Round down result @@ -344,7 +353,7 @@ def setThermostatMode(mode) { } //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":22}}}]} args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: state.boostLength], targetHeatTemperature: [targetValue: "22"]]]] + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: state.boostLength], targetHeatTemperature: [targetValue: getBoostTempValue()]]]] ] } From 52981ec6e176f3bbc4adae26c8f4c65496cea232 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 10 Mar 2016 12:03:20 +0000 Subject: [PATCH 149/685] v2.1 - Send notification for daily cost summary and daily usage summary --- .../ovo-energy-connect.groovy | 62 ++++++++++++++++++- ...king-cleanerer-neato-botvac-edition.groovy | 2 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy index 122d0620aa3..84631694e42 100644 --- a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy +++ b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy @@ -15,6 +15,7 @@ * VERSION HISTORY * 09.03.2016 * v2.0 - New OVO Connect App + * v2.1 - Send notification for daily cost summary and daily usage summary * */ definition( @@ -31,6 +32,7 @@ preferences { page(name:"firstPage", title:"OVO Account Setup", content:"firstPage", install: true) page(name: "loginPAGE") page(name: "selectDevicePAGE") + page(name: "preferencesPAGE") page(name: "accountDetailsPAGE") } @@ -56,6 +58,9 @@ def firstPage() { section ("Choose your Smart Meters:") { href("selectDevicePAGE", title: null, description: devicesSelected() ? "Devices: " + getDevicesSelectedString() : "Tap to select smart meters", state: devicesSelected()) } + section ("Notifications:") { + href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure notifications", state: preferencesSelected()) + } section ("Account Details:") { href("accountDetailsPAGE", title: null, description: "Tap to view OVO Energy Account Details") } @@ -98,6 +103,20 @@ def getDevicesSelectedString() { return listString } +def preferencesSelected() { + return (sendPush || sendSMS != null) && (sendDailyCostSummary || sendDailyUsageSummary) ? "complete" : null +} + +def getPreferencesString() { + def listString = "" + if (sendPush) listString += "Send Push, " + if (sendSMS != null) listString += "Send SMS, " + if (sendDailyCostSummary) listString += "Daily Cost Summary, " + if (sendDailyUsageSummary) listString += "Daily Usage Summary, " + if (listString != "") listString = listString.substring(0, listString.length() - 2) + return listString +} + def loginPAGE() { if (username == null || username == '' || password == null || password == '') { return dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) { @@ -143,6 +162,19 @@ def selectDevicePAGE() { } } +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("OVO Smart Meter Notifications:") { + input "sendDailyCostSummary", "bool", title: "Send daily cost information?", required: false, defaultValue: false + input "sendDailyUsageSummary", "bool", title: "Send daily usage information?", required: false, defaultValue: false + } + } +} + def accountDetailsPAGE() { def accountData = updateAccountDetails() dynamicPage(name: "accountDetailsPAGE", title: "OVO Energy Account Details", uninstall: false, install: false) { @@ -200,7 +232,35 @@ def initialize() { addMeters() runIn(10, 'refreshDevices') // Asynchronously refresh devices so we don't block - + + //subscribe to events for notifications if activated + if (preferencesSelected() == "complete") { + getChildDevices().each { childDevice -> + subscribe(childDevice, "yesterdayTotalPower", evtHandler) + subscribe(childDevice, "yesterdayTotalPowerCost", evtHandler) + } + } +} + +def evtHandler(evt) { + def msg + if (evt.name == "yesterdayTotalPowerCost") { + msg = "${evt.displayName} total daily cost was ${evt.value}" + if (settings.sendDailyCostSummary) generateNotification(msg) + } + else if (evt.name == "yesterdayTotalPower") { + msg = "${evt.displayName} total daily usage was ${evt.value} ${evt.unit} " + if (settings.sendDailyUsageSummary) generateNotification(msg) + } +} + +def generateNotification(msg) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush) { + sendPush(msg) + } } def updateDevices() { diff --git a/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy b/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy index 46d205eac37..79394af8449 100644 --- a/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy +++ b/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy @@ -23,7 +23,7 @@ * * Modified by Alex Lee Yuk Cheung for Neato Botvac Devices * Version: 1.0 - Initial Version - Added Auto Dock On Pause, Force Clean Option, Changes to Polling behaviour - * Version: 1.1 - Fix to setting SHM state when error has occured whilst cleaning + * Version: 1.1 - Fix to setting SHM state when error has occured whilst cleaning */ definition( From 630c5a0f841db76c16cf41518202570cbf89c83d Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 10 Mar 2016 17:57:28 +0000 Subject: [PATCH 150/685] v2.1.5 - Option to disable Hive Heating Device for summer. Disable mode stops any automation commands from other smart apps reactivating Hive Heating. --- .../hive-heating-v2-0.groovy | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 4b1824a76bc..a05f413fbff 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -22,11 +22,13 @@ * v2.1.2 - Minor tweaks to main display * v2.1.3 - Allow changing of boost interval amount in device settings. * v2.1.4 - Allow changing of boost temperature in device settings. + * v2.1.5 - Option to disable Hive Heating Device for summer. Disable mode stops any automation commands from other smart apps reactivating Hive Heating. */ preferences { input( "boostInterval", "number", title: "Boost Interval (minutes)", description: "Boost interval amount in minutes", required: false, defaultValue: 10 ) input( "boostTemp", "number", title: "Boost Temperature (°C)", description: "Boost interval amount in Centigrade", required: false, defaultValue: 22 ) + input( "disableDevice", "bool", title: "Disable Hive Heating?", required: false, defaultValue: false ) } metadata { @@ -198,22 +200,24 @@ def setHeatingSetpoint(temp) { if (temp > 32) { temp = 32 } - - //if thermostat is off, set to manual - if (latestThermostatMode.stringValue == 'off') { - def args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] - ] - def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + + if (settings.disableDevice == null || settings.disableDevice == false) { + //if thermostat is off, set to manual + if (latestThermostatMode.stringValue == 'off') { + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], activeScheduleLock: [targetValue: true]]]] + ] + def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) - } + } - // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} - def args = [ + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} + def args = [ nodes: [ [attributes: [targetHeatTemperature: [targetValue: temp]]]] ] - def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + } runIn(4, refresh) } @@ -331,34 +335,36 @@ def auto() { } def setThermostatMode(mode) { - mode = mode == 'cool' ? 'heat' : mode - log.debug "Executing 'setThermostatMode with mode $mode'" - def args = [ + if (settings.disableDevice == null || settings.disableDevice == false) { + mode = mode == 'cool' ? 'heat' : mode + log.debug "Executing 'setThermostatMode with mode $mode'" + def args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: false]]]] ] - if (mode == 'off') { + if (mode == 'off') { args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] ] - } else if (mode == 'heat') { + } else if (mode == 'heat') { //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"HEAT"},"activeScheduleLock":{"targetValue":true}}}]} args = [ nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "HEAT"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] ] - } else if (mode == 'emergency heat') { - if (state.boostLength == null || state.boostLength == '') - { - state.boostLength = 60 - sendEvent("name":"boostLength", "value": 60, displayed: true) - } - //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":22}}}]} - args = [ - nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: state.boostLength], targetHeatTemperature: [targetValue: getBoostTempValue()]]]] + } else if (mode == 'emergency heat') { + if (state.boostLength == null || state.boostLength == '') + { + state.boostLength = 60 + sendEvent("name":"boostLength", "value": 60, displayed: true) + } + //{"nodes":[{"attributes":{"activeHeatCoolMode":{"targetValue":"BOOST"},"scheduleLockDuration":{"targetValue":30},"targetHeatTemperature":{"targetValue":22}}}]} + args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "BOOST"], scheduleLockDuration: [targetValue: state.boostLength], targetHeatTemperature: [targetValue: getBoostTempValue()]]]] ] - } + } - def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) - mode = mode == 'range' ? 'auto' : mode + def resp = parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + mode = mode == 'range' ? 'auto' : mode + } runIn(4, refresh) } @@ -419,7 +425,15 @@ def poll() { def mode = 'auto' - if (activeHeatCoolMode == "OFF") { + //If Hive heating device is set to disabled, then force off if not already off. + if (settings.disableDevice != null && settings.disableDevice == true && activeHeatCoolMode != "OFF") { + def args = [ + nodes: [ [attributes: [activeHeatCoolMode: [targetValue: "OFF"], scheduleLockDuration: [targetValue: 0], activeScheduleLock: [targetValue: true]]]] + ] + parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + mode = 'off' + } + else if (activeHeatCoolMode == "OFF") { mode = 'off' statusMsg = statusMsg + " OFF" } @@ -437,6 +451,11 @@ def poll() { else { statusMsg = statusMsg + " SCHEDULE" } + + if (settings.disableDevice != null && settings.disableDevice == true) { + statusMsg = "DISABLED" + } + sendEvent(name: 'thermostatMode', value: mode) // determine if Hive heating relay is on From 453902edac52f45a61ffc2f81fe88bf7fb4bfddf Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 10 Mar 2016 23:22:36 +0000 Subject: [PATCH 151/685] v2.1.5b - Bug fix when desired heat set point is null, control stops working. --- .../alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index a05f413fbff..977b9e9c652 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -23,6 +23,7 @@ * v2.1.3 - Allow changing of boost interval amount in device settings. * v2.1.4 - Allow changing of boost temperature in device settings. * v2.1.5 - Option to disable Hive Heating Device for summer. Disable mode stops any automation commands from other smart apps reactivating Hive Heating. + * v2.1.5b - Bug fix when desired heat set point is null, control stops working. */ preferences { @@ -304,7 +305,7 @@ def setTemperatureForSlider(value) { } def getHeatTemp() { - return state.desiredHeatSetpoint + return state.desiredHeatSetpoint == null ? device.currentValue("heatingSetpoint") : state.desiredHeatSetpoint } def off() { From 7f3a00038595f99befadc5268e45bbabab160765 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 11 Mar 2016 09:19:43 +0000 Subject: [PATCH 152/685] v2.1.5c - Fix multitile button behaviour that has changed since ST app 2.1.0. Add colour code to temperature reporting in activity feed. --- .../hive-heating-v2-0.groovy | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 977b9e9c652..ed1ceceb62a 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -24,6 +24,7 @@ * v2.1.4 - Allow changing of boost temperature in device settings. * v2.1.5 - Option to disable Hive Heating Device for summer. Disable mode stops any automation commands from other smart apps reactivating Hive Heating. * v2.1.5b - Bug fix when desired heat set point is null, control stops working. + * v2.1.5c - Fix multitile button behaviour that has changed since ST app 2.1.0. Add colour code to temperature reporting in activity feed. */ preferences { @@ -112,6 +113,19 @@ metadata { standardTile("heatingSetpointDown", "device.desiredHeatSetpoint", width: 1, height: 1, canChangeIcon: false, inactiveLabel: false, decoration: "flat") { state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#ffffff" } + + valueTile("device.temperature", "device.temperature", canChangeBackground: true){ + state "default", label: '${currentValue}°', unit:"C", + backgroundColors:[ + [value: 0, color: "#50b5dd"], + [value: 10, color: "#43a575"], + [value: 13, color: "#c5d11b"], + [value: 17, color: "#f4961a"], + [value: 20, color: "#e75928"], + [value: 25, color: "#d9372b"], + [value: 29, color: "#b9203b"] + ] + } valueTile("heatingSetpoint", "device.desiredHeatSetpoint", width: 2, height: 2) { state "default", label:'${currentValue}°', unit:"C", @@ -296,7 +310,9 @@ def heatingSetpointDown(){ def setTemperature(value) { log.debug "Executing 'setTemperature with $value'" - (value == 0) ? (setNewSetPointValue(getHeatTemp().toInteger() - 1)) : (setNewSetPointValue(getHeatTemp().toInteger() + 1)) + def currentTemp = device.currentState("temperature").doubleValue + (value < currentTemp) ? (setNewSetPointValue(getHeatTemp().toInteger() - 1)) : (setNewSetPointValue(getHeatTemp().toInteger() + 1)) + } def setTemperatureForSlider(value) { From bb76463ead1e01e28e4ce76d5e741c7cf45a8c61 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 11 Mar 2016 11:43:10 +0000 Subject: [PATCH 153/685] v2.2 - Support fetching latest unit prices and standing charge from OVO account. * Send notification for specified daily cost level breach. --- .../ovo-energy-connect.groovy | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy index 84631694e42..ff738e55659 100644 --- a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy +++ b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy @@ -16,6 +16,8 @@ * 09.03.2016 * v2.0 - New OVO Connect App * v2.1 - Send notification for daily cost summary and daily usage summary + * v2.2 - Support fetching latest unit prices and standing charge from OVO account. + * Send notification for specified daily cost level breach. * */ definition( @@ -104,7 +106,7 @@ def getDevicesSelectedString() { } def preferencesSelected() { - return (sendPush || sendSMS != null) && (sendDailyCostSummary || sendDailyUsageSummary) ? "complete" : null + return (sendPush || sendSMS != null) && (sendDailyCostSummary || sendDailyUsageSummary || costAlertLevel) ? "complete" : null } def getPreferencesString() { @@ -113,6 +115,7 @@ def getPreferencesString() { if (sendSMS != null) listString += "Send SMS, " if (sendDailyCostSummary) listString += "Daily Cost Summary, " if (sendDailyUsageSummary) listString += "Daily Usage Summary, " + if (costAlertLevel) listString += "Daily Cost Level Alert, " if (listString != "") listString = listString.substring(0, listString.length() - 2) return listString } @@ -171,6 +174,7 @@ def preferencesPAGE() { section("OVO Smart Meter Notifications:") { input "sendDailyCostSummary", "bool", title: "Send daily cost information?", required: false, defaultValue: false input "sendDailyUsageSummary", "bool", title: "Send daily usage information?", required: false, defaultValue: false + input "costAlertLevel", "bool", title: "Alert when daily cost exceeds amount?", required: false, defaultValue: false } } } @@ -238,6 +242,7 @@ def initialize() { getChildDevices().each { childDevice -> subscribe(childDevice, "yesterdayTotalPower", evtHandler) subscribe(childDevice, "yesterdayTotalPowerCost", evtHandler) + subscribe(childDevice, "costAlertLevelPassed", evtHandler) } } } @@ -252,6 +257,10 @@ def evtHandler(evt) { msg = "${evt.displayName} total daily usage was ${evt.value} ${evt.unit} " if (settings.sendDailyUsageSummary) generateNotification(msg) } + else if (evt.name == "costAlertLevelPassed" && evt.value != "false") { + msg = "WARNING: ${evt.displayName} daily cost has exceeded ${evt.value}" + if (settings.costAlertLevel) generateNotification(msg) + } } def generateNotification(msg) { @@ -338,6 +347,41 @@ def devicesList() { } } +def updateLatestPrices() { + log.info("Update latest prices from OVO...") + logErrors([]) { + def resp = apiGET("https://paym.ovoenergy.com/api/paym/accounts") + if (resp.status == 200) { + state.contracts = [:] + def contracts = resp.data.contracts[0] + contracts.each { contract -> + if (contract.utility != null) { + def value = [contract.standingCharge.amount.amount, contract.rates.amount.amount] + def key = contract.utility.toUpperCase() + state.contracts["${key}"] = value + } + } + } + else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + } + } +} + +def getUnitPrice(utilityType) { + if (state.contracts[utilityType] != null) { + return state.contracts[utilityType][1] + } + return 0 +} + +def getStandingCharge(utilityType) { + if (state.contracts[utilityType] != null) { + return state.contracts[utilityType][0] + } + return 0 +} + def updateAccountDetails() { logErrors([]) { def resp = apiGET("https://paym.ovoenergy.com/api/paym/accounts") From a7d283f02b2c265a8a25c82d8d66ba53010ca401 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 11 Mar 2016 11:44:47 +0000 Subject: [PATCH 154/685] v2.1 - Improve pricing calculations using contract info from OVO. Notification framework for high costs. Enable alert for specified daily cost level breach. --- .../ovo-energy-meter-v2-0.groovy | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index e1f14567c35..b8914760cb4 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -15,7 +15,13 @@ * VERSION HISTORY * 09.03.2016 * v2.0 - Initial V2.0 Release with OVO Energy (Connect) app + * v2.1 - Improve pricing calculations using contract info from OVO. Notification framework for high costs. + * Enable alert for specified daily cost level breach. */ +preferences +{ + input( "costAlertLevel", "number", title: "Set cost alert level (£)", description: "Send alert when daily cost reaches amount", required: false, defaultValue: 10 ) +} metadata { definition (name: "OVO Energy Meter V2.0", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { @@ -42,7 +48,7 @@ metadata { state "default", label: 'Total Power:\n${currentValue} kWh' } valueTile("totalConsumptionPrice", "device.currentDailyTotalPowerCost", decoration: "flat", width: 3, height: 1) { - state "default", label: 'Total Price:\n${currentValue}' + state "default", label: 'Total Cost:\n${currentValue}' } valueTile("yesterdayTotalPower", "device.yesterdayTotalPower", decoration: "flat", width: 3, height: 1) { @@ -92,14 +98,26 @@ def refreshLiveData() { def demand = ((int)Math.round((data.meterlive.consumption.demand as BigDecimal) * 1000)) def consumptionPrice = (Math.round((data.meterlive.consumption.consumptionPrice.amount as BigDecimal) * 100))/100 def consumptionPriceCurrency = data.meterlive.consumption.consumptionPrice.currency - def unitPrice = (Math.round((data.meterlive.consumption.unitPrice.amount as BigDecimal) * 100))/100 + def unitPriceBigDecimal = data.meterlive.consumption.unitPrice.amount as BigDecimal + def unitPrice = (Math.round((unitPriceBigDecimal) * 100))/100 def unitPriceCurrency = data.meterlive.consumption.unitPrice.currency //demand = String.format("%4f",demand) consumptionPrice = String.format("%1.2f",consumptionPrice) unitPrice = String.format("%1.2f",unitPrice) - // set local variables + //update unit price and standing charge from more up to date OVO API + parent.updateLatestPrices() + def latestUnitPrice = ((device.name.contains('Gas')) ? parent.getUnitPrice('GAS') : parent.getUnitPrice('ELECTRICITY')) as BigDecimal + if (latestUnitPrice > 0) { + unitPriceBigDecimal = latestUnitPrice + unitPrice = String.format("%1.5f", unitPriceBigDecimal) + } + + def standingCharge = ((device.name.contains('Gas')) ? parent.getStandingCharge('GAS') : parent.getStandingCharge('ELECTRICITY')) as BigDecimal + log.debug "unitPrice: ${unitPriceBigDecimal} standingCharge: ${standingCharge}" + // set local variables + sendEvent(name: 'power', value: "$demand", unit: "W") sendEvent(name: 'consumptionPrice', value: "£$consumptionPrice", displayed: false) sendEvent(name: 'unitPrice', value: "£$unitPrice", displayed: false) @@ -119,7 +137,7 @@ def refreshLiveData() { //Store the day's power info as yesterdays def totalPower = getTotalDailyPower() data.yesterdayTotalPower = (Math.round((totalPower as BigDecimal) * 1000))/1000 - data.yesterdayTotalPowerCost = (Math.round(((totalPower as BigDecimal) * (data.meterlive.consumption.unitPrice.amount as BigDecimal)) * 100))/100 + data.yesterdayTotalPowerCost = (Math.round((((totalPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 sendEvent(name: 'yesterdayTotalPower', value: "$data.yesterdayTotalPower", unit: "KWh", displayed: false) sendEvent(name: 'yesterdayTotalPowerCost', value: "£$data.yesterdayTotalPowerCost", displayed: false) @@ -141,7 +159,14 @@ def refreshLiveData() { def hourCount = 0 def formattedAverageTotalPower = (Math.round((totalDailyPower as BigDecimal) * 1000))/1000 - def formattedCurrentTotalPowerCost = (Math.round(((totalDailyPower as BigDecimal) * (data.meterlive.consumption.unitPrice.amount as BigDecimal)) * 100))/100 + def formattedCurrentTotalPowerCost = (Math.round((((totalDailyPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 + + //Send event to raise notification on high cost + if (formattedCurrentTotalPowerCost > (getCostAlertLevelValue() as BigDecimal)) { + sendEvent(name: 'costAlertLevelPassed', value: "£${getCostAlertLevelValue()}") + } else { + sendEvent(name: 'costAlertLevelPassed', value: "false") + } formattedAverageTotalPower = String.format("%1.2f",formattedAverageTotalPower) formattedCurrentTotalPowerCost = String.format("%1.2f",formattedCurrentTotalPowerCost) @@ -162,22 +187,9 @@ private def getTotalDailyPower() { return totalDailyPower } -def api(method, args = [], success = {}) { - log.debug "Executing 'api'" - - if(!isLoggedIn()) { - log.debug "Need to login" - login(method, args, success) - return - } - - def methods = [ - 'live': [uri: "https://live.ovoenergy.com/api/live/meters/${settings.meterId}/consumptions/instant", type: 'get'], - 'historical': [uri: "https://live.ovoenergy.com/api/live/meters/${settings.meterId}/consumptions/historical?startTimestamp=${(int)(new Date().getTime())/1000}&period=DAY&dataSource=CAD", type: 'get'], - ] - - def request = methods.getAt(method) - - log.debug "Starting $method : $args" - doRequest(request.uri, args, request.type, success) +def getCostAlertLevelValue() { + if (settings.costAlertLevel == null) { + return "10" + } + return settings.costAlertLevel } \ No newline at end of file From d53ceed681d6b1a62c9474939e654375d76be288 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 11 Mar 2016 16:10:56 +0000 Subject: [PATCH 155/685] Fix blank temperature on idle mode. --- .../alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy | 3 ++- .../alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index ed1ceceb62a..447f0f527ca 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -25,6 +25,7 @@ * v2.1.5 - Option to disable Hive Heating Device for summer. Disable mode stops any automation commands from other smart apps reactivating Hive Heating. * v2.1.5b - Bug fix when desired heat set point is null, control stops working. * v2.1.5c - Fix multitile button behaviour that has changed since ST app 2.1.0. Add colour code to temperature reporting in activity feed. + * v2.1.6d - Fix blank temperature on idle mode. */ preferences { @@ -74,7 +75,7 @@ metadata { attributeState("default", action: "setTemperature") } tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { - attributeState("idle", backgroundColor:"#ffffff") + attributeState("idle") attributeState("heating", backgroundColor:"#ec6e05") } tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { diff --git a/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy b/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy index f50f0621a11..be298d5dbc3 100644 --- a/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy +++ b/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy @@ -15,6 +15,7 @@ * VERSION HISTORY * 25.02.2016 * v2.0 BETA - Initial Release + * v2.0b - Fix blank temperature readings on Android ST app */ metadata { @@ -38,7 +39,7 @@ metadata { multiAttributeTile(name: "hotWaterRelay", width: 6, height: 4, type:"lighting") { tileAttribute("device.thermostatOperatingState", key:"PRIMARY_CONTROL"){ attributeState "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05" - attributeState "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#ffffff" + attributeState "idle", icon: "st.thermostat.heating-cooling-off" } tileAttribute ("hiveHotWater", key: "SECONDARY_CONTROL") { attributeState "hiveHotWater", label:'${currentValue}' From d51517903ec100ef336ccd81bb213ae52c3dea4b Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 11 Mar 2016 21:51:54 +0000 Subject: [PATCH 156/685] v2.1.6e - Another attempt to fix blank temperature reading on Android. --- .../alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 447f0f527ca..8fb1773b720 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -25,7 +25,8 @@ * v2.1.5 - Option to disable Hive Heating Device for summer. Disable mode stops any automation commands from other smart apps reactivating Hive Heating. * v2.1.5b - Bug fix when desired heat set point is null, control stops working. * v2.1.5c - Fix multitile button behaviour that has changed since ST app 2.1.0. Add colour code to temperature reporting in activity feed. - * v2.1.6d - Fix blank temperature on idle mode. + * v2.1.6d - Fix blank temperature readings on Android ST app + * v2.1.6e - Another attempt to fix blank temperature reading on Android. */ preferences { @@ -75,7 +76,7 @@ metadata { attributeState("default", action: "setTemperature") } tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { - attributeState("idle") + attributeState("idle", backgroundColor:"#bbbbbb") attributeState("heating", backgroundColor:"#ec6e05") } tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { @@ -86,7 +87,7 @@ metadata { attributeState("emergency heat", label:'Boost') } tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { - attributeState "default", label: '${currentValue}', backgroundColors: [ + attributeState "default", label: '${currentValue}°', backgroundColors: [ // Celsius Color Range [value: 0, color: "#50b5dd"], [value: 10, color: "#43a575"], From fb979b65873e88b4bab94146e70d3d0eb9eb1569 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 11 Mar 2016 22:00:43 +0000 Subject: [PATCH 157/685] Hot Water Device Minor Layout Fixes --- .../alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy b/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy index be298d5dbc3..dd9d25478d3 100644 --- a/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy +++ b/devicetypes/alyc100/hive-hot-water-v2-0.src/hive-hot-water-v2-0.groovy @@ -39,7 +39,7 @@ metadata { multiAttributeTile(name: "hotWaterRelay", width: 6, height: 4, type:"lighting") { tileAttribute("device.thermostatOperatingState", key:"PRIMARY_CONTROL"){ attributeState "heating", icon: "st.thermostat.heat", backgroundColor: "#EC6E05" - attributeState "idle", icon: "st.thermostat.heating-cooling-off" + attributeState "idle", icon: "st.thermostat.heating-cooling-off", backgroundColor: "#bbbbbb" } tileAttribute ("hiveHotWater", key: "SECONDARY_CONTROL") { attributeState "hiveHotWater", label:'${currentValue}' From 0b2b23e719342acbbc9b2d8ec141b45ecee5b04b Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 11 Mar 2016 23:46:46 +0000 Subject: [PATCH 158/685] v1.0.3b - Added icons to MiHome device list. --- smartapps/alyc100/mihome-connect.src/mihome-connect.groovy | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index ad77fc5504e..9655de8180b 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -25,8 +25,7 @@ * 31.01.2016 * v1.0.3 - Bug fix to refresh schedule job. * - * 25.02.2016 - * v1.0.4 - View detected devices in Connect App + * v1.0.3b - Added icons to MiHome device list. */ definition( name: "MiHome (Connect)", @@ -70,7 +69,7 @@ def firstPage() { } section("Devices Discovered And Automatically Added...") { state.devices.each {devices -> - paragraph devices.trim() + paragraph image: "https://mihome4u.co.uk/assets/homepage/mihome4-01bc8a0e478b385df3248b55cc2df7ca.png", devices.trim() } } From 61ef04c1909a75de636b98d2f7dec00ea0d36fa0 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 13 Mar 2016 19:49:03 +0000 Subject: [PATCH 159/685] v2.1.6f - Allow decimal value for boost temperature. Changes to VALUE_CONTROL method to match latest ST docs. --- .../hive-heating-v2-0.src/hive-heating-v2-0.groovy | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 8fb1773b720..4976ffe1e14 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -27,11 +27,12 @@ * v2.1.5c - Fix multitile button behaviour that has changed since ST app 2.1.0. Add colour code to temperature reporting in activity feed. * v2.1.6d - Fix blank temperature readings on Android ST app * v2.1.6e - Another attempt to fix blank temperature reading on Android. + * v2.1.6f - Allow decimal value for boost temperature. Changes to VALUE_CONTROL method to match latest ST docs. */ preferences { input( "boostInterval", "number", title: "Boost Interval (minutes)", description: "Boost interval amount in minutes", required: false, defaultValue: 10 ) - input( "boostTemp", "number", title: "Boost Temperature (°C)", description: "Boost interval amount in Centigrade", required: false, defaultValue: 22 ) + input( "boostTemp", "decimal", title: "Boost Temperature (°C)", description: "Boost interval amount in Centigrade", required: false, defaultValue: 22, range: "5..32" ) input( "disableDevice", "bool", title: "Disable Hive Heating?", required: false, defaultValue: false ) } @@ -54,7 +55,6 @@ metadata { command "setHeatingSetpoint" command "setTemperatureForSlider" command "setBoostLength" - command "setTemperature" command "boostButton" } @@ -73,7 +73,8 @@ metadata { attributeState "hiveHeating", label:'${currentValue}' } tileAttribute("device.temperature", key: "VALUE_CONTROL") { - attributeState("default", action: "setTemperature") + attributeState("VALUE_UP", action: "heatingSetpointUp") + attributeState("VALUE_DOWN", action: "heatingSetpointDown") } tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { attributeState("idle", backgroundColor:"#bbbbbb") @@ -310,13 +311,6 @@ def heatingSetpointDown(){ setNewSetPointValue(getHeatTemp().toInteger() - 1) } -def setTemperature(value) { - log.debug "Executing 'setTemperature with $value'" - def currentTemp = device.currentState("temperature").doubleValue - (value < currentTemp) ? (setNewSetPointValue(getHeatTemp().toInteger() - 1)) : (setNewSetPointValue(getHeatTemp().toInteger() + 1)) - -} - def setTemperatureForSlider(value) { log.debug "Executing 'setTemperatureForSlider with $value'" setNewSetPointValue(value) From 494450bbd02f21f6ae47105199cf77a57b2b8789 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 13 Mar 2016 19:53:05 +0000 Subject: [PATCH 160/685] v2.1b - Allow cost alert level to be decimal --- .../ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index b8914760cb4..6ad0c3cc6d0 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -17,10 +17,11 @@ * v2.0 - Initial V2.0 Release with OVO Energy (Connect) app * v2.1 - Improve pricing calculations using contract info from OVO. Notification framework for high costs. * Enable alert for specified daily cost level breach. + * v2.1b - Allow cost alert level to be decimal */ preferences { - input( "costAlertLevel", "number", title: "Set cost alert level (£)", description: "Send alert when daily cost reaches amount", required: false, defaultValue: 10 ) + input( "costAlertLevel", "decimal", title: "Set cost alert level (£)", description: "Send alert when daily cost reaches amount", required: false, defaultValue: 10.00 ) } metadata { From 2704c2e6717623b857149e8f5a52ec66e7c9ba75 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 14 Mar 2016 12:18:07 +0000 Subject: [PATCH 161/685] v2.2 - Percentage comparison from previous cost values added into display --- .../ovo-energy-meter-v2-0.groovy | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index 6ad0c3cc6d0..e222ab33c12 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -13,11 +13,11 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY - * 09.03.2016 * v2.0 - Initial V2.0 Release with OVO Energy (Connect) app * v2.1 - Improve pricing calculations using contract info from OVO. Notification framework for high costs. * Enable alert for specified daily cost level breach. * v2.1b - Allow cost alert level to be decimal + * v2.2 - Percentage comparison from previous cost values added into display */ preferences { @@ -128,6 +128,10 @@ def refreshLiveData() { { data.dailyPowerHistory = [:] } + if (data.yesterdayPowerHistory == null) + { + data.yesterdayPowerHistory = [:] + } //Get current hour //data.hour = null @@ -138,11 +142,21 @@ def refreshLiveData() { //Store the day's power info as yesterdays def totalPower = getTotalDailyPower() data.yesterdayTotalPower = (Math.round((totalPower as BigDecimal) * 1000))/1000 - data.yesterdayTotalPowerCost = (Math.round((((totalPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 + def newYesterdayTotalPowerCost = (Math.round((((totalPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 + def costYesterdayComparison = calculatePercentChange(newYesterdayTotalPowerCost as BigDecimal, data.yesterdayTotalPowerCost as BigDecimal) + def formattedCostYesterdayComparison + if (costYesterdayComparison >= 0) { + formattedCostYesterdayComparison = "+" + costYesterdayComparison + } + else { + formattedCostYesterdayComparison = "-" + costYesterdayComparison + } + data.yesterdayTotalPowerCost = newYesterdayTotalPowerCost sendEvent(name: 'yesterdayTotalPower', value: "$data.yesterdayTotalPower", unit: "KWh", displayed: false) - sendEvent(name: 'yesterdayTotalPowerCost', value: "£$data.yesterdayTotalPowerCost", displayed: false) + sendEvent(name: 'yesterdayTotalPowerCost', value: "£$data.yesterdayTotalPowerCost (" + formattedCostYesterdayComparison + "%)", displayed: false) //Reset power history + data.yesterdayPowerHistory = data.dailyPowerHistory data.dailyPowerHistory = [:] } data.hour = currentHour @@ -158,9 +172,16 @@ def refreshLiveData() { def totalDailyPower = getTotalDailyPower() def hourCount = 0 - + def costDailyComparison = calculatePercentChange(getTotalDailyPower() as BigDecimal, getYesterdayPower(data.currentHour) as BigDecimal) def formattedAverageTotalPower = (Math.round((totalDailyPower as BigDecimal) * 1000))/1000 def formattedCurrentTotalPowerCost = (Math.round((((totalDailyPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 + def formattedCostDailyComparison + if (costDailyComparison >= 0) { + formattedCostDailyComparison = "+" + costDailyComparison + } + else { + formattedCostDailyComparison = "-" + costDailyComparison + } //Send event to raise notification on high cost if (formattedCurrentTotalPowerCost > (getCostAlertLevelValue() as BigDecimal)) { @@ -171,6 +192,7 @@ def refreshLiveData() { formattedAverageTotalPower = String.format("%1.2f",formattedAverageTotalPower) formattedCurrentTotalPowerCost = String.format("%1.2f",formattedCurrentTotalPowerCost) + formattedCurrentTotalPowerCost += " (" + formattedCostDailyComparison + "%)" sendEvent(name: 'averageDailyTotalPower', value: "$formattedAverageTotalPower", unit: "KWh", displayed: false) sendEvent(name: 'currentDailyTotalPowerCost', value: "£$formattedCurrentTotalPowerCost", displayed: false) @@ -183,11 +205,30 @@ def refreshLiveData() { private def getTotalDailyPower() { def totalDailyPower = 0 data.dailyPowerHistory.each { hour, averagePower -> - totalDailyPower = totalDailyPower + averagePower + totalDailyPower += averagePower }; return totalDailyPower } +private def getYesterdayPower(currentHour) { + def totalDailyPower = 0 + for (int i=0; i 0) return 1000 + else if (delta == 0) return 0 + else return -1000 + } +} + def getCostAlertLevelValue() { if (settings.costAlertLevel == null) { return "10" From 0404a6c567707415ec8d2eaf6d4224a8a4a87b93 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 14 Mar 2016 12:40:14 +0000 Subject: [PATCH 162/685] Bug fixes --- .../ovo-energy-meter-v2-0.groovy | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index e222ab33c12..49b46eb1c17 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -133,7 +133,6 @@ def refreshLiveData() { data.yesterdayPowerHistory = [:] } //Get current hour - //data.hour = null def currentHour = new Date().getAt(Calendar.HOUR_OF_DAY) if ((data.hour == null) || (data.hour != currentHour)) { @@ -172,7 +171,7 @@ def refreshLiveData() { def totalDailyPower = getTotalDailyPower() def hourCount = 0 - def costDailyComparison = calculatePercentChange(getTotalDailyPower() as BigDecimal, getYesterdayPower(data.currentHour) as BigDecimal) + def costDailyComparison = calculatePercentChange(getTotalDailyPower() as BigDecimal, getYesterdayPower(data.hour) as BigDecimal) def formattedAverageTotalPower = (Math.round((totalDailyPower as BigDecimal) * 1000))/1000 def formattedCurrentTotalPowerCost = (Math.round((((totalDailyPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 def formattedCostDailyComparison @@ -212,9 +211,9 @@ private def getTotalDailyPower() { private def getYesterdayPower(currentHour) { def totalDailyPower = 0 - for (int i=0; i Date: Mon, 14 Mar 2016 12:43:43 +0000 Subject: [PATCH 163/685] Bug fixes --- .../ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index 49b46eb1c17..703d11cad43 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -212,7 +212,7 @@ private def getTotalDailyPower() { private def getYesterdayPower(currentHour) { def totalDailyPower = 0 for (int i=0; i<=currentHour.toInteger(); i++) { - totalDailyPower += data.yesterdayPowerHistory["Hour $i"] + if (data.yesterdayPowerHistory["Hour $i"] != null) totalDailyPower += data.yesterdayPowerHistory["Hour $i"] } return totalDailyPower } From caa9c3ad1c5f484756da870a7f6863e91d98bee6 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 14 Mar 2016 12:43:46 +0000 Subject: [PATCH 164/685] Bug fixes From 4f5c089bae604ebaebc398f2ac92fbdefd9b8b23 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 14 Mar 2016 20:51:25 +0000 Subject: [PATCH 165/685] v2.2.1 - Add current consumption price based on unit price from OVO account API not OVO live API --- .../ovo-energy-meter-v2-0.groovy | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index 703d11cad43..b64099de7b8 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -18,6 +18,7 @@ * Enable alert for specified daily cost level breach. * v2.1b - Allow cost alert level to be decimal * v2.2 - Percentage comparison from previous cost values added into display + * v2.2.1 - Add current consumption price based on unit price from OVO account API not OVO live API */ preferences { @@ -95,19 +96,13 @@ def refreshLiveData() { data.meterlive = resp.data - // get electricity readings - def demand = ((int)Math.round((data.meterlive.consumption.demand as BigDecimal) * 1000)) - def consumptionPrice = (Math.round((data.meterlive.consumption.consumptionPrice.amount as BigDecimal) * 100))/100 - def consumptionPriceCurrency = data.meterlive.consumption.consumptionPrice.currency + //update unit price from OVO Live API def unitPriceBigDecimal = data.meterlive.consumption.unitPrice.amount as BigDecimal def unitPrice = (Math.round((unitPriceBigDecimal) * 100))/100 def unitPriceCurrency = data.meterlive.consumption.unitPrice.currency - - //demand = String.format("%4f",demand) - consumptionPrice = String.format("%1.2f",consumptionPrice) unitPrice = String.format("%1.2f",unitPrice) - //update unit price and standing charge from more up to date OVO API + //update unit price and standing charge from more up to date OVO Account API if available parent.updateLatestPrices() def latestUnitPrice = ((device.name.contains('Gas')) ? parent.getUnitPrice('GAS') : parent.getUnitPrice('ELECTRICITY')) as BigDecimal if (latestUnitPrice > 0) { @@ -115,6 +110,14 @@ def refreshLiveData() { unitPrice = String.format("%1.5f", unitPriceBigDecimal) } + // get electricity readings + def demand = ((int)Math.round((data.meterlive.consumption.demand as BigDecimal) * 1000)) + def consumptionPrice = (Math.round(((unitPriceBigDecimal as BigDecimal) * (data.meterlive.consumption.demand as BigDecimal)) * 100))/100 + def consumptionPriceCurrency = data.meterlive.consumption.consumptionPrice.currency + + //demand = String.format("%4f",demand) + consumptionPrice = String.format("%1.2f",consumptionPrice) + def standingCharge = ((device.name.contains('Gas')) ? parent.getStandingCharge('GAS') : parent.getStandingCharge('ELECTRICITY')) as BigDecimal log.debug "unitPrice: ${unitPriceBigDecimal} standingCharge: ${standingCharge}" // set local variables From 653feec54a02c77b764be5bc74b094d8ee9e4028 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 15 Mar 2016 11:19:46 +0000 Subject: [PATCH 166/685] v2.2.1b - Remove double negative on percentage values. --- .../ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index b64099de7b8..65eed95ab3c 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -19,6 +19,7 @@ * v2.1b - Allow cost alert level to be decimal * v2.2 - Percentage comparison from previous cost values added into display * v2.2.1 - Add current consumption price based on unit price from OVO account API not OVO live API + * v2.2.1b - Remove double negative on percentage values. */ preferences { @@ -150,9 +151,6 @@ def refreshLiveData() { if (costYesterdayComparison >= 0) { formattedCostYesterdayComparison = "+" + costYesterdayComparison } - else { - formattedCostYesterdayComparison = "-" + costYesterdayComparison - } data.yesterdayTotalPowerCost = newYesterdayTotalPowerCost sendEvent(name: 'yesterdayTotalPower', value: "$data.yesterdayTotalPower", unit: "KWh", displayed: false) sendEvent(name: 'yesterdayTotalPowerCost', value: "£$data.yesterdayTotalPowerCost (" + formattedCostYesterdayComparison + "%)", displayed: false) @@ -181,9 +179,6 @@ def refreshLiveData() { if (costDailyComparison >= 0) { formattedCostDailyComparison = "+" + costDailyComparison } - else { - formattedCostDailyComparison = "-" + costDailyComparison - } //Send event to raise notification on high cost if (formattedCurrentTotalPowerCost > (getCostAlertLevelValue() as BigDecimal)) { From fa258db9285c4dce7f700d6a8e661b99598bd02c Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 15 Mar 2016 11:22:42 +0000 Subject: [PATCH 167/685] v2.2.1b bug fix --- .../ovo-energy-meter-v2-0.groovy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index 65eed95ab3c..016cb705f07 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -147,9 +147,9 @@ def refreshLiveData() { data.yesterdayTotalPower = (Math.round((totalPower as BigDecimal) * 1000))/1000 def newYesterdayTotalPowerCost = (Math.round((((totalPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 def costYesterdayComparison = calculatePercentChange(newYesterdayTotalPowerCost as BigDecimal, data.yesterdayTotalPowerCost as BigDecimal) - def formattedCostYesterdayComparison + def formattedCostYesterdayComparison = costDailyComparison if (costYesterdayComparison >= 0) { - formattedCostYesterdayComparison = "+" + costYesterdayComparison + formattedCostYesterdayComparison = "+" + formattedCostYesterdayComparison } data.yesterdayTotalPowerCost = newYesterdayTotalPowerCost sendEvent(name: 'yesterdayTotalPower', value: "$data.yesterdayTotalPower", unit: "KWh", displayed: false) @@ -175,9 +175,9 @@ def refreshLiveData() { def costDailyComparison = calculatePercentChange(getTotalDailyPower() as BigDecimal, getYesterdayPower(data.hour) as BigDecimal) def formattedAverageTotalPower = (Math.round((totalDailyPower as BigDecimal) * 1000))/1000 def formattedCurrentTotalPowerCost = (Math.round((((totalDailyPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 - def formattedCostDailyComparison + def formattedCostDailyComparison = costDailyComparison if (costDailyComparison >= 0) { - formattedCostDailyComparison = "+" + costDailyComparison + formattedCostDailyComparison = "+" + formattedCostDailyComparison } //Send event to raise notification on high cost @@ -217,6 +217,7 @@ private def getYesterdayPower(currentHour) { private def calculatePercentChange(current, previous) { def delta = current - previous + log.debug "Calculating %age: DELTA $delta CURRENT $current PREVIOUS $previous" if (previous != 0) { return Math.round((delta / previous) * 100) } else { From 132748f577d04365757cef95a22e32b981eee9e0 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 16 Mar 2016 00:03:39 +0000 Subject: [PATCH 168/685] v2.2.1b - Bug fixes --- .../ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index 016cb705f07..2f2fdda8ec9 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -147,7 +147,7 @@ def refreshLiveData() { data.yesterdayTotalPower = (Math.round((totalPower as BigDecimal) * 1000))/1000 def newYesterdayTotalPowerCost = (Math.round((((totalPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 def costYesterdayComparison = calculatePercentChange(newYesterdayTotalPowerCost as BigDecimal, data.yesterdayTotalPowerCost as BigDecimal) - def formattedCostYesterdayComparison = costDailyComparison + def formattedCostYesterdayComparison = costYesterdayComparison if (costYesterdayComparison >= 0) { formattedCostYesterdayComparison = "+" + formattedCostYesterdayComparison } From 2804072c08ddc9846067d410eb9d350be5d828c8 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 16 Mar 2016 00:08:17 +0000 Subject: [PATCH 169/685] v2.2.1b - Bug fixes --- .../ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index 2f2fdda8ec9..663a116e57c 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -145,13 +145,14 @@ def refreshLiveData() { //Store the day's power info as yesterdays def totalPower = getTotalDailyPower() data.yesterdayTotalPower = (Math.round((totalPower as BigDecimal) * 1000))/1000 + def newYesterdayTotalPowerCost = (Math.round((((totalPower as BigDecimal) * unitPriceBigDecimal) + standingCharge) * 100))/100 def costYesterdayComparison = calculatePercentChange(newYesterdayTotalPowerCost as BigDecimal, data.yesterdayTotalPowerCost as BigDecimal) def formattedCostYesterdayComparison = costYesterdayComparison if (costYesterdayComparison >= 0) { formattedCostYesterdayComparison = "+" + formattedCostYesterdayComparison } - data.yesterdayTotalPowerCost = newYesterdayTotalPowerCost + data.yesterdayTotalPowerCost = String.format("%1.2f",newYesterdayTotalPowerCost) sendEvent(name: 'yesterdayTotalPower', value: "$data.yesterdayTotalPower", unit: "KWh", displayed: false) sendEvent(name: 'yesterdayTotalPowerCost', value: "£$data.yesterdayTotalPowerCost (" + formattedCostYesterdayComparison + "%)", displayed: false) From f82aeb29fbae85292687e4d5009ecd71b3f4fe14 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Wed, 16 Mar 2016 11:23:54 +0000 Subject: [PATCH 170/685] Added icons for SmartApps --- ...3_334250273417145_3395772416845089626_n.png | Bin 0 -> 17048 bytes smartapps/alyc100/icon175x175.jpeg | Bin 0 -> 4141 bytes ...e-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png | Bin 0 -> 480 bytes ...ihome4-01bc8a0e478b385df3248b55cc2df7ca.png | Bin 0 -> 22006 bytes .../alyc100/ovowebsitessuite-carousel.png | Bin 0 -> 24366 bytes ...-frame-6c75d5394d102f52cb8cf73704855446.png | Bin 0 -> 400322 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png create mode 100644 smartapps/alyc100/icon175x175.jpeg create mode 100644 smartapps/alyc100/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png create mode 100644 smartapps/alyc100/mihome4-01bc8a0e478b385df3248b55cc2df7ca.png create mode 100644 smartapps/alyc100/ovowebsitessuite-carousel.png create mode 100644 smartapps/alyc100/thermostat-frame-6c75d5394d102f52cb8cf73704855446.png diff --git a/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png b/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png new file mode 100644 index 0000000000000000000000000000000000000000..5f48118188f3f5beab3fdab703f530f1b5ee46f4 GIT binary patch literal 17048 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 Rcwh(+WqEaIg{(=){{;fZuiO9t literal 0 HcmV?d00001 diff --git a/smartapps/alyc100/icon175x175.jpeg b/smartapps/alyc100/icon175x175.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..09946907f7eeb5b6cb9584cf207ca4445c023cca GIT binary patch literal 4141 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 literal 0 HcmV?d00001 diff --git a/smartapps/alyc100/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png b/smartapps/alyc100/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png new file mode 100644 index 0000000000000000000000000000000000000000..daebf198ac4d2a8586a6482e948c124a92f456d1 GIT binary patch literal 480 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-s%*9TgAsieWw;%dH0CJ53d_r9R z|NsB_*O%vCU)=t1_x$UNho2qW{&3go+w13FTRi#VjP^4<)yEt250xhG%?jTU=e;@D zVV#GU@aiU@cEOS$zhDNSeiR@e>aY+f$ywkLSS(_N_7h7KVm*PC-?d^*jj-O0)Sa(w7nPUQ9`x*&`pgGQQ z@l2OmosNBFeCr&_%P@V{Ue-B}Q=;A->0H$H|Khgm3>W)^pB_1BU;0SpXQs`$b?)0{ z|Fu{jvEz5B;PF7=?^-`su2Jgv=Kd({X0UJ5t0SA8^%XJf4r%V@EK`0lx81wFn0?0&=L;r$1?`%4 zD#kn;A`72iX8OgX5OK)NWuE$(>-QN9rabj#*e5XKJ=2H72abPX3B2akwC<&Y3os}c NJYD@<);T3K0RTKm-n{?- literal 0 HcmV?d00001 diff --git a/smartapps/alyc100/mihome4-01bc8a0e478b385df3248b55cc2df7ca.png b/smartapps/alyc100/mihome4-01bc8a0e478b385df3248b55cc2df7ca.png new file mode 100644 index 0000000000000000000000000000000000000000..8a0e6a804f81753edaec1ce2cee3b440549dc717 GIT binary patch literal 22006 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�^0d1FOi z-$j+tH%3ZW=L}RYxdh}@z(uyITd2mibKk!1Ti-&gmy=y_T#64EU;XOkSAXZ~ zt9V>}^>@B?<=4K7|M8VezY0$Z|9${9Nz(;*Ie`U z%P#vW5B!p^edV&RUC#6Wt#5zpy02LgsX{b`4&Zh|9CSFzkGz8;YMWROOeWh(qhtTK z;${8t{B4+B?au1}>or#+)++%l;zh{#=E}>ye$92)sBzsjSAG3T`G5Rg*c4p#+u#3! z8L_JD0H?vK7j>rFl5MBV-uBBe<2c%P(?M_bl46z;fxL!C9c{N$SAHL`ejDuyF?6Li zz&)?J=6fO1?_KjP_zeHol~-O)p?1~R3Fvj#egE>L8JT`ZMU~glfpu$e_`b6nQmV3Q z58iCaM_E|3$aa9xEQWj4b!f3`z6H?*j(PQW!g0+t-~0aehF;EUU?NF z72_+(xV{HmG=Rkx0PGRwPCsb7ugDIfwN&n;BAdD_S>Myv1={I#ISjj0>iaVA<0TkQ zFvR?#^)3T2(#YTs6myq;dC3>P(ASM8H9F}$MF-SQPCSv+$;y=}HO(DBEC3r7RS~Ek zE*3CF73g|1Sz>v5TiA+=Ven$F4Ot5Y71|2JI_o&e30 zj7~pquFiB&Wu42F-Zg*jtC)3f0}?9bsEkKT7=l{UdJCJ9KkZCQ#N+sKxA^Ouo9LB; z@nd?E;MWaoQ7u8P0F#l9y0U3_b%BpagHw*0x7`=I$MN1$Po<14Akk)Tr_*sK{O=Cv;U0VWJ{9>jT@=e`?G){B4yY3l&ijX4NrV z{oVjpg(wE)dAwS{N&RD6yy^g#(|0rr#EEo~F*iAVQSl@-W^ z3~~cuU3tV&Ra;`e0Np5?;=BH+Yhu}!qyC|vB6Eb|ccqGEkl6V{nN>XZ{ExneWL3jb zB{KG!aquxGe&mAlDuEbcqkzu`GP6Psdg3MfTt4>B1J6B)kNCyBsNhutz5#SpIA}?L zIw=4$IQP5@&iLr53*Y_z`HXbG?ei+BfW7wKckVmhbNI2Re)P->E;#=`D+N-4IOt@q zk0*$7J`ayh69L0`S^7)Xr>Da>Jem++JUFS~Hz7hlD4l!W`JX%e%(Fgv%4x?Q_pT$L zK%p@YfHKnULM0`*?|%Euod@I4|9HodN4)E}g~yzF>ZvE6b<*bx%FqNm7i0r633j=1 zURJ=DAL7&K$ntx7a=9!#iaGH^iYLAMgGU_s-uJ!#(D~s2lrUJSZa1$AtWXRf*trB3 z2K)dT$5kp6ae;(Ppb>LEmqj4T*aQmSA;bWtI2*v%(0BB8!<$Z2=?jnIsh&Si4)~4m zTld>zZo=8CZ&Vg!%?6D+#djvxi7=&yiw_UwHbF@grz$9xKkE&|($WLB(! zTSS*2MERV20Pr0CUrzhVB78R#GNpwCl<*Nk_+_&ZCF0y`+nTxtEJYXLCBy>sF(0bH zC{zmFgcd!a7$PWFlpk+rbOuF$3T0Q3prTVEK0-(s8Rm9`Y9p{%zVP?%&7kK@A=FB_ zy9~xHfOSbOU`<<$;wjeQUgoKe$?l8^}?jHQj_(R}F z%^9im_EyLVz_wI>caq8L^fKZHZ@FAgy$kyU}eBC!IepcON&u{-ri|8`RGLRIqCgVAbGuW26bd zl>)br2xN)>(M=WwtcZ{vgthND5>Ky_RnMy`tO{x$xh--6IBgj{x733+B#CfW;`(lZ+J{K{`}(_kdLs@{DvRmwWH&9di*|P-g~~^OUeO8B&(2!vK2Dwa zd>x>YRr{(i{rv(YM!)b-OkM?gMEJGH{Kc&{;427yLR;$l;Jnw-s$|sOp5ERb^v!aa z0$xk7LJ0&y3V>eZ#HuBOB1KuH6QRDI4nSemQ>pZ$K{2Zelwz?9m3IedQqr$1QFA2! zsE3+U3IKD}>uptV0I(=eeL$xdc-u2o2c)cP2MAVto9*yBKn8XY-cAHtDxLSP*W0Q+ zM5JOxKFEm!g<4w|{$#MNBkMc0!sij*ASc?xea9Vo@aqC~4vxQ`(qz}6 z`fCCr&58rF{Uc1|_2liusD6cKQiF#iLMz5vD_aE5~Z=!a@LV9$MD%&`hJNI3) zcfSmr(^D=L!du$IOWMV?Cw5&YNG`$giQ341TD6eceeiy-+L?IGY(!qTg7>K8a_7DC)moD~G3N9@+;h)0yi1sF5Yg?a@3fGBRf|nK zs7m|=T%et&-+g1|>5%eq2Znm7E;1_JAf?u>)qW6DdvBSduPR)Y=buQcv``%T+Pzp0 z>ZyW2!53|&Ef{Haov`i5tJ(A|0j9oCEw)k+$0~O1ELCV5O>`se?ijN(lv4}hCEGmt z+CtcIB@^3xU%%MuK_*Ovk)$1?#r%pHtI~bSQ4M$e?CO|t`XDCNazR~vr1s;g`ua~ z<@1nlY6i8H=W=fv8OJtS9?dsexfa{I={2`-Qni+&SB+t_TH*J ztd0Hf4|TZrm5}ID;h4>hY1#UH4bAD-4pnrsMR%AR#MIg5hjT%&# T=AM>S00000NkvXXu0mjf2ru$_ literal 0 HcmV?d00001 diff --git a/smartapps/alyc100/ovowebsitessuite-carousel.png b/smartapps/alyc100/ovowebsitessuite-carousel.png new file mode 100644 index 0000000000000000000000000000000000000000..100eb1134072370c69b678ea05b653d3d9fdb98c GIT binary patch literal 24366 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 literal 0 HcmV?d00001 diff --git a/smartapps/alyc100/thermostat-frame-6c75d5394d102f52cb8cf73704855446.png b/smartapps/alyc100/thermostat-frame-6c75d5394d102f52cb8cf73704855446.png new file mode 100644 index 0000000000000000000000000000000000000000..8c221d40fb36fc8b84ebcc93527f54c7310a8484 GIT binary patch literal 400322 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

Pn8O;d4S>oUSFUh4<3{9U=- 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

3T)Sc$T}0AlYAe>S@;$NeeAO!b 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 literal 0 HcmV?d00001 From 20ce054ae55050f8d7ccb8584ea9128c39fd98a9 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 16 Mar 2016 11:33:16 +0000 Subject: [PATCH 171/685] Move external icon references to Github --- .../alyc100/hive-connect.src/hive-connect.groovy | 11 ++++++----- .../alyc100/mihome-connect.src/mihome-connect.groovy | 7 ++++--- .../ovo-energy-connect.src/ovo-energy-connect.groovy | 9 +++++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 7cc0d64897a..4b263d35780 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -18,6 +18,7 @@ * v2.0.1 BETA - Fix bug for accounts that do not have capabilities attribute against thermostat nodes. * 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 * */ definition( @@ -25,8 +26,8 @@ definition( namespace: "alyc100", author: "Alex Lee Yuk Cheung", description: "Connect your Hive devices to SmartThings.", - iconUrl: "https://scontent-lhr3-1.xx.fbcdn.net/hphotos-xfp1/v/t1.0-9/10457773_334250273417145_3395772416845089626_n.png?oh=fb96583154582526d24409597fbccc18&oe=57248CDA", - iconX2Url: "https://scontent-lhr3-1.xx.fbcdn.net/hphotos-xfp1/v/t1.0-9/10457773_334250273417145_3395772416845089626_n.png?oh=fb96583154582526d24409597fbccc18&oe=57248CDA", + 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 ) @@ -74,7 +75,7 @@ def firstPage() { } def headerSECTION() { - return paragraph (image: "https://scontent-lhr3-1.xx.fbcdn.net/hphotos-xfp1/v/t1.0-9/10457773_334250273417145_3395772416845089626_n.png?oh=fb96583154582526d24409597fbccc18&oe=57248CDA", + return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png", "Hive (Connect)\nVersion: 2.1.1\nBuild: 153009032016") } @@ -171,8 +172,8 @@ def selectDevicePAGE() { dynamicPage(name: "selectDevicePAGE", title: "Devices", uninstall: false, install: false) { section { headerSECTION() } section("Select your devices:") { - input "selectedHeating", "enum", image: "https://www.hivehome.com/assets/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://www.hivehome.com/assets/thermostat-frame-6c75d5394d102f52cb8cf73704855446.png", required:false, title:"Select Hive Hot Water Devices \n(${state.hiveHotWaterDevices.size() ?: 0} found)", multiple:true, options:state.hiveHotWaterDevices + 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 } } diff --git a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy index 9655de8180b..62f2deaed27 100644 --- a/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy +++ b/smartapps/alyc100/mihome-connect.src/mihome-connect.groovy @@ -26,14 +26,15 @@ * v1.0.3 - Bug fix to refresh schedule job. * * v1.0.3b - Added icons to MiHome device list. + * v1.0.4 - Move external icon references into Github */ definition( name: "MiHome (Connect)", namespace: "alyc100", author: "Alex Lee Yuk Cheung", description: "Connect your MiHome devices to SmartThings.", - iconUrl: "https://mihome4u.co.uk/assets/homepage/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", - iconX2Url: "https://mihome4u.co.uk/assets/homepage/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", + iconUrl: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", + iconX2Url: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", singleInstance: true ) @@ -69,7 +70,7 @@ def firstPage() { } section("Devices Discovered And Automatically Added...") { state.devices.each {devices -> - paragraph image: "https://mihome4u.co.uk/assets/homepage/mihome4-01bc8a0e478b385df3248b55cc2df7ca.png", devices.trim() + paragraph image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/mihome4-01bc8a0e478b385df3248b55cc2df7ca.png", devices.trim() } } diff --git a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy index ff738e55659..9be14410b5b 100644 --- a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy +++ b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy @@ -18,6 +18,7 @@ * v2.1 - Send notification for daily cost summary and daily usage summary * v2.2 - Support fetching latest unit prices and standing charge from OVO account. * Send notification for specified daily cost level breach. + * v2.2.1 - Move external icon references to Github * */ definition( @@ -25,8 +26,8 @@ definition( namespace: "alyc100", author: "Alex Lee Yuk Cheung", description: "Connect your OVO Energy Account to SmartThings. (Requires OVO Smart Gateway)", - iconUrl: "http://a1.mzstatic.com/eu/r30/Purple69/v4/a8/75/b1/a875b13e-a6f1-fe8d-8063-6f36517fc272/icon175x175.jpeg", - iconX2Url: "http://a1.mzstatic.com/eu/r30/Purple69/v4/a8/75/b1/a875b13e-a6f1-fe8d-8063-6f36517fc272/icon175x175.jpeg", + iconUrl: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/icon175x175.jpeg", + iconX2Url: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/icon175x175.jpeg", singleInstance: true ) @@ -76,7 +77,7 @@ def firstPage() { } def headerSECTION() { - return paragraph (image: "http://a1.mzstatic.com/eu/r30/Purple69/v4/a8/75/b1/a875b13e-a6f1-fe8d-8063-6f36517fc272/icon175x175.jpeg", + return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/icon175x175.jpeg", "OVO Energy (Connect)\nVersion: 2.0\nBuild: 103009032016") } @@ -160,7 +161,7 @@ def selectDevicePAGE() { dynamicPage(name: "selectDevicePAGE", title: "Devices", uninstall: false, install: false) { section { headerSECTION() } section("Select your devices:") { - input "selectedMeters", "enum", image: "https://www.ovoenergy.com/binaries/content/gallery/ovowebsitessuite/images/ovo-answers/ihd-screens-15.png/ihd-screens-15.png/ovowebsitessuite%3Acarousel", required:false, title:"Select Smart Meter Devices \n(${state.smartMeterDevices.size() ?: 0} found)", multiple:true, options:state.smartMeterDevices + input "selectedMeters", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/ovowebsitessuite-carousel.png", required:false, title:"Select Smart Meter Devices \n(${state.smartMeterDevices.size() ?: 0} found)", multiple:true, options:state.smartMeterDevices } } } From fa07b2556773bd457b17b1b76eb553477679a975 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 16 Mar 2016 11:42:11 +0000 Subject: [PATCH 172/685] Update App Headers --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 2 +- .../alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 4b263d35780..b33e58b2fcc 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -76,7 +76,7 @@ def firstPage() { def headerSECTION() { return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png", - "Hive (Connect)\nVersion: 2.1.1\nBuild: 153009032016") + "Hive (Connect)\nVersion: 2.1.2\nDate: 16032016(1130)") } def stateTokenPresent() { diff --git a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy index 9be14410b5b..9fb58514ba3 100644 --- a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy +++ b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy @@ -78,7 +78,7 @@ def firstPage() { def headerSECTION() { return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/icon175x175.jpeg", - "OVO Energy (Connect)\nVersion: 2.0\nBuild: 103009032016") + "OVO Energy (Connect)\nVersion: 2.2.1\nDate: 16032016(1130)") } def stateTokenPresent() { From f9a5da1540a2a855071f90775b2207a27209551d Mon Sep 17 00:00:00 2001 From: alyc100 Date: Wed, 16 Mar 2016 11:45:38 +0000 Subject: [PATCH 173/685] Neato device icons --- devicetypes/alyc100/auto-charge-resume.png | Bin 0 -> 20851 bytes devicetypes/alyc100/best-pet-hair-cleaning.png | Bin 0 -> 23862 bytes devicetypes/alyc100/neato-icons_1x.png | Bin 0 -> 7001 bytes devicetypes/alyc100/neato_staub.png | Bin 0 -> 3091 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 devicetypes/alyc100/auto-charge-resume.png create mode 100644 devicetypes/alyc100/best-pet-hair-cleaning.png create mode 100644 devicetypes/alyc100/neato-icons_1x.png create mode 100644 devicetypes/alyc100/neato_staub.png diff --git a/devicetypes/alyc100/auto-charge-resume.png b/devicetypes/alyc100/auto-charge-resume.png new file mode 100644 index 0000000000000000000000000000000000000000..da53ac07293577a6fee2c59be32b7a61aa13d173 GIT binary patch literal 20851 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 literal 0 HcmV?d00001 diff --git a/devicetypes/alyc100/best-pet-hair-cleaning.png b/devicetypes/alyc100/best-pet-hair-cleaning.png new file mode 100644 index 0000000000000000000000000000000000000000..8df6a0e37f7d135e1f4a37fe89214d3a21cb840b GIT binary patch literal 23862 zcmeI4cUV(dx9|gzpcH9}0#ZakS_q+cP^tnV9Rw+X&><0m1dt|!(v&8M(v&LFMaod) zh@v1My+Z&Ikls7ogU&mX@twKcd%t_1?;r9!d06hV*4lgj_S*Zbb&?oz{hBHz86z18 z1fsm6rlbP`5k(%nk3)bK|5~w6z@HOnHDfmrh@9@=O$2(HcoqbbV6)dV!We04NLitr z1uU&m2&4ek84YNIKr-@Jw563J62pc-+Sr6va{J+%d#7aY6@zi6_IxKYF@5L zU9W3;R$h))lGg0`-@$U z43K18t!<=qlvIB24)~WWyB!9DmV&`NJUj$EgauHpwlKJ)q$EsG2qq*11vH>;o-PL0rNzu2x9p+rU&V{N3y^_BMYm&R;wo zT=~bbBeC}XV0LijhuQZ~__0DVz+9yiU6Ga;l&c;JXLP}7Oz{wBIpL}SdtnF<) z|ILS_l&GMTs05)80?nU&0Gr3!5@Y#a@APxceb=e!q6_ZGO#}z z2{iu*DJeyi6Ur4B7E(@F2KIN&1Xka}OzMh@8^+Sb3VB6I4rmatx3`v(utLC*qL!ji zQ3-2HsECb(B~(((MieR}W+P!GWNC#E7Wx5EQT`` zma9E*%vm}S=p2mxzuU#%xcRrP)a>1W;PfO2S|D`4A7r}7TYqi+*99m0?*|gv($x)l zu-#+&KUxx8`Q7rzViDZRzpv$wRv=LTaunDfNy=ZB`Hy|@ zzbyPYrT=Begzkrh053P-pIX&fpa;W`u%p~4X^LK=t4N4O3}M5u6ti;%{l z@)52>5fLgJ;Uc7QsCo3H ziU)ztprXy+senKnkyn&1>0$e320g0IA*<^bTdt^Susxu^^i(gKG@UNg-=EupTJKn^ zx^_dx2{Uru(D6`igR0r#M_^M*apAb=8);kfo64Gr3bFOQYCE4f$mxe<7l|wB27i zK-ymppMtxIlhKNxpw5=L2kr;W6PI3%&vdI<~rFu4-Ao|Bxw2tj)h`@;}Oct`z`)i zI+9M7v5|&sZMb{Hte}S^T(37#EnF2t*GSE-?8~N1R!GN9_DR#8y@c7k$-C>313h^z zlBF9wOe#<}WC|<uNwyCN4`La_LMkgS&0>>Z1KzyQ$J`*&QZQWV*&Pr(*X}=OG zPR3nv;!>^(dQ{?GwQ(l#aIQn7MC4~s6p@bMAb3sP+?P*$N_PI30Lfn5<*{*E`pwZP zGK3*scIs*d0}IJR(&h^)4NPo83LQ~ILILFw!**#{c{KBabgCx4h%EW;=@79Ls;rzx z$&lfBE`0f^XOQ3<4Q56TS!KkvSIoB_{8FEXgHJVjm>_oQtWHB?>f82E2@faEj@~w@ zE>#=fp4Jno;@y4oIF|R>c!67Egd3R<$HN|Onyy$ z+y_mU?*PPeAi^E*fTytdC}p-U*qY&?j6a@gS;aI!yyGo3r$s0G&Y^^3VOei4$K*89 z|Hajl5Es(YoUsxQx>;gJko+@mRYc3Nn#T>6!x(+-oY^){i5^}auoAM2e<5pS+9QO~ zyb>?g4XL|x{bU@uTL_P4SAnovoZ7?~J7Y?l89;-UE?8ww*5S?|9WG#seCZD2Sox6C z-sMZKZ15&^C|Blkajf!zLsyDhwLU?NX2MJxQib&B(l^?=k%^SKPZ|p1v{(2*-5^zy za3KYe9u?X_H$M0Mks0qbuZ;g{&O4mCra6JCQsxFL&At{u3l}0O3w{;cy&=~!>KL-DvuS4Z z*0EG$;?~VDEl6;mB{3QdCwHcS(GIGu6DOB!-1ye3*G!t_3PZ$V>5^#E2;Y?zZ3(TLr`@*h8 z7uq)m`%`0xQf>NT;x|jxv~qsMjno+h_0kVMd#aghgT+e0Jnv;JS7j+*1S>Ty?5$+z zq&HWAs?JCZBKTt41J*N^b-z@OcuZBPLxR7R3NPHhYsf-HmzG#i*mHGi3FFHmfaP2w zwYV6j30yBa0r%61S}3RMxwpl&qq{v2)7+j!qG+x2k`#!n-wa6huJXTqI$>ksjlUI^ zN6SKbTWtc`GMdej>rnI&q)nqv=NNW|;rSittpuxzcLqEyv}G3ypoOzoe6$`6h%_+c zRgq`;`U?$KR23f84__ucX*Q4Ii}FbKf_ET*Sq9u;>M6qA-sgZMRa->q_mNM@4Cz?( zGwbd~Qczca^u^sGDXmNyy?I&x0ktPes=)K1`l=Enc*U>le3wAWvc}dc&-d)p+~m!U zQNK1ctF0>Ebb^N|d_0cGsH|c!EDji)`jma8OU*pgktl~KEoAe#3QY`cGe@5n3-R2A zinnr~Es+asg-k_)MqL=uu&k|DB!cx35k#qTYtGNgaJNZya^IAY#c_PS(-$&%cc)8g zXl!Z)=`FdSYN0~gQ0?6iDWGN$Sbu@+4k zVl7)(My;Tqbi>oDmOP|iLH0B(YdCW`Y2t-mG7u|4@2@*GzibQ`<1_1)Cmw*9YG|C2 zsQTzXZR%UIf>Y=xPyspCBF(t9^yhQ#+vTa zlW3f-*{B97_F=(*v^*M8fExuz58Tc1;k2SZLJ`0GM~dA}umS%11o zgZ6p%a5@cFz`gJM$Py>1hmwk%)~)^^#MKFN4@lnI^HqOdnJ?D`n&b3V>AV>7#4tzX z?VL;9*>K*$KtIY2P-|W}uL@n+tGJdP8)T{f!n40BeQ@PmuoKVVFprijIg(C7-@k{98Wx&a=zg{aSmE_GR^oNmD4O zp`+idG%X4eo4AO?4JHdqIlUn6cLUoO_1-!7vsn%n{EKF{FV#DG zO5U(>|8>lUWIrdft-fj0H|-0n*CD|T=|HSWSga>D`5F|xecD5^dfQH_nfGIkY9)AV zM~{*1<-_WaW~J_83yq=7x@7}o*?qV6cssKKD703!fzh#}`l98Dg?SCk%jToq%47p; zsSTjX>Z{pNTfpgz{4AY~>Lsp4wB+?z=27ssb7r9#EMh}v-OPOYP8fA*Wf7$=^+P9i z$mXP!FQr z+jrbwnu8f0f6Jnvj_tTD*<#)+UbG%d#?sv*ARVDEGi3MyWzydq@d6lGAJ@dDIa~{!73JJU+CKaYPC?CWu{4AGd1UY6iwM(MAL z8fgJH|GdX6FiwbPt20Si2NoO zma&)a|MKM#WIBg}x}^20WQ%;q`TEUez$D4_4wa~pIWUq3am}8-4)@PZ$oo4=t(-H9 zd00TF;ape)#3a*r-eY)m;KS7%X0MZ4GtaD^3|aWZ5~YeYn80>=PfB&w>JAzQk-DDf zG|kEw+A0laH3lnRjUeEkwq%I(o^uGGO0W4RT+VkY4!msjgGc1Si{?kA^6LV%s;aJDswsEz8*Ic$3B z_Wtn&)RaU8R6%7UN>o9QGO6m^j2o_5uOJ3jjJsyPpXUMKxsg1-Y|CCg0G=+qhk>F%j=YABKFu%7jQN>W6u79 zV*jRWm%v(U{}+5!$Icy_X~Y-Ih(l-VFJbodR)iGNAKsD!k|*BTc^VPe?qErquNov+ z4-NyVX2F(H+P2_uH~;qbVQHydAD|!Jne%u-{4CDa&R22GdCF(_@L>FN-I=7SfIp@E;IEDkKuM?l^z{T@~I!pThiz%d36Q*(YTCs^9N#S2z)zOB`~ zeBVgPAx)9-VBd787>>*M8O^8HG(ova-h9PbQhtxgH2JSGwPzP5fJ!mphMvQ7P5&#n zLSWlm%?GO1VVy}f;P?_Cx&5)%>EwbBUJrjoT~qVbIrJ&?IYD;Yem?)CZLYN&4yQ}w zl|SFAF-Xp68Cun8APZ-xD^J}9 z>Ri~FjR32U`tf556)hP2{8SMRZDAL-KPPCBgyDQ`^PnJ!H1ULtQPHbCYB;Yq+j78s zrkjpfBUtsz9o0`4A?b1?Ro@P>m5BtCg<(d~b6CDY#zOi+vCXuYGf4UcU5l$Ejax9> zdj>@dXz=g}ebIxZ-UUSG5+EAQASW;JtVjq#ap^wzbyS z&5jF7;F<8yxYZaORH19Y%7pkasO1rQzW^jgYyc!EiWc@3Pg#NbdC-$YM+A(H)(_wI(~h+NK{MP6EOMIL{Gjo=bHh(HsF3`?m!lCce*RnhrTKG zitw_#E>{eL(@3#wNB! zz2Rg!Ha?wQmu_J5-2)jvyC1LRc&CDvfsCK1N`*gWn^rL4EVuaE(Fs$;#}RFYey7Xf zfjkLSIbmfCz>S(Bl7B3iMSPl8_ff8yqO2Ds_UUBjvVzw@itiJT#@u9Ko#1nEMNN^> zzBKaD&AJgp?BF^OkHamCi+CR%iEl~O9%q3H>fNc!-t6&ojV6Qf0Q}mAjrLb+A`Q+p(t*G>|V&meS7GQdA zDnkJSpmWg;*uj-|cTO+3HB@i3@sZT%+H4;fCK0_NUeJFv;o^{m^pD7>zgfrYafa@} zvc3ls+WOs^Bx*4J@uFB9QEEO#WK-~F{QF0k;coSawBXGr%@?@%ayz+Gvw9kFVdbAt z&{tiUsrg8Y$3&@&U~6x@>gjAcbr36O=3e=|c(UCtaVKG!o662=E2m6iCOKcfi0UW~ zx&2rdEte%@>?|x?wI!&~;bRVJOoz32eS0f{H%p!9h?zRGn;7IfMRnnQFu&FSi^+q@ z&Q1zho{dsc7p^|4xC>t4&es-CQ>)V6jP`@vd1{AhO&!#DtxdU-=w=CI{IB>TnH_d? zYD4VqZ%D)SuR!mC_`uUyIe~e>lFz&tW%8Ta)3nXm`ix(0DN7{A_n=EoWvW9@r5Dh& zf0Rl2ZO`02v38!Oy}Qbt#*lS*jjRr&y-xC>{BA+@FLoE64B+MX&njWHx8^f+BWEsS z#+gNf$BRYaJoqnUiH#+}qK)Dt2Ip;#y;v0a-Rph20?2F(^wn-t>|0B@|fW*zL5EF)fHtWqNITJ<4>4)hwreF7#Yi*4Fm5-`K}fS9{1Z9MQrWCI&THRp{T%`kZNCU zN@{&>g*Zlhc1Cc&?!0=y67PaKp6?44i{pK$e+qS(!;Nv|Xx!%}PKhB8qKmY%46>X( zDZYrLXhoiy+um~#C&B4`RY9FT5QlTV_s(w0#@Vs?gfE2$n+3(Mr5TSb8j*#JD0w$t zM?{X=cZlbwKRHPx2j({^Wld0$?wrN3`oArDGXN?&UU6;czOR?pqj&G?2Cg@XEXo>k z%x$xhnqH3{lcAN6Bf)0R-vF@^-{y3InvJOfSqqVliV65k+ta8;b-J=ix#Q z-ZWr|$Js`*jGCD7y%XqMRzBcx9_b97nV_%3hNq6{nh|fcoo+O7*pT*4e_$$4wCG#icnqv3tmS1*{D2b2eVck$rcL}D}N>ktt zFErOBRo8PjwaiTbvX^96YeXW!(3ADB)|;zUcJ&=S>+pKJeoG`|*w$C->vUUe-@7U~ zas}v+axQA$3_+nG`(RBs#5l;2SDL9c$2nN}S+p@^!|}}mHpmV$l+?{s%X)sU38f_x z-aX2OxHka3fpMD7QV;>IQ4TjKcy|>S zShsPCflX(-AUMFStpLwni@iuq$8&NeqnxJox?IjZAETuB>x;S~W!p1n{H4!gPyZ&f zo|Th3GF9I4T(m6QhW5dxdS;|o>AmiU7cpQ7 zd8)+7YjEDaipURJf!Jt~l0z*AG_k|szi4g)B)H>R0+Bha za4-p1+EMDVl~(tfn>DSJX4M;923j3@VIEVCEI~dfn7)|lZX6e1`epBuoOp_sVUTkD zhZ%&sm_qt(|9nMp2*@$XP;%i zPgC~kUT3+vH0?54)@;;mwdT|s_{j;=ALd4J@hHBQH+2+6O|FoIYYqwP-_(^ndUL0~ zdWZ06hl=6W>71ERPW~(;cLMFOe$*SxtHW0vOBF)|T2L#zLp4B3FSf;Aq~sje-hRfe zcuKgpQT%|Y5wt3$M`~o~p@eb#JycKoK>>2xDUbgc=>=#m{f-KDs z^NdwEGF(X=Stz+T$AtSXZo+?Ea6#f>l8{;Ld6d~TgM<1&r`$2oN#S~R%J9BW&b8^syW^KySBDTWK(E zIt@1CX1S@SQT%3O-fJN93w;GHU>3Gad*c>f0kBeDv^dlBhSbnm<_y)qOxnSgm-xrr=*1n0&=M+s!SlO! zSw2`dNHYxQD0PpulRNcKKKg2S=6K|Dl3_>LSE}16J5k_{Zt&}ciA`rmY(bTC0k@~| z3f;7SN@DRKm~tzv)Bn4Kv5DGJXa6roarwza>t1WNsY0>zVdqU;DZ1CIn71+DN|JU$ruy zB|5UZ+;?~FS&tLQv+TuOaVU_^=L{UL0wxfu6p<`*Ay_#CvLrKZMsB6;8~xDFBh{m~ zVX7gWq4<%R!kUbQY@HD9i&JrlA&@7E7=5Ll_Oj(^io;V?IgqMB4p@7HNuR*2*i z&{yJG(%YYYmCAnlE+pvrHR8Sd@w8vtFrtgc;l|dj0sdt%wdwO$aj>BU6lwI}*A?n_ zXFf*{)p0sknm$nP6QGibTYhWB8k%Q5q)2fuq4WHkB9TIpLgqqQct3i`soR{i#&luV zkduCy@k#L@92cP@A**(~pe!b2kw5S~r*lrz)PR22#gM2`W6I+D^c0bMLHU7AR{_NIW_FNaBLe6;p%bs0UnhLv9CI1&eCEr!8x( zQT}8jQ%2&qOoD*MRXSjsPAZ8HqoRhJ41xIw4L5q$oQd!Gt=3N%{#)fBeK_yFO$C|B z=RimE(Y4Rejvbe&V~%lbQkOK&8_4aXTc&zR&{j8gO3>~;pjS7)%xB}|M!uOt zErt+Jm9;Sc%XzlIsB z$ucIzt6_jFjERl9EPJVo)DN|l(a$?g*I4PI89wqNLw&A&Dr{MMRIDS7=hQs=*U+bi zZMx#8BmyUe2CprLOlj3v`F{Yx6&QWOwCiM7GlWZj4T)b$W2OfWse~_|RTX&!v$IQy z&F4)93J8rX(^+n1=!uJe%YlEhOGHjVm=Byjg zCUbW*gH{LywdmXBx-EL+QVzx{{q37~!NK>YTRPFAiz#(RN!|ew>1>kszYe~t9Mru4 zeB`&Jh*&UsX$Waei{yffa9yTm3S8 z?^t=j+3=0mSsH7cPW)tAJ`RtWQS5I>FrZri_9p$(J>N2N_H#AU+h+pe4ya}y!ts}sv zl5ds}TRM$G$ulI`QXiY4Uc=MD@bl)!z8cK}k3e-3RrVBWtUj_-5Ai%5{D5Uz@U>t< zxCj6rR;oUU@nx_zCho~5Zy0^WRaI|lK?`^P_#T&%hXA7cqc3=e&o<%KO;^#|x=H8n zcvk3`oamt|FDEe+(HWcx$}o7M^{K;K`Mp@T`z6V$%i#$d6!1tC+$@E~hXuYiN`f$b zCtJsl^PnCRGA#q{R25yZxZXOwb0`d?b@w&XCrC{ki%$IlecX*%I^2o64-WqI0Wi>g z6A_&fNuVJ8a{9+NO8!4Q`2@b<^3#LX|M`>8{~b^C0Y87U26!y~)7NkQ^%Wd~N3#EC iN(4`)V~6%nf(9OCWq9THNgw>r=2w)jDdk_bxceVOG9r`! literal 0 HcmV?d00001 diff --git a/devicetypes/alyc100/neato-icons_1x.png b/devicetypes/alyc100/neato-icons_1x.png new file mode 100644 index 0000000000000000000000000000000000000000..a7b225237a873011b05654d8302e43d733d3e3e0 GIT binary patch literal 7001 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 Date: Wed, 16 Mar 2016 11:48:56 +0000 Subject: [PATCH 174/685] Neato device icons --- devicetypes/alyc100/laser-guided-navigation.png | Bin 0 -> 5879 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 devicetypes/alyc100/laser-guided-navigation.png diff --git a/devicetypes/alyc100/laser-guided-navigation.png b/devicetypes/alyc100/laser-guided-navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..39adbd99495c291ffeb23bf4a84197e7a5f5589e GIT binary patch literal 5879 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 literal 0 HcmV?d00001 From d5a879e6a77b94a3c73759b10288a77ce9b0b05b Mon Sep 17 00:00:00 2001 From: alyc100 Date: Wed, 16 Mar 2016 12:09:47 +0000 Subject: [PATCH 175/685] Neato device icons --- devicetypes/alyc100/battery_low.png | Bin 0 -> 26258 bytes devicetypes/alyc100/battery_ok.png | Bin 0 -> 25092 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 devicetypes/alyc100/battery_low.png create mode 100644 devicetypes/alyc100/battery_ok.png diff --git a/devicetypes/alyc100/battery_low.png b/devicetypes/alyc100/battery_low.png new file mode 100644 index 0000000000000000000000000000000000000000..00eb5f0b0fc9631e450a9562f568eaa6f69662ef GIT binary patch literal 26258 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 Hxukyp>+%y4 literal 0 HcmV?d00001 From db2d654d7491d1605a7ca6f9b72fd6d26f1000f0 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Wed, 16 Mar 2016 12:15:10 +0000 Subject: [PATCH 176/685] Neato device icons --- .../alyc100/16-battery-100-plugged-512x512.png | Bin 0 -> 6857 bytes .../alyc100/17-battery-50-plugged-512x512.png | Bin 0 -> 6308 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 devicetypes/alyc100/16-battery-100-plugged-512x512.png create mode 100644 devicetypes/alyc100/17-battery-50-plugged-512x512.png diff --git a/devicetypes/alyc100/16-battery-100-plugged-512x512.png b/devicetypes/alyc100/16-battery-100-plugged-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..425df5b95a002133510d982d854e56adaf7f7bf3 GIT binary patch literal 6857 zcmbVRc|26@+rMYAWqpdsUd=G5hzLbfQqE*wGlb$GZ~Yd8H>gDvzm#Q2fK zosPAXt{L6NuWVDDlaqIckx;FS5j*g;0{?l&dG-6-%Sk=JtHp-~M&R3j_5ZDofJP;zGqnd` zTK}bhgul5-^l@ zv$K^0wFGA)e+zWB85gNTQv2Xe2zEfF-rF5GD6w>s_60c-?|K>?Lu)gagJ?kQdj-Wm z43(m%CUn;7?`TUUco6KLvCr{5|e?kYTliGYvCCjvg(G@l3cYTd*F zE%rNf{GRmJ;?vc8!;u)boRzl~WNLK7QQH*uSJfE@bX~S^7q`Tv1+N9sV_vf*zue=SiAJ z4=dOwyV_-v0VDSUI~_?N1Xp{K@1H&7Z;$H5V==T`!IgmakkW1|Yh2dTIV2*{wa`)? zn8!Cn5(*q{RoDq8TJEZNUH(U=%i$`1hJvZiVBFR>Ar)15Ye!^e0k(vKJb$QS_)et#M$gtv*$kF)BUoHY9>Umyk zH3OWc3ezL*{Ly27xLisLn`Ww-`R7o`MCmw|CQ_FxIs(+&!(aEe%9b!zspJ#`|I zQQbE=L$3}HCpU^3o;IS+TqB>ENbPjOh?BTEY z#!$!wFHK6*IJRH)P{C*i-F7y{ST`NfWG_z{`8%Ok2wwCLgIfpxcxj~mAbfWB&e@3R zK@Bvl-ys^1E6)~nPlei{Q-mLT(HH24Ph0Qt>{967!+e1*g^$kWFYDEY^Mb$vQnZuT zbl%E#W>fpm++%h>;sz54&VGIx?IF^0HR$d-?#GMU`t(PiOVUQqSqerpeIwKmb(jOM zMX5dd&xW!(+;XcGLtlu1k;V!!_Hy4Xme|Qxvj%(eDbvD8pYyl=_MJhSKr7u|Y^B$f zGTlbFRCz^T1q~xGnPMw4dO{KyTDtdkAU4Kij2TLuWZ>#W7hS0Hvj$=x5?K9l7(B@8oIv!m1*~5Vzi(22`*%sM zY@UP@3kX6V#A8A{1cB7dtI{JUW^dop(_>zxFZrP1Z$gF|)=5Vic!g8P4%#YIX)y

Dl*YMtZ()wl&0Q-?mpeT= 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 literal 0 HcmV?d00001 diff --git a/devicetypes/alyc100/17-battery-50-plugged-512x512.png b/devicetypes/alyc100/17-battery-50-plugged-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..6469f23935cc349e05d4582b57954bc03a746c1d GIT binary patch literal 6308 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 Date: Wed, 16 Mar 2016 12:15:25 +0000 Subject: [PATCH 177/685] Delete battery_ok.png --- devicetypes/alyc100/battery_ok.png | Bin 25092 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 devicetypes/alyc100/battery_ok.png diff --git a/devicetypes/alyc100/battery_ok.png b/devicetypes/alyc100/battery_ok.png deleted file mode 100644 index 665a45393083f0fc620903e6311b30f5401fa840..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25092 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 Hxukyp>+%y4 From 1686d11f02662c7ce1192fc8077ad61f6cc1889b Mon Sep 17 00:00:00 2001 From: alyc100 Date: Wed, 16 Mar 2016 12:15:31 +0000 Subject: [PATCH 178/685] Delete battery_low.png --- devicetypes/alyc100/battery_low.png | Bin 26258 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 devicetypes/alyc100/battery_low.png diff --git a/devicetypes/alyc100/battery_low.png b/devicetypes/alyc100/battery_low.png deleted file mode 100644 index 00eb5f0b0fc9631e450a9562f568eaa6f69662ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26258 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 Date: Wed, 16 Mar 2016 14:54:18 +0000 Subject: [PATCH 179/685] Neato Version: 1.0.4 - Added Neato icons --- .../neato-botvac-connected.groovy | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy index 283fa822e5d..c3d766c8fde 100644 --- a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy +++ b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy @@ -28,6 +28,7 @@ * Neato Version: 1.0.1 - Improved Botvac connection detection * Neato Version: 1.0.2 - Added Please Clear My Path Error message * Neato Version: 1.0.3 - Added Navigation No Progress Error message + * Neato Version: 1.0.4 - Added Neato icons */ import groovy.json.JsonSlurper @@ -55,8 +56,8 @@ metadata { tiles(scale: 2) { multiAttributeTile(name: "clean", width: 6, height: 4, type:"lighting") { tileAttribute("device.switch", key:"PRIMARY_CONTROL", canChangeBackground: true){ - attributeState("off", label: 'STOPPED', action: "on", icon: "st.Appliances.appliances13", backgroundColor: "#ffffff") - attributeState("on", label: 'CLEANING', action: "off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821") + attributeState("off", label: 'STOPPED', action: "on", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png", backgroundColor: "#ffffff") + attributeState("on", label: 'CLEANING', action: "off", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/best-pet-hair-cleaning.png", backgroundColor: "#79b821") } tileAttribute ("statusMsg", key: "SECONDARY_CONTROL") { attributeState "statusMsg", label:'${currentValue}' @@ -89,16 +90,16 @@ metadata { } standardTile("status", "device.status", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { state ("unknown", label:'${currentValue}', icon: "st.unknown.unknown.unknown") - state ("cleaning", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#79b821") - state ("ready", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#79b821") - state ("error", label:'${currentValue}', icon: "st.Appliances.appliances13", backgroundColor: "#bc2323") - state ("paused", label:'${currentValue}', icon: "st.Appliances.appliances13") + state ("cleaning", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/best-pet-hair-cleaning.png") + state ("ready", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png") + state ("error", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png", backgroundColor: "#bc2323") + state ("paused", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png") } standardTile("dockStatus", "device.dockStatus", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("docked", label:'Docked', icon: "st.Transportation.transportation13", backgroundColor: "#79b821") - state ("dockable", label:'Send To Dock', action: "dock", icon: "st.Transportation.transportation2", backgroundColor: "#E5E500") - state ("undocked", label:'Undocked', icon: "st.Transportation.transportation13", backgroundColor: "#E5E500") + state ("docked", label:'DOCKED', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/auto-charge-resume.png") + state ("dockable", label:'DOCK', action: "dock", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato_staub.png") + state ("undocked", label:'UNDOCKED', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png") } standardTile("scheduled", "device.scheduled", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { From 7ad0538256194dd861d2e3d9957c345ec8911d1a Mon Sep 17 00:00:00 2001 From: alyc100 Date: Wed, 16 Mar 2016 15:40:40 +0000 Subject: [PATCH 180/685] Delete 16-battery-100-plugged-512x512.png --- .../alyc100/16-battery-100-plugged-512x512.png | Bin 6857 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 devicetypes/alyc100/16-battery-100-plugged-512x512.png diff --git a/devicetypes/alyc100/16-battery-100-plugged-512x512.png b/devicetypes/alyc100/16-battery-100-plugged-512x512.png deleted file mode 100644 index 425df5b95a002133510d982d854e56adaf7f7bf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6857 zcmbVRc|26@+rMYAWqpdsUd=G5hzLbfQqE*wGlb$GZ~Yd8H>gDvzm#Q2fK zosPAXt{L6NuWVDDlaqIckx;FS5j*g;0{?l&dG-6-%Sk=JtHp-~M&R3j_5ZDofJP;zGqnd` zTK}bhgul5-^l@ zv$K^0wFGA)e+zWB85gNTQv2Xe2zEfF-rF5GD6w>s_60c-?|K>?Lu)gagJ?kQdj-Wm z43(m%CUn;7?`TUUco6KLvCr{5|e?kYTliGYvCCjvg(G@l3cYTd*F zE%rNf{GRmJ;?vc8!;u)boRzl~WNLK7QQH*uSJfE@bX~S^7q`Tv1+N9sV_vf*zue=SiAJ z4=dOwyV_-v0VDSUI~_?N1Xp{K@1H&7Z;$H5V==T`!IgmakkW1|Yh2dTIV2*{wa`)? zn8!Cn5(*q{RoDq8TJEZNUH(U=%i$`1hJvZiVBFR>Ar)15Ye!^e0k(vKJb$QS_)et#M$gtv*$kF)BUoHY9>Umyk zH3OWc3ezL*{Ly27xLisLn`Ww-`R7o`MCmw|CQ_FxIs(+&!(aEe%9b!zspJ#`|I zQQbE=L$3}HCpU^3o;IS+TqB>ENbPjOh?BTEY z#!$!wFHK6*IJRH)P{C*i-F7y{ST`NfWG_z{`8%Ok2wwCLgIfpxcxj~mAbfWB&e@3R zK@Bvl-ys^1E6)~nPlei{Q-mLT(HH24Ph0Qt>{967!+e1*g^$kWFYDEY^Mb$vQnZuT zbl%E#W>fpm++%h>;sz54&VGIx?IF^0HR$d-?#GMU`t(PiOVUQqSqerpeIwKmb(jOM zMX5dd&xW!(+;XcGLtlu1k;V!!_Hy4Xme|Qxvj%(eDbvD8pYyl=_MJhSKr7u|Y^B$f zGTlbFRCz^T1q~xGnPMw4dO{KyTDtdkAU4Kij2TLuWZ>#W7hS0Hvj$=x5?K9l7(B@8oIv!m1*~5Vzi(22`*%sM zY@UP@3kX6V#A8A{1cB7dtI{JUW^dop(_>zxFZrP1Z$gF|)=5Vic!g8P4%#YIX)y

JA2U1N#AOq=bo4%wKLHg;y5ZT&tbv_VVCQX=Js-rOC+^;^f2woI?htI z76}cHa~h8rX!W7kbyI`1yE#Kd6GX4`M1`=h<;kq+YZEDkov>$>=c3uK$D9TmxZ^6T z*1y$iU~Jl8F_+cRw{B!|WZ^NP5*SEji$9(h6tZ&jT~F)gSN(7-zPi@ZZ@fcUY@l&w-~n6wy_p?ZS=zM7=WFeV z%|Xu9T=~*{dT&-wyD{UL;Rq;f)VGPFd+Q6U*yolT{z$d^5uR17X%L{ddrxWGJ2*hO2f zo*d)U%~BMb^X-BBH@Y2vNom|xZ1PNyUk$?g`wO9|JHq`!M9pq zEVnLRT%UM8cZ|o%14-vvjscxW?a8#zChjI_R-B`A^*8WH;c

@st*uaV;Y)7KG7@ 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 z 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 JReSvMe*l*!lH~vZ literal 0 HcmV?d00001 From 8f5ea4fadf16b77f9f7e47cb41a4a0b93a0aaa06 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Thu, 13 Oct 2016 11:20:46 +0100 Subject: [PATCH 211/685] Add files via upload --- devicetypes/alyc100/neato_botvac_image.png | Bin 0 -> 299127 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 devicetypes/alyc100/neato_botvac_image.png diff --git a/devicetypes/alyc100/neato_botvac_image.png b/devicetypes/alyc100/neato_botvac_image.png new file mode 100644 index 0000000000000000000000000000000000000000..e97fca2dfecea3b90f981a50f0777515dc3bbe41 GIT binary patch literal 299127 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 z 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 literal 0 HcmV?d00001 From afa27f8a1ced43eb088513dbdd9e6f237425250b Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 14 Oct 2016 18:15:59 +0100 Subject: [PATCH 212/685] V1.0 - Neato (Connect) Initial Version --- .../neato-connect.src/neato-connect.groovy | 720 ++++++++++++++++++ 1 file changed, 720 insertions(+) create mode 100644 smartapps/alyc100/neato-connect.src/neato-connect.groovy diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy new file mode 100644 index 00000000000..b0ef39b390e --- /dev/null +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -0,0 +1,720 @@ +/** + * Neato (Connect) + * + * Copyright 2016 Alex Lee Yuk Cheung + * + * 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 + * 14-10-2016: 1.0 - Initial Version + */ +definition( + name: "Neato (Connect)", + namespace: "alyc100", + author: "Alex Lee Yuk Cheung", + description: "Integration to Neato Robotics Connected Series robot vacuums", + category: "", + iconUrl: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/neato_icon.png", + iconX2Url: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/neato_icon.png", + iconX3Url: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/neato_icon.png", + oauth: true) + +{ + appSetting "clientId" + appSetting "clientSecret" +} + + +preferences { + page(name: "auth", title: "Neato", nextPage:"", content:"authPage", uninstall: true, install:true) + page(name: "selectDevicePAGE") + page(name: "preferencesPAGE") + page(name: "notificationsPAGE") +} + +mappings { + path("/oauth/initialize") {action: [GET: "oauthInitUrl"]} + path("/oauth/callback") {action: [GET: "callback"]} +} + +def authPage() { + log.debug "authPage()" + + if(!atomicState.accessToken) { //this is to access token for 3rd party to make a call to connect app + atomicState.accessToken = createAccessToken() + } + + def description + def uninstallAllowed = false + def oauthTokenProvided = false + + if(atomicState.authToken) { + description = "You are connected." + uninstallAllowed = true + oauthTokenProvided = true + } else { + description = "Click to enter Neato Credentials" + } + + def redirectUrl = buildRedirectUrl + log.debug "RedirectUrl = ${redirectUrl}" + // get rid of next button until the user is actually auth'd + if (!oauthTokenProvided) { + return dynamicPage(name: "auth", title: "Login", nextPage: "", uninstall:uninstallAllowed) { + section { headerSECTION() } + section() { + paragraph "Tap below to log in to the Neato service and authorize SmartThings access." + href url:redirectUrl, style:"embedded", required:true, title:"Neato", description:description + } + } + } else { + updateDevices() + dynamicPage(name: "auth", uninstall: false, install: false) { + section { headerSECTION() } + section ("Choose your Neato Botvacs:") { + href("selectDevicePAGE", title: null, description: devicesSelected() ? "Devices:\n " + getDevicesSelectedString() : "Tap to select your Neato Botvacs", state: devicesSelected()) + } + section ("Preferences:") { + href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure preferences", state: preferencesSelected()) + } + section ("Notifications:") { + href("notificationsPAGE", title: null, description: notificationsSelected() ? getNotificationsString() : "Tap to configure notifications", state: notificationsSelected()) + } + def botvacList = "" + selectedBotvacs.each() { + def childDevice = getChildDevice("${it}") + try { + botvacList += "${childDevice.displayName} is ${childDevice.currentStatus}. Battery is ${childDevice.currentBattery}%\n" + } + catch (e) { + log.trace "Error checking status." + log.trace e + } + if (botvacList) { + section("Botvac Status:") { + paragraph image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato_botvac_image.png", botvacList.trim() + } + } + } + } + } +} + +def selectDevicePAGE() { + updateDevices() + dynamicPage(name: "selectDevicePAGE", title: "Devices", uninstall: false, install: false) { + section { headerSECTION() } + section() { + paragraph "Tap below to see the list of Neato Botvacs available in your Neato account and select the ones you want to connect to SmartThings." + input "selectedBotvacs", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato_botvac_image.png", required:false, title:"Select Neato Devices \n(${state.botvacDevices.size() ?: 0} found)", multiple:true, options:state.botvacDevices + } + } +} + +def notificationsPAGE() { + return dynamicPage(name: "notificationsPAGE", title: "Notifications", install: false, uninstall: false) { + section(""){ + input "sendPush", "bool", title: "Send as Push?", required: false, defaultValue: true + input "sendSMS", "phone", title: "Send as SMS?", required: false, defaultValue: null + input "sendBotvacOn", "bool", title: "Notify when on?", required: false, defaultValue: false + input "sendBotvacOff", "bool", title: "Notify when off?", required: false, defaultValue: false + input "sendBotvacError", "bool", title: "Notify on error?", required: false, defaultValue: true + input "sendBotvacBin", "bool", title: "Notify on full bin?", required: false, defaultValue: true + } + } +} + +def preferencesPAGE() { + return dynamicPage(name: "preferencesPAGE", title: "Preferences", install: false, uninstall: false) { + + section("Force Clean"){ + paragraph "If Botvac has been inactive for a number of days specified, then force a clean." + input "forceClean", "bool", title: "Force clean after elapsed time?", required: false, defaultValue: false, submitOnChange: true + if (forceClean) { + input ("forceCleanDelay", "number", title: "Number of days before force clean (in days)", required: false, defaultValue: 7) + } + } + section("Auto Dock") { + paragraph "When Botvac is paused, automatically send to base after a specified number of seconds." + input "autoDock", "bool", title: "Auto dock Botvac after pause?", required: false, defaultValue: true, submitOnChange: true + if (autoDock) { + input ("autoDockDelay", "number", title: "Auto dock delay after pause (in seconds)", required: false, defaultValue: 60) + } + } + section("Auto Smart Home Monitor..."){ + paragraph "If Smart Home Monitor is set to Arm(Away), auto Set Smart Home Monitor to Arm(Stay) when cleaning and reset when done. If Smart Home Monitor is Disarmed during cleaning, then this will not reactivate SHM." + input "autoSHM", "bool", title: "Auto Set Smart Home Monitor?", required: true, multiple: true, defaultValue: false, submitOnChange: true + + } + } +} + +def headerSECTION() { + return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/neato_icon.png", + "${textVersion()}") +} + +def oauthInitUrl() { + log.debug "oauthInitUrl with callback: ${callbackUrl}" + + atomicState.oauthInitState = UUID.randomUUID().toString() + + def oauthParams = [ + response_type: "code", + scope: "public_profile control_robots", + client_id: clientId(), + state: atomicState.oauthInitState, + redirect_uri: callbackUrl + ] + + redirect(location: "${apiEndpoint}/oauth2/authorize?${toQueryString(oauthParams)}") +} + +// The toQueryString implementation simply gathers everything in the passed in map and converts them to a string joined with the "&" character. +String toQueryString(Map m) { + return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&") +} + +def callback() { + log.debug "callback()>> params: $params, params.code ${params.code}" + + def code = params.code + def oauthState = params.state + + if (oauthState == atomicState.oauthInitState) { + def tokenParams = [ + grant_type: "authorization_code", + code : code, + client_id : clientId(), + client_secret: clientSecret(), + redirect_uri: callbackUrl + ] + + def tokenUrl = "https://beehive.neatocloud.com/oauth2/token?${toQueryString(tokenParams)}" + + httpPost(uri: tokenUrl) { resp -> + atomicState.refreshToken = resp.data.refresh_token + atomicState.authToken = resp.data.access_token + } + + if (atomicState.authToken) { + success() + } else { + fail() + } + + } else { + log.error "callback() failed oauthState != atomicState.oauthInitState" + } + +} + +// Example success method +def success() { + def message = """ +

Your Neato Account is now connected to SmartThings!

+

Click 'Done' to finish setup.

+ """ + displayMessageAsHtml(message) +} + +def fail() { + def message = """ +

The connection could not be established!

+

Click 'Done' to return to the menu.

+ """ + displayMessageAsHtml(message) +} + +def displayMessageAsHtml(message) { + def redirectHtml = "" + if (redirectUrl) { redirectHtml = """""" } + + def html = """ + + + + + SmartThings & Neato connection + + + +
+ SmartThings logo + connected device icon + nest icon + ${message} +
+ + + """ + render contentType: 'text/html', data: html +} + +private refreshAuthToken() { + log.debug "refreshing auth token" + + if(!atomicState.refreshToken) { + log.warn "Can not refresh OAuth token since there is no refreshToken stored" + } else { + def refreshParams = [ + method: 'POST', + uri : "https://beehive.neatocloud.com", + path : "/oauth2/token", + query : [grant_type: 'refresh_token', refresh_token: "${atomicState.refreshToken}"], + ] + + def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Neato (Connect) SmartApp and re-enter your account login credentials." + //changed to httpPost + try { + def jsonMap + httpPost(refreshParams) { resp -> + if(resp.status == 200) { + log.debug "Token refreshed...calling saved RestAction now!" + debugEvent("Token refreshed ... calling saved RestAction now!") + saveTokenAndResumeAction(resp.data) + } + } + } catch (groovyx.net.http.HttpResponseException e) { + log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}" + def reAttemptPeriod = 300 // in sec + if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc. + runIn(reAttemptPeriod, "refreshAuthToken") + } else if (e.statusCode == 401) { // unauthorized + atomicState.reAttempt = atomicState.reAttempt + 1 + log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}" + if (atomicState.reAttempt <= 3) { + runIn(reAttemptPeriod, "refreshAuthToken") + } else { + sendPushAndFeeds(notificationMessage) + atomicState.reAttempt = 0 + } + } + } + } +} +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + unsubscribe() + initialize() +} + +def initialize() { + // TODO: subscribe to attributes, devices, locations, etc. + if (selectedBotvacs) + addBotvacs() + unschedule() + runEvery5Minutes('pollOn') // Asynchronously refresh devices so we don't block + + //subscribe to events for notifications if activated + if (preferencesSelected() == "complete" || notificationsSelected() == "complete") { + getChildDevices().each { childDevice -> + subscribe(childDevice, "status.cleaning", eventHandler, [filterEvents: false]) + subscribe(childDevice, "status.ready", eventHandler, [filterEvents: false]) + subscribe(childDevice, "status.error", eventHandler, [filterEvents: false]) + subscribe(childDevice, "status.paused", eventHandler, [filterEvents: false]) + subscribe(childDevice, "bin.full", eventHandler, [filterEvents: false]) + } + } +} + +def uninstalled() { + log.info("Uninstalling, removing child devices...") + unschedule() + removeChildDevices(getChildDevices()) +} + + +def updateDevices() { + log.debug "Executing 'updateDevices'" + if (!state.devices) { + state.devices = [:] + } + def devices = devicesList() + state.botvacDevices = [:] + def selectors = [] + devices.each { device -> + if (device.serial != null) { + selectors.add("${device.serial}|${device.secret_key}") + def value + value = "Neato Botvac - " + device.name + def key = device.serial + "|" + device.secret_key + state.botvacDevices["${key}"] = value + } + } + log.debug selectors + //Remove devices if does not exist on the Neato platform + getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { + log.info("Deleting ${it.deviceNetworkId}") + try { + deleteChildDevice(it.deviceNetworkId) + } catch (physicalgraph.exception.NotFoundException e) { + log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") + } catch (physicalgraph.exception.ConflictException ce) { + log.info("Device ${it.deviceNetworkId} in use. Please manually delete.") + } + } +} + +def addBotvacs() { + log.debug "Executing 'addBotvacs'" + updateDevices() + + selectedBotvacs.each { device -> + + def childDevice = getChildDevice("${device}") + + if (!childDevice) { + log.info("Adding Neato Botvac device ${device}: ${state.botvacDevices[device]}") + + def data = [ + name: state.botvacDevices[device], + label: state.botvacDevices[device], + ] + childDevice = addChildDevice(app.namespace, "Neato Botvac Connected Series", "$device", null, data) + childDevice.refresh() + + log.debug "Created ${state.botvacDevices[device]} with id: ${device}" + } else { + log.debug "found ${state.botvacDevices[device]} with id ${device} already exists" + } + + } +} + +private removeChildDevices(devices) { + devices.each { + deleteChildDevice(it.deviceNetworkId) // 'it' is default + } +} + +def devicesList() { + logErrors([]) { + def resp = beehiveGET("/users/me/robots") + if (resp.status == 200) { + return resp.data + } else { + log.error("Non-200 from device list call. ${resp.status} ${resp.data}") + return [] + } + } +} + +def devicesSelected() { + return (selectedBotvacs) ? "complete" : null +} + +def getDevicesSelectedString() { + if (state.botvacDevices == null) { + updateDevices() + } + def listString = "" + selectedBotvacs.each { childDevice -> + if (listString == "") { + if (null != state.botvacDevices) { + listString += "• " + state.botvacDevices[childDevice] + } + } + else { + if (null != state.botvacDevices) { + listString += "\n• " + state.botvacDevices[childDevice] + } + } + } + return listString +} + +def preferencesSelected() { + return (forceClean || autoDock || autoSHM) ? "complete" : null +} + +def getPreferencesString() { + def listString = "" + if (forceClean) listString += "• Force clean after ${forceCleanDelay} days\n" + if (autoDock) listString += "• Auto Dock after ${autoDockDelay} seconds\n" + if (autoSHM) listString += "• Automatically set Smart Home Monitor\n" + + if (listString != "") listString = listString.substring(0, listString.length() - 1) + return listString +} + +def notificationsSelected() { + return (sendPush || sendSMS != null) && (sendBotvacOn || sendBotvacOff || sendBotvacError || sendBotvacBin) ? "complete" : null +} + +def getNotificationsString() { + def listString = "" + if (sendPush) listString += "• Send Push\n" + if (sendSMS != null) listString += "• Send SMS to ${sendSMS}\n" + if (sendBotvacOn) listString += "• Botvac On Notification\n" + if (sendBotvacOff) listString += "• Botvac Off Notification\n" + if (sendBotvacError) listString += "• Botvac Error Notification\n" + if (sendBotvacBin) listString += "• Bin Full Notification\n" + if (listString != "") listString = listString.substring(0, listString.length() - 1) + return listString +} + +//Beehive API Access +def beehiveGET(path, body = [:]) { + try { + log.debug("Beginning API GET: ${beehiveURL(path)}, ${beehiveRequestHeaders()}") + + httpGet(uri: beehiveURL(path), contentType: 'application/json', headers: beehiveRequestHeaders()) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + } +} + +Map beehiveRequestHeaders() { + return [ + 'Accept': 'application/vnd.neato.nucleo.v1', + 'Content-Type': 'application/*+json', + 'X-Agent': '0.11.3-142', + 'Authorization': "Bearer ${atomicState.authToken}" + ] +} + +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()}") + return options.errorReturn + } catch (java.net.SocketTimeoutException e) { + log.warn "Connection timed out, not much we can do here" + return options.errorReturn + } +} + +// Implement event handlers +def eventHandler(evt) { + log.debug "Executing 'eventHandler' for ${evt.displayName}" + def msg + if (evt.value == "paused") { + log.trace "Setting auto dock for ${evt.displayName}" + //If configured, set to dock automatically after one minute. + if (autoDock == true) { + runIn(autoDockDelay, scheduleAutoDock) + } + } + else if (evt.value == "error") { + unschedule() + runEvery5Minutes('pollOn') + sendEvent(linkText:app.label, name:"${evt.displayName}", value:"error",descriptionText:"${evt.displayName} has an error", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "${evt.displayName} has an error" + msg = "${evt.displayName} has an error" + if (sendBotvacError == true) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } + } + } + else if (evt.value == "cleaning") { + unschedule() + //Increase poll interval during cleaning + schedule("0 0/1 * * * ?", pollOn) + if (state.lastClean == null) { + state.lastClean = [:] + } + //Record last cleaning time for device + state.lastClean[evt.displayName] = now() + sendEvent(linkText:app.label, name:"${evt.displayName}", value:"on",descriptionText:"${evt.displayName} is on", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "${evt.displayName} is on" + msg = "${evt.displayName} is on" + if (sendBotvacOn == true) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } + } + if (settings.autoSHM.contains('true') ) { + if (location.currentState("alarmSystemStatus")?.value == "away") { + sendEvent(linkText:app.label, name:"Smart Home Monitor", value:"stay",descriptionText:"Smart Home Monitor was set to stay", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "Smart Home Monitor is set to stay" + sendLocationEvent(name: "alarmSystemStatus", value: "stay") + state.autoSHMchange = "y" + sendPush("Smart Home Monitor is set to stay as ${evt.displayName} is on") + } + } + } + else if (evt.value == "full") { + unschedule() + runEvery5Minutes('pollOn') + sendEvent(linkText:app.label, name:"${evt.displayName}", value:"bin full",descriptionText:"${evt.displayName} bin is full", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "${evt.displayName} bin is full" + msg = "${evt.displayName} bin is full" + if (sendBotvacBin == true) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } + } + } + else if (evt.value == "ready") { + unschedule() + runEvery5Minutes('pollOn') + sendEvent(linkText:app.label, name:"${evt.displayName}", value:"off",descriptionText:"${evt.displayName} is off", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "${evt.displayName} is off" + msg = "${evt.displayName} is off" + if (sendBotvacOff == true) { + if (settings.sendSMS != null) { + sendSms(sendSMS, msg) + } + if (settings.sendPush == true) { + sendPush(msg) + } + } + } +} + +def scheduleAutoDock() { + getChildDevices().each { childDevice -> + if (childDevice.latestState('status').stringValue == 'paused') { + childDevice.dock() + } + } +} + +def pollOn() { + log.debug "Executing 'pollOn'" + + def activeCleaners = false + + getChildDevices().each { childDevice -> + state.pollState = now() + childDevice.poll() + //Force on if last clean was a long time ago + if (childDevice.currentSwitch == "off" && forceClean && state.lastClean != null && state.lastClean[childDevice.displayName] != null) { + def t = now() - state.lastClean[childDevice.displayName] + log.debug "$childDevice.displayName last cleaned at " + state.lastClean[childDevice.displayName] + ". $t milliseconds has elapsed since." + if (t > (forceCleanDelay * 86400000)) { + log.debug "Force clean activated as $t milliseconds has elapsed" + sendPush(childDevice.displayName + " has not cleaned for " + forceCleanDelay + " days. Forcing a clean.") + childDevice.on() + } + } + //Search for active cleaners + if (childDevice.latestState('status').stringValue == 'cleaning') { + activeCleaners = true + } + } + + if (!activeCleaners) { + if (settings.autoSHM.contains('true') ) { + if (location.currentState("alarmSystemStatus")?.value == "stay" && state.autoSHMchange == "y"){ + sendEvent(linkText:app.label, name:"Smart Home Monitor", value:"away",descriptionText:"Smart Home Monitor was set back to away", eventType:"SOLUTION_EVENT", displayed: true) + log.trace "Smart Home Monitor is set back to away" + sendLocationEvent(name: "alarmSystemStatus", value: "away") + state.autoSHMchange = "n" + sendPush("Smart Home Monitor is set to away as all cleaners are off") + } + } + } + + //If SHM is disarmed because of external event, then disable auto SHM mode + if (location.currentState("alarmSystemStatus")?.value == "off") { + state.autoSHMchange = "n" + } +} + + +def getChildName() { return "Neato BotVac" } +def getServerUrl() { return "https://graph.api.smartthings.com" } +def getShardUrl() { return getApiServerUrl() } +def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback" } +def getBuildRedirectUrl() { return "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" } +def getApiEndpoint() { return "https://apps.neatorobotics.com" } +def getSmartThingsClientId() { return appSettings.clientId } +def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } +private def textVersion() { + def text = "Neato (Connect)\nVersion: 1.0\nDate: 14102016(1700)" +} + +private def textCopyright() { + def text = "Copyright © 2016 Alex Lee Yuk Cheung" +} + +def clientId() { + if(!appSettings.clientId) { + return "3ba64237d07f43e2e6ecff97de60916b73c4b06df71e9ad35ec02d7b3b513881" + } else { + return appSettings.clientId + } +} + +def clientSecret() { + if(!appSettings.clientSecret) { + return "e7fd560dab04efdd38488f918a2a8b0c097157d765e19003360fc458f5119bde" + } else { + return appSettings.clientSecret + } +} \ No newline at end of file From af8a1a28de66435479f1d7cb7290e41680b0b06d Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 14 Oct 2016 18:17:09 +0100 Subject: [PATCH 213/685] V1.0 - Neato Botvac Connected Series device initial release --- .../neato-botvac-connected-series.groovy | 386 ++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy diff --git a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy new file mode 100644 index 00000000000..1fe970d9b58 --- /dev/null +++ b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy @@ -0,0 +1,386 @@ +/** + * Neato Botvac Connected Series + * + * Copyright 2016 Alex Lee Yuk Cheung + * + * 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 + * 14-10-2016: 1.0 - Initial Version + * + */ +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; + +metadata { + definition (name: "Neato Botvac Connected Series", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { + capability "Battery" + capability "Polling" + capability "Refresh" + capability "Switch" + + command "refresh" + command "dock" + command "enableSchedule" + command "disableSchedule" + + attribute "network","string" + attribute "bin","string" + } + + + simulator { + // TODO: define status and reply messages here + } + + tiles(scale: 2) { + multiAttributeTile(name: "clean", width: 6, height: 4, type:"lighting") { + tileAttribute("device.switch", key:"PRIMARY_CONTROL", canChangeBackground: true){ + attributeState("off", label: 'STOPPED', action: "switch.on", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png", backgroundColor: "#ffffff", nextState:"on") + attributeState("on", label: 'CLEANING', action: "switch.off", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/best-pet-hair-cleaning.png", backgroundColor: "#79b821", nextState:"off") + } + tileAttribute ("statusMsg", key: "SECONDARY_CONTROL") { + attributeState "statusMsg", label:'${currentValue}' + } + } + + valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { + state "battery", label:'${currentValue}% battery', unit:"" + } + standardTile("charging", "device.charging", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'Charging', icon: "st.samsung.da.RC_ic_charge", backgroundColor: "#E5E500") + state ("false", label:'', icon: "st.samsung.da.RC_ic_charge") + } + standardTile("bin", "device.bin", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") + state ("empty", label:'Bin Empty', icon: "st.Office.office10",backgroundColor: "#79b821") + state ("full", label:'Bin Full', icon: "st.Office.office10", backgroundColor: "#bc2323") + } + /*standardTile("clean", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { + state("on", label: 'dock', action: "switch.off", icon: "st.Appliances.appliances13", backgroundColor: "#79b821", nextState:"off") + state("off", label: 'clean', action: "switch.on", icon: "st.Appliances.appliances13", backgroundColor: "#79b821", nextState:"on") + }*/ + standardTile("network", "device.network", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") + state ("Connected", label:'Link Good', icon: "st.Health & Wellness.health9", backgroundColor: "#79b821") + state ("Not Connected", label:'Link Bad', icon: "st.Health & Wellness.health9", backgroundColor: "#bc2323") + } + standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { + state("default", label:'refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon") + } + standardTile("status", "device.status", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("unknown", label:'${currentValue}', icon: "st.unknown.unknown.unknown") + state ("cleaning", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/best-pet-hair-cleaning.png") + state ("ready", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png") + state ("error", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png", backgroundColor: "#bc2323") + state ("paused", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png") + } + + standardTile("dockStatus", "device.dockStatus", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("docked", label:'DOCKED', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/auto-charge-resume.png") + state ("dockable", label:'DOCK', action: "dock", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato_staub.png") + state ("undocked", label:'UNDOCKED', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png") + } + + standardTile("scheduled", "device.scheduled", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'Sched', action: "disableSchedule", icon:"st.Office.office7") + state ("false", label:'Manual', action: "enableSchedule", icon: "st.Appliances.appliances13") + } + + standardTile("dockHasBeenSeen", "device.dockHasBeenSeen", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + state ("true", label:'SEEN', backgroundColor: "#79b821", icon:"st.Transportation.transportation13") + state ("false", label:'SEARCHING', backgroundColor: "#E5E500", icon:"st.Transportation.transportation13") + } + + main("clean") + details(["clean","status","battery","bin","network", "dockStatus", "charging", "dockHasBeenSeen", "scheduled", "refresh"]) + } +} + +// handle commands + +def installed() { + log.debug "Installed with settings: ${settings}" + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + initialize() +} + +def initialize() { + poll() +} + +def on() { + log.debug "Executing 'on'" + if (device.latestState('status').stringValue == 'paused') { + nucleoPOST("/messages", '{"reqId":"1", "cmd":"resumeCleaning"}') + } + else { + nucleoPOST("/messages", '{"reqId":"1", "cmd":"startCleaning", "params":{"category": 2, "mode": 2, "modifier": 2}}') + } + runIn(2, refresh) +} + +def off() { + log.debug "Executing 'off'" + def currentState = device.latestState('status').stringValue + if (currentState == 'cleaning' || currentState == 'error') { + nucleoPOST("/messages", '{"reqId":"1", "cmd":"pauseCleaning"}') + } + runIn(2, refresh) +} + +def dock() { + log.debug "Executing 'dock'" + if (device.latestState('status').stringValue == 'paused') { + nucleoPOST("/messages", '{"reqId":"1", "cmd":"sendToBase"}') + } + runIn(2, refresh) +} + +def enableSchedule() { + log.debug "Executing 'enableSchedule'" + nucleoPOST("/messages", '{"reqId":"1", "cmd":"enableSchedule"}') + runIn(2, refresh) +} + +def disableSchedule() { + log.debug "Executing 'disableSchedule'" + nucleoPOST("/messages", '{"reqId":"1", "cmd":"disableSchedule"}') + runIn(2, refresh) +} + +def setOffline() { + sendEvent(name: 'network', value: "Not Connected" as String) +} + +def poll() { + log.debug "Executing 'poll'" + def resp = nucleoPOST("/messages", '{"reqId":"1", "cmd":"getRobotState"}') + def result = resp.data + def statusMsg = "" + def binFullFlag = false + if (resp.status != 200) { + if (result.containsKey("message")) { + switch (result.message) { + case "Could not find robot_serial for specified vendor_name": + statusMsg += 'Robot serial and/or secret is not correct' + break; + } + } + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + setOffline() + sendEvent(name: 'status', value: "error" as String) + statusMsg += 'Not Connected To Neato' + log.debug headerString + } + else { + if (result.containsKey("state")) { + sendEvent(name: 'network', value: "Connected" as String) + //state 1 - Ready to clean + //state 2 - Cleaning + //state 3 - Paused + //state 4 - Error + switch (result.state) { + case "1": + sendEvent(name: "status", value: "ready") + sendEvent(name: "switch", value: "off") + statusMsg += 'READY TO CLEAN' + log.debug device.latestState('switch').stringValue + break; + case "2": + sendEvent(name: "status", value: "cleaning") + sendEvent(name: "switch", value: "on") + statusMsg += 'CURRENTLY CLEANING' + break; + case "3": + sendEvent(name: "status", value: "paused") + sendEvent(name: "switch", value: "off") + statusMsg += 'PAUSED' + break; + case "4": + sendEvent(name: "status", value: "error") + statusMsg += 'HAS A PROBLEM' + break; + default: + sendEvent(name: "status", value: "unknown") + statusMsg += 'UNKNOWN' + break; + } + } + if (result.containsKey("error")) { + switch (result.error) { + case "ui_alert_dust_bin_full": + binFullFlag = true + break; + case "ui_alert_return_to_base": + statusMsg += ' - Returning to base' + break; + case "ui_alert_return_to_start": + statusMsg += ' - Returning to start' + break; + case "ui_alert_return_to_charge": + statusMsg += ' - Returning to charge' + break; + case "ui_alert_busy_charging": + statusMsg += ' - Busy charging' + break; + case "ui_alert_recovering_location": + statusMsg += ' - Recovering Location' + break; + case "ui_error_dust_bin_full": + binFullFlag = true + statusMsg += ' - Dust bin full!' + break; + case "ui_error_picked_up": + statusMsg += ' - Picked up!' + break; + case "ui_error_brush_stuck": + statusMsg += ' - Brush stuck!' + break; + case "ui_error_stuck": + statusMsg += ' - I\'m stuck!' + break; + case "ui_error_dust_bin_missing": + statusMsg += ' - Dust Bin is missing!' + break + case "ui_error_navigation_falling": + statusMsg += ' - Please clear my path!' + break + case "ui_error_navigation_noprogress": + statusMsg += ' - Please clear my path!' + break + case "ui_error_battery_overtemp": + statusMsg += ' - Battery is overheating!' + break + case "ui_error_unable_to_return_to_base": + statusMsg += ' - Unable to return to base!' + break + case "ui_error_bumper_stuck": + statusMsg += ' - Bumper stuck!' + break + case "ui_error_lwheel_stuck": + statusMsg += ' - Left wheel stuck!' + break + case "ui_error_rwheel_stuck": + statusMsg += ' - Right wheel stuck!' + break + case "ui_error_lds_jammed": + statusMsg += ' - LIDAR jammed!' + break + case "ui_error_brush_overload": + statusMsg += ' - Brush overloaded!' + break + case "ui_error_hardware_failure": + statusMsg += ' - Hardware Failure!' + break + case "ui_error_unable_to_see": + statusMsg += ' - Unable to see!' + break + case "ui_error_rdrop_stuck": + statusMsg += ' - Right drop stuck!' + break + case "ui_error_ldrop_stuck": + statusMsg += ' - Left drop stuck!' + break + default: + if ("ui_alert_invalid" != result.error) { + statusMsg += ' - ' + result.error.replaceAll('ui_error_', '').replaceAll('ui_alert_', '').replaceAll('_',' ').capitalize() + } + break; + //More error detail messages here as discovered + + } + } + if (result.containsKey("details")) { + if (result.details.isDocked) { + sendEvent(name: 'dockStatus', value: "docked" as String) + } else { + sendEvent(name: 'dockStatus', value: "undocked" as String) + } + sendEvent(name: 'charging', value: result.details.isCharging as String) + sendEvent(name: 'scheduled', value: result.details.isScheduleEnabled as String) + sendEvent(name: 'dockHasBeenSeen', value: result.details.dockHasBeenSeen as String) + sendEvent(name: 'battery', value: result.details.charge as Integer) + } + if (result.containsKey("availableCommands")) { + if (result.availableCommands.goToBase) { + sendEvent(name: 'dockStatus', value: "dockable") + } + } + + if (binFullFlag) { + sendEvent(name: 'bin', value: "full" as String) + } else { + sendEvent(name: 'bin', value: "empty" as String) + } + } + sendEvent(name: 'statusMsg', value: statusMsg, displayed: false) + +} + +def refresh() { + log.debug "Executing 'refresh'" + poll() +} + +def nucleoPOST(path, body) { + try { + log.debug("Beginning API POST: ${nucleoURL(path)}, ${body}") + def date = new Date().format("EEE, dd MMM yyyy HH:mm:ss z", TimeZone.getTimeZone('GMT')) + log.debug getHMACSignature(date, body) + httpPostJson(uri: nucleoURL(path), body: body, headers: nucleoRequestHeaders(date, getHMACSignature(date, body)) ) {response -> + parent.logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + parent.logResponse(e.response) + return e.response + } +} + +def getHMACSignature(date, body) { + //request params + def robot_serial = device.deviceNetworkId.tokenize("|")[0] + //Format date should be "Fri, 03 Apr 2015 09:12:31 GMT" + + def robot_secret_key = device.deviceNetworkId.tokenize("|")[1] + + // build string to be signed + def string_to_sign = "${robot_serial.toLowerCase()}\n${date}\n${body}" + + // create signature with SHA256 + //signature = OpenSSL::HMAC.hexdigest('sha256', robot_secret_key, string_to_sign) + try { + Mac mac = Mac.getInstance("HmacSHA256") + SecretKeySpec secretKeySpec = new SecretKeySpec(robot_secret_key.getBytes(), "HmacSHA256") + mac.init(secretKeySpec) + byte[] digest = mac.doFinal(string_to_sign.getBytes()) + return digest.encodeHex() + } catch (InvalidKeyException e) { + throw new RuntimeException("Invalid key exception while converting to HMac SHA256") + } +} + +Map nucleoRequestHeaders(date, HMACsignature) { + return [ + 'X-Date': "${date}", + 'Accept': 'application/vnd.neato.nucleo.v1', + 'Content-Type': 'application/*+json', + 'X-Agent': '0.11.3-142', + 'Authorization': "NEATOAPP ${HMACsignature}" + ] +} + +def nucleoURL(path = '/') { return "https://nucleo.neatocloud.com:4443/vendors/neato/robots/${device.deviceNetworkId.tokenize("|")[0]}${path}" } \ No newline at end of file From 15774f6494a370d6fc5c7d45ccfd37c0adb511de Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 14 Oct 2016 18:24:35 +0100 Subject: [PATCH 214/685] Remove dev logging --- .../neato-botvac-connected-series.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy index 1fe970d9b58..59661dc7583 100644 --- a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy +++ b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy @@ -197,7 +197,6 @@ def poll() { sendEvent(name: "status", value: "ready") sendEvent(name: "switch", value: "off") statusMsg += 'READY TO CLEAN' - log.debug device.latestState('switch').stringValue break; case "2": sendEvent(name: "status", value: "cleaning") From 11a87c13db371106ad10c5b850b07cdfe512d609 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 14 Oct 2016 18:56:22 +0100 Subject: [PATCH 215/685] Add support for Contact Book --- .../neato-connect.src/neato-connect.groovy | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index b0ef39b390e..0af0444e3fa 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -121,7 +121,9 @@ def selectDevicePAGE() { def notificationsPAGE() { return dynamicPage(name: "notificationsPAGE", title: "Notifications", install: false, uninstall: false) { section(""){ - input "sendPush", "bool", title: "Send as Push?", required: false, defaultValue: true + input("recipients", "contact", title: "Send notifications to", required: false) { + input "sendPush", "bool", title: "Send as Push?", required: false + } input "sendSMS", "phone", title: "Send as SMS?", required: false, defaultValue: null input "sendBotvacOn", "bool", title: "Notify when on?", required: false, defaultValue: false input "sendBotvacOff", "bool", title: "Notify when off?", required: false, defaultValue: false @@ -492,11 +494,12 @@ def getPreferencesString() { } def notificationsSelected() { - return (sendPush || sendSMS != null) && (sendBotvacOn || sendBotvacOff || sendBotvacError || sendBotvacBin) ? "complete" : null + return ((location.contactBookEnabled && recipients) || sendPush || sendSMS != null) && (sendBotvacOn || sendBotvacOff || sendBotvacError || sendBotvacBin) ? "complete" : null } def getNotificationsString() { def listString = "" + if (location.contactBookEnabled && recipients) listString += "• Send via Contact Book\n" if (sendPush) listString += "• Send Push\n" if (sendSMS != null) listString += "• Send SMS to ${sendSMS}\n" if (sendBotvacOn) listString += "• Botvac On Notification\n" @@ -566,12 +569,7 @@ def eventHandler(evt) { log.trace "${evt.displayName} has an error" msg = "${evt.displayName} has an error" if (sendBotvacError == true) { - if (settings.sendSMS != null) { - sendSms(sendSMS, msg) - } - if (settings.sendPush == true) { - sendPush(msg) - } + messageHandler(msg, false) } } else if (evt.value == "cleaning") { @@ -587,12 +585,7 @@ def eventHandler(evt) { log.trace "${evt.displayName} is on" msg = "${evt.displayName} is on" if (sendBotvacOn == true) { - if (settings.sendSMS != null) { - sendSms(sendSMS, msg) - } - if (settings.sendPush == true) { - sendPush(msg) - } + messageHandler(msg, false) } if (settings.autoSHM.contains('true') ) { if (location.currentState("alarmSystemStatus")?.value == "away") { @@ -600,7 +593,7 @@ def eventHandler(evt) { log.trace "Smart Home Monitor is set to stay" sendLocationEvent(name: "alarmSystemStatus", value: "stay") state.autoSHMchange = "y" - sendPush("Smart Home Monitor is set to stay as ${evt.displayName} is on") + messageHandler("Smart Home Monitor is set to stay as ${evt.displayName} is on", true) } } } @@ -611,12 +604,7 @@ def eventHandler(evt) { log.trace "${evt.displayName} bin is full" msg = "${evt.displayName} bin is full" if (sendBotvacBin == true) { - if (settings.sendSMS != null) { - sendSms(sendSMS, msg) - } - if (settings.sendPush == true) { - sendPush(msg) - } + messageHandler(msg, false) } } else if (evt.value == "ready") { @@ -626,12 +614,7 @@ def eventHandler(evt) { log.trace "${evt.displayName} is off" msg = "${evt.displayName} is off" if (sendBotvacOff == true) { - if (settings.sendSMS != null) { - sendSms(sendSMS, msg) - } - if (settings.sendPush == true) { - sendPush(msg) - } + messageHandler(msg, false) } } } @@ -658,7 +641,7 @@ def pollOn() { log.debug "$childDevice.displayName last cleaned at " + state.lastClean[childDevice.displayName] + ". $t milliseconds has elapsed since." if (t > (forceCleanDelay * 86400000)) { log.debug "Force clean activated as $t milliseconds has elapsed" - sendPush(childDevice.displayName + " has not cleaned for " + forceCleanDelay + " days. Forcing a clean.") + messageHandler(childDevice.displayName + " has not cleaned for " + forceCleanDelay + " days. Forcing a clean.", true) childDevice.on() } } @@ -675,7 +658,7 @@ def pollOn() { log.trace "Smart Home Monitor is set back to away" sendLocationEvent(name: "alarmSystemStatus", value: "away") state.autoSHMchange = "n" - sendPush("Smart Home Monitor is set to away as all cleaners are off") + messageHandler("Smart Home Monitor is set to away as all cleaners are off", true) } } } @@ -686,6 +669,18 @@ def pollOn() { } } +def messageHandler(msg, forceFlag) { + if (settings.sendSMS != null && !forceFlag) { + sendSms(sendSMS, msg) + } + if (location.contactBookEnabled && recipients) { + sendNotificationToContacts(msg, recipients) + } else if (settings.sendPush == true || forceFlag) { + sendPush(msg) + } + +} + def getChildName() { return "Neato BotVac" } def getServerUrl() { return "https://graph.api.smartthings.com" } From dc43d8116ae5ba0846c614296918be8c53f36a03 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 14 Oct 2016 19:32:47 +0100 Subject: [PATCH 216/685] Minor fixes --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 0af0444e3fa..5588015ee03 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -292,7 +292,7 @@ def displayMessageAsHtml(message) {
SmartThings logo connected device icon - nest icon + neato icon ${message}
From 5598de1bdc5742a982bd2bcab0d29676524cfd3e Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 14 Oct 2016 20:10:16 +0100 Subject: [PATCH 217/685] Minor error message formatting tweaks. --- .../neato-botvac-connected-series.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy index 59661dc7583..2afe40b7487 100644 --- a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy +++ b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy @@ -175,7 +175,7 @@ def poll() { if (result.containsKey("message")) { switch (result.message) { case "Could not find robot_serial for specified vendor_name": - statusMsg += 'Robot serial and/or secret is not correct' + statusMsg += 'Robot serial and/or secret is not correct.\n' break; } } From 0b150188badcd7a57bf80c0a3d9f670fb5cc5cd3 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Fri, 14 Oct 2016 21:41:57 +0100 Subject: [PATCH 218/685] 1.0b - Minor fix to preference list --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 5588015ee03..131bc2784eb 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -14,6 +14,7 @@ * * VERSION HISTORY * 14-10-2016: 1.0 - Initial Version + * 14-10-2016: 1.0b - Minor fix to preference list */ definition( name: "Neato (Connect)", @@ -152,7 +153,7 @@ def preferencesPAGE() { } section("Auto Smart Home Monitor..."){ paragraph "If Smart Home Monitor is set to Arm(Away), auto Set Smart Home Monitor to Arm(Stay) when cleaning and reset when done. If Smart Home Monitor is Disarmed during cleaning, then this will not reactivate SHM." - input "autoSHM", "bool", title: "Auto Set Smart Home Monitor?", required: true, multiple: true, defaultValue: false, submitOnChange: true + input "autoSHM", "bool", title: "Auto Set Smart Home Monitor?", required: false, defaultValue: false, submitOnChange: true } } From 9586cd73a6e7916d75da1d1330a2e7cb2e252ad5 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 15 Oct 2016 00:51:10 +0100 Subject: [PATCH 219/685] Versioning update --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 131bc2784eb..376e5570bf2 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -692,7 +692,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.0\nDate: 14102016(1700)" + def text = "Neato (Connect)\nVersion: 1.0b\nDate: 14102016(2145)" } private def textCopyright() { From d2c1b8754103c0592e6194a9f3d9c7e0d80f1aa8 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 15 Oct 2016 13:42:51 +0100 Subject: [PATCH 220/685] 1.0c - Fix to auto SHM mode not triggering --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 376e5570bf2..475aed41369 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,8 +13,9 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY - * 14-10-2016: 1.0 - Initial Version + * 15-10-2016: 1.0c - Fix to auto SHM mode not triggering * 14-10-2016: 1.0b - Minor fix to preference list + * 14-10-2016: 1.0 - Initial Version */ definition( name: "Neato (Connect)", @@ -588,7 +589,7 @@ def eventHandler(evt) { if (sendBotvacOn == true) { messageHandler(msg, false) } - if (settings.autoSHM.contains('true') ) { + if (autoSHM) { if (location.currentState("alarmSystemStatus")?.value == "away") { sendEvent(linkText:app.label, name:"Smart Home Monitor", value:"stay",descriptionText:"Smart Home Monitor was set to stay", eventType:"SOLUTION_EVENT", displayed: true) log.trace "Smart Home Monitor is set to stay" @@ -653,7 +654,7 @@ def pollOn() { } if (!activeCleaners) { - if (settings.autoSHM.contains('true') ) { + if (autoSHM) { if (location.currentState("alarmSystemStatus")?.value == "stay" && state.autoSHMchange == "y"){ sendEvent(linkText:app.label, name:"Smart Home Monitor", value:"away",descriptionText:"Smart Home Monitor was set back to away", eventType:"SOLUTION_EVENT", displayed: true) log.trace "Smart Home Monitor is set back to away" @@ -692,7 +693,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.0b\nDate: 14102016(2145)" + def text = "Neato (Connect)\nVersion: 1.0c\nDate: 15102016(1330)" } private def textCopyright() { From 1ee00295ce2d9a264088366f46f7cf9dfbe87f5c Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 17 Oct 2016 19:00:15 +0100 Subject: [PATCH 221/685] V1.1 - SmartSchedule functionality and minor fixes --- .../neato-connect.src/neato-connect.groovy | 279 +++++++++++++++--- 1 file changed, 242 insertions(+), 37 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 475aed41369..922b8c99ea4 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 17-10-2016: 1.1 - SmartSchedule functionality and minor fixes * 15-10-2016: 1.0c - Fix to auto SHM mode not triggering * 14-10-2016: 1.0b - Minor fix to preference list * 14-10-2016: 1.0 - Initial Version @@ -39,6 +40,7 @@ preferences { page(name: "selectDevicePAGE") page(name: "preferencesPAGE") page(name: "notificationsPAGE") + page(name: "smartSchedulePAGE") } mappings { @@ -81,14 +83,17 @@ def authPage() { dynamicPage(name: "auth", uninstall: false, install: false) { section { headerSECTION() } section ("Choose your Neato Botvacs:") { - href("selectDevicePAGE", title: null, description: devicesSelected() ? "Devices:\n " + getDevicesSelectedString() : "Tap to select your Neato Botvacs", state: devicesSelected()) - } - section ("Preferences:") { - href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure preferences", state: preferencesSelected()) - } - section ("Notifications:") { - href("notificationsPAGE", title: null, description: notificationsSelected() ? getNotificationsString() : "Tap to configure notifications", state: notificationsSelected()) - } + href("selectDevicePAGE", title: null, description: devicesSelected() ? "Devices:\n " + getDevicesSelectedString() : "Tap to select your Neato Botvacs", state: devicesSelected()) + } + section ("SmartSchedule Configuration:") { + href("smartSchedulePAGE", title: null, description: smartScheduleSelected() ? getSmartScheduleString() : "Tap to configure SmartSchedule", state: smartScheduleSelected()) + } + section ("Preferences:") { + href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure preferences", state: preferencesSelected()) + } + section ("Notifications:") { + href("notificationsPAGE", title: null, description: notificationsSelected() ? getNotificationsString() : "Tap to configure notifications", state: notificationsSelected()) + } def botvacList = "" selectedBotvacs.each() { def childDevice = getChildDevice("${it}") @@ -120,17 +125,61 @@ def selectDevicePAGE() { } } +def smartSchedulePAGE() { + return dynamicPage(name: "smartSchedulePAGE", title: "SmartSchedule Configuration", install: false, uninstall: false) { + section() { + paragraph "Configure a dymanic schedule for your Botvacs so that they can clean whilst you're out of the house." + input "smartScheduleEnabled", "bool", title: "Enable SmartSchedule?", required: false, defaultValue: false, submitOnChange: true + } + if (smartScheduleEnabled) { + section("Configure your Away Modes and cleaning interval:") { + //SmartSchedule configuration options. + //Configure regular cleaning interval in days + input ("ssCleaningInterval", "number", title: "Set your ideal cleaning interval in days", required: true, defaultValue: 3) + //Define your away modes + input ("ssAwayModes", "mode", title:"Specify your Away Modes:", multiple: true, required: true) + } + section("SmartSchedule restrictions:") { + //Define time of day + paragraph "Set SmartSchedule restrictions so that your Botvacs don't start in the middle of the night." + href "timeIntervalInput", title: "Operate Botvacs only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true + //Define allowed days of operation + input ("days", "enum", title: "Operate Botvacs only on certain days of the week", multiple: true, required: false, + options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]) + } + section("SmartSchedule overrides:") { + //Define override switches to restart SmartSchedule countdown + paragraph "Routine override switches/buttons will cancel the next scheduled clean and reset the interval countdown when switched on." + input ("ssOverrideSwitch", "capability.switch", title:"Set SmartSchedule override switches", multiple: true, required: false) + } + section("Notifications:") { + paragraph "Turn on SmartSchedule notifications. You can configure specific recipients via Notification settings section." + input "ssNotification", "bool", title: "Enable SmartSchedule notifications?", required: false, defaultValue: true + } + } + } + +} + +page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) { + section { + input "starting", "time", title: "Starting", required: true + input "ending", "time", title: "Ending", required: true + } +} + def notificationsPAGE() { return dynamicPage(name: "notificationsPAGE", title: "Notifications", install: false, uninstall: false) { - section(""){ + section(){ input("recipients", "contact", title: "Send notifications to", required: false) { - input "sendPush", "bool", title: "Send as Push?", required: false + input "sendPush", "bool", title: "Send notifications via Push?", required: false } - input "sendSMS", "phone", title: "Send as SMS?", required: false, defaultValue: null - input "sendBotvacOn", "bool", title: "Notify when on?", required: false, defaultValue: false - input "sendBotvacOff", "bool", title: "Notify when off?", required: false, defaultValue: false - input "sendBotvacError", "bool", title: "Notify on error?", required: false, defaultValue: true - input "sendBotvacBin", "bool", title: "Notify on full bin?", required: false, defaultValue: true + input "sendSMS", "phone", title: "Send notifications via SMS?", required: false, defaultValue: null + input "sendBotvacOn", "bool", title: "Notify when Botvacs are on?", required: false, defaultValue: false + input "sendBotvacOff", "bool", title: "Notify when Botvacs are off?", required: false, defaultValue: false + input "sendBotvacError", "bool", title: "Notify on Botvacs have an error?", required: false, defaultValue: true + input "sendBotvacBin", "bool", title: "Notify when Botvacs have a full bin?", required: false, defaultValue: true + input "ssNotification", "bool", title: "Enable SmartSchedule notifications?", required: false, defaultValue: true } } } @@ -154,7 +203,7 @@ def preferencesPAGE() { } section("Auto Smart Home Monitor..."){ paragraph "If Smart Home Monitor is set to Arm(Away), auto Set Smart Home Monitor to Arm(Stay) when cleaning and reset when done. If Smart Home Monitor is Disarmed during cleaning, then this will not reactivate SHM." - input "autoSHM", "bool", title: "Auto Set Smart Home Monitor?", required: false, defaultValue: false, submitOnChange: true + input "autoSHM", "bool", title: "Auto set Smart Home Monitor?", required: false, defaultValue: false, submitOnChange: true } } @@ -358,21 +407,42 @@ def updated() { def initialize() { // TODO: subscribe to attributes, devices, locations, etc. - if (selectedBotvacs) - addBotvacs() + //Initialise variables + if (state.lastClean == null) { + state.lastClean = [:] + } + if (state.smartSchedule == null) { + state.smartSchedule = [:] + } + if (selectedBotvacs) addBotvacs() + unschedule() runEvery5Minutes('pollOn') // Asynchronously refresh devices so we don't block //subscribe to events for notifications if activated + if (smartScheduleEnabled || preferencesSelected() == "complete" || notificationsSelected() == "complete") { + subscribe(childDevice, "status.cleaning", eventHandler, [filterEvents: false]) + } + if (preferencesSelected() == "complete" || notificationsSelected() == "complete") { - getChildDevices().each { childDevice -> - subscribe(childDevice, "status.cleaning", eventHandler, [filterEvents: false]) + getChildDevices().each { childDevice -> subscribe(childDevice, "status.ready", eventHandler, [filterEvents: false]) subscribe(childDevice, "status.error", eventHandler, [filterEvents: false]) subscribe(childDevice, "status.paused", eventHandler, [filterEvents: false]) subscribe(childDevice, "bin.full", eventHandler, [filterEvents: false]) } } + //subscribe to events for smartSchedule + if (smartScheduleEnabled) { + subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) + if (starting && ending) { + schedule(starting, smartScheduleHandler) + } + else { + schedule("29 0 0 1/1 * ? *", smartScheduleHandler) + } + subscribe(ssOverrideSwitch, "switch.on", smartScheduleHandler, [filterEvents: false]) + } } def uninstalled() { @@ -435,7 +505,9 @@ def addBotvacs() { } else { log.debug "found ${state.botvacDevices[device]} with id ${device} already exists" } - + + //Set SmartSchedule flag to false by default + if (state.smartSchedule[childDevice.deviceNetworkId] == null) state.smartSchedule[childDevice.deviceNetworkId] = false } } @@ -481,6 +553,21 @@ def getDevicesSelectedString() { return listString } +def smartScheduleSelected() { + return smartScheduleEnabled ? "complete" : null +} + +def getSmartScheduleString() { + def listString = "" + if (smartScheduleEnabled) { + listString += "SmartSchedule set for every ${ssCleaningInterval} days\n• When mode is ${ssAwayModes}\n" + if (starting) listString += "• ${getTimeLabel(starting, ending)}\n" + if (days) listString += "• Only on $days.\n" + if (ssOverrideSwitch) listString += "• Override schedule if any of ${ssOverrideSwitch} turns on." + } + return listString +} + def preferencesSelected() { return (forceClean || autoDock || autoSHM) ? "complete" : null } @@ -501,13 +588,14 @@ def notificationsSelected() { def getNotificationsString() { def listString = "" - if (location.contactBookEnabled && recipients) listString += "• Send via Contact Book\n" + if (location.contactBookEnabled && recipients) listString += "• Send notifications to " + recipients + "\n" if (sendPush) listString += "• Send Push\n" if (sendSMS != null) listString += "• Send SMS to ${sendSMS}\n" if (sendBotvacOn) listString += "• Botvac On Notification\n" if (sendBotvacOff) listString += "• Botvac Off Notification\n" if (sendBotvacError) listString += "• Botvac Error Notification\n" if (sendBotvacBin) listString += "• Bin Full Notification\n" + if (ssNotification) listString += "• SmartSchedule Notifications\n" if (listString != "") listString = listString.substring(0, listString.length() - 1) return listString } @@ -565,7 +653,7 @@ def eventHandler(evt) { } } else if (evt.value == "error") { - unschedule() + unschedule(pollOn) runEvery5Minutes('pollOn') sendEvent(linkText:app.label, name:"${evt.displayName}", value:"error",descriptionText:"${evt.displayName} has an error", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} has an error" @@ -575,14 +663,13 @@ def eventHandler(evt) { } } else if (evt.value == "cleaning") { - unschedule() + unschedule(pollOn) //Increase poll interval during cleaning schedule("0 0/1 * * * ?", pollOn) - if (state.lastClean == null) { - state.lastClean = [:] - } //Record last cleaning time for device - state.lastClean[evt.displayName] = now() + state.lastClean[evt.deviceNetworkId] = now() + //Remove SmartSchedule flag + state.smartSchedule[evt.deviceNetworkId] = false sendEvent(linkText:app.label, name:"${evt.displayName}", value:"on",descriptionText:"${evt.displayName} is on", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} is on" msg = "${evt.displayName} is on" @@ -600,7 +687,7 @@ def eventHandler(evt) { } } else if (evt.value == "full") { - unschedule() + unschedule(pollOn) runEvery5Minutes('pollOn') sendEvent(linkText:app.label, name:"${evt.displayName}", value:"bin full",descriptionText:"${evt.displayName} bin is full", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} bin is full" @@ -610,7 +697,7 @@ def eventHandler(evt) { } } else if (evt.value == "ready") { - unschedule() + unschedule(pollOn) runEvery5Minutes('pollOn') sendEvent(linkText:app.label, name:"${evt.displayName}", value:"off",descriptionText:"${evt.displayName} is off", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} is off" @@ -621,6 +708,51 @@ def eventHandler(evt) { } } +def smartScheduleHandler(evt) { + if (evt != null) { + log.debug "Executing 'smartScheduleHandler' for ${evt.displayName}" + } else { + log.debug "Executing 'smartScheduleHandler' for schedule event" + } + //If switch on for override event + if (evt != null && evt.name == "switch") { + //For each vacuum + getChildDevices().each { childDevice -> + //Reset last clean date to current time + state.lastClean[childDevice.deviceNetworkId] = now() + + //DEBUG PURPOSES ONLY. FAKE TIME ON OVERRIDE SWITCH AND INCREASE POLL + //state.lastClean[childDevice.deviceNetworkId] = Date.parseToStringDate("Thu Oct 13 01:23:45 UTC 2016").getTime() + //unschedule(pollOn) + //schedule("0 0/1 * * * ?", pollOn) + //log.debug "Fake data loaded.... " + (now() - state.lastClean[childDevice.deviceNetworkId])/86400000 + + //Remove existing SmartSchedule flag + state.smartSchedule[childDevice.deviceNetworkId] = false + } + if (ssNotification) { + messageHandler("Neato SmartSchedule has reset all Botvac schedules as override switch ${evt.displayName} is on.", false) + } + } + //If mode change event OR schedule trigger + else { + //Check current mode is in Away list + //Check time & day - allOK() + if ((location.mode in ssAwayModes) && allOk) { + //For each vacuum + getChildDevices().each { childDevice -> + //If smartSchedule flag has been set, start clean. + if (state.smartSchedule[childDevice.deviceNetworkId]) { + if (ssNotification) { + messageHandler("Neato SmartSchedule has started ${childDevice.displayName} cleaning.", false) + } + childDevice.on() + } + } + } + } +} + def scheduleAutoDock() { getChildDevices().each { childDevice -> if (childDevice.latestState('status').stringValue == 'paused') { @@ -633,14 +765,22 @@ def pollOn() { log.debug "Executing 'pollOn'" def activeCleaners = false - + log.debug "Last clean states: ${state.lastClean}" + log.debug "Smart schedule states: ${state.smartSchedule}" getChildDevices().each { childDevice -> state.pollState = now() childDevice.poll() //Force on if last clean was a long time ago - if (childDevice.currentSwitch == "off" && forceClean && state.lastClean != null && state.lastClean[childDevice.displayName] != null) { - def t = now() - state.lastClean[childDevice.displayName] - log.debug "$childDevice.displayName last cleaned at " + state.lastClean[childDevice.displayName] + ". $t milliseconds has elapsed since." + if (childDevice.currentSwitch == "off" && forceClean && state.lastClean != null && state.lastClean[childDevice.deviceNetworkId] != null) { + def t = now() - state.lastClean[childDevice.deviceNetworkId] + //Set SmartSchedule flag if SmartSchedule has not been set already, interval has elapsed and user is at home + if ((!(location.mode in ssAwayModes)) && (t > (ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { + state.smartSchedule[childDevice.deviceNetworkId] = true + if (ssNotification) { + messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean when you're next away (date and time restrictions permitting). Please clear obstacles and leave internal doors open when you next leave the house.", false) + } + } + log.debug "$childDevice.displayName last cleaned at " + state.lastClean[childDevice.deviceNetworkId] + ". ${t/86400000} days has elapsed since." if (t > (forceCleanDelay * 86400000)) { log.debug "Force clean activated as $t milliseconds has elapsed" messageHandler(childDevice.displayName + " has not cleaned for " + forceCleanDelay + " days. Forcing a clean.", true) @@ -660,7 +800,7 @@ def pollOn() { log.trace "Smart Home Monitor is set back to away" sendLocationEvent(name: "alarmSystemStatus", value: "away") state.autoSHMchange = "n" - messageHandler("Smart Home Monitor is set to away as all cleaners are off", true) + messageHandler("Smart Home Monitor is set to away as all Neato Botvacs are off", true) } } } @@ -671,6 +811,8 @@ def pollOn() { } } +//Helper methods + def messageHandler(msg, forceFlag) { if (settings.sendSMS != null && !forceFlag) { sendSms(sendSMS, msg) @@ -680,7 +822,70 @@ def messageHandler(msg, forceFlag) { } else if (settings.sendPush == true || forceFlag) { sendPush(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 +} + +def greyedOutTime(starting, ending){ + def result = "" + if (starting || ending) { + result = "complete" + } + result } @@ -693,7 +898,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.0c\nDate: 15102016(1330)" + def text = "Neato (Connect)\nVersion: 1.1\nDate: 17102016(1900)" } private def textCopyright() { From 69528e8493e06a110b5f9a4338c57a8faa771380 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 17 Oct 2016 20:51:22 +0100 Subject: [PATCH 222/685] 1.1b - Set last clean value to new devices for smart schedule. --- .../neato-connect.src/neato-connect.groovy | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 922b8c99ea4..f8f3e31e5c9 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 17-10-2016: 1.1b - Set last clean value to new devices for smart schedule. * 17-10-2016: 1.1 - SmartSchedule functionality and minor fixes * 15-10-2016: 1.0c - Fix to auto SHM mode not triggering * 14-10-2016: 1.0b - Minor fix to preference list @@ -421,7 +422,9 @@ def initialize() { //subscribe to events for notifications if activated if (smartScheduleEnabled || preferencesSelected() == "complete" || notificationsSelected() == "complete") { - subscribe(childDevice, "status.cleaning", eventHandler, [filterEvents: false]) + getChildDevices().each { childDevice -> + subscribe(childDevice, "status.cleaning", eventHandler, [filterEvents: false]) + } } if (preferencesSelected() == "complete" || notificationsSelected() == "complete") { @@ -434,6 +437,12 @@ def initialize() { } //subscribe to events for smartSchedule if (smartScheduleEnabled) { + getChildDevices().each { childDevice -> + //Initialize flags for Smart Schedule + if (state.smartSchedule[childDevice.deviceNetworkId] == null) state.smartSchedule[childDevice.deviceNetworkId] = false + if (state.lastClean[childDevice.deviceNetworkId] == null) state.lastClean[childDevice.deviceNetworkId] = now() + } + subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) if (starting && ending) { schedule(starting, smartScheduleHandler) @@ -443,6 +452,7 @@ def initialize() { } subscribe(ssOverrideSwitch, "switch.on", smartScheduleHandler, [filterEvents: false]) } + } def uninstalled() { @@ -505,9 +515,6 @@ def addBotvacs() { } else { log.debug "found ${state.botvacDevices[device]} with id ${device} already exists" } - - //Set SmartSchedule flag to false by default - if (state.smartSchedule[childDevice.deviceNetworkId] == null) state.smartSchedule[childDevice.deviceNetworkId] = false } } @@ -898,7 +905,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1\nDate: 17102016(1900)" + def text = "Neato (Connect)\nVersion: 1.1b\nDate: 17102016(2046)" } private def textCopyright() { From 073dc953b52dde34efe9347ad8467e8f2104c818 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 11:25:39 +0100 Subject: [PATCH 223/685] V1.1c - Bug fix attempt. --- .../neato-connect.src/neato-connect.groovy | 115 ++++++++++-------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index f8f3e31e5c9..0b549a457a4 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 18-10-2016: 1.1c - Bug fix attempt. * 17-10-2016: 1.1b - Set last clean value to new devices for smart schedule. * 17-10-2016: 1.1 - SmartSchedule functionality and minor fixes * 15-10-2016: 1.0c - Fix to auto SHM mode not triggering @@ -132,7 +133,7 @@ def smartSchedulePAGE() { paragraph "Configure a dymanic schedule for your Botvacs so that they can clean whilst you're out of the house." input "smartScheduleEnabled", "bool", title: "Enable SmartSchedule?", required: false, defaultValue: false, submitOnChange: true } - if (smartScheduleEnabled) { + if (settings.smartScheduleEnabled) { section("Configure your Away Modes and cleaning interval:") { //SmartSchedule configuration options. //Configure regular cleaning interval in days @@ -172,15 +173,17 @@ page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfte def notificationsPAGE() { return dynamicPage(name: "notificationsPAGE", title: "Notifications", install: false, uninstall: false) { section(){ - input("recipients", "contact", title: "Send notifications to", required: false) { - input "sendPush", "bool", title: "Send notifications via Push?", required: false + input("recipients", "contact", title: "Send notifications to", required: false, submitOnChange: true) { + input "sendPush", "bool", title: "Send notifications via Push?", required: false, defaultValue: false, submitOnChange: true + } + input "sendSMS", "phone", title: "Send notifications via SMS?", required: false, defaultValue: null, submitOnChange: true + if ((location.contactBookEnabled && settings.recipients) || settings.sendPush || settings.sendSMS != null) { + input "sendBotvacOn", "bool", title: "Notify when Botvacs are on?", required: false, defaultValue: false + input "sendBotvacOff", "bool", title: "Notify when Botvacs are off?", required: false, defaultValue: false + input "sendBotvacError", "bool", title: "Notify on Botvacs have an error?", required: false, defaultValue: true + input "sendBotvacBin", "bool", title: "Notify when Botvacs have a full bin?", required: false, defaultValue: true + input "ssNotification", "bool", title: "Enable SmartSchedule notifications?", required: false, defaultValue: true } - input "sendSMS", "phone", title: "Send notifications via SMS?", required: false, defaultValue: null - input "sendBotvacOn", "bool", title: "Notify when Botvacs are on?", required: false, defaultValue: false - input "sendBotvacOff", "bool", title: "Notify when Botvacs are off?", required: false, defaultValue: false - input "sendBotvacError", "bool", title: "Notify on Botvacs have an error?", required: false, defaultValue: true - input "sendBotvacBin", "bool", title: "Notify when Botvacs have a full bin?", required: false, defaultValue: true - input "ssNotification", "bool", title: "Enable SmartSchedule notifications?", required: false, defaultValue: true } } } @@ -444,8 +447,8 @@ def initialize() { } subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) - if (starting && ending) { - schedule(starting, smartScheduleHandler) + if (settings.starting && settings.ending) { + schedule(settings.starting, smartScheduleHandler) } else { schedule("29 0 0 1/1 * ? *", smartScheduleHandler) @@ -453,6 +456,11 @@ def initialize() { subscribe(ssOverrideSwitch, "switch.on", smartScheduleHandler, [filterEvents: false]) } + //Disable push option if contact book is enabled + if (location.contactBookEnabled) { + settings.sendPush = false + } + } def uninstalled() { @@ -567,42 +575,44 @@ def smartScheduleSelected() { def getSmartScheduleString() { def listString = "" if (smartScheduleEnabled) { - listString += "SmartSchedule set for every ${ssCleaningInterval} days\n• When mode is ${ssAwayModes}\n" - if (starting) listString += "• ${getTimeLabel(starting, ending)}\n" - if (days) listString += "• Only on $days.\n" - if (ssOverrideSwitch) listString += "• Override schedule if any of ${ssOverrideSwitch} turns on." + listString += "SmartSchedule set for every ${settings.ssCleaningInterval} days\n• When mode is ${settings.ssAwayModes}\n" + if (settings.starting) listString += "• ${getTimeLabel(settings.starting, settings.ending)}\n" + if (settings.days) listString += "• Only on $settings.days.\n" + if (settings.ssOverrideSwitch) listString += "• Override schedule if any of ${settings.ssOverrideSwitch} turns on." } return listString } def preferencesSelected() { - return (forceClean || autoDock || autoSHM) ? "complete" : null + return (settings.forceClean || settings.autoDock || settings.autoSHM) ? "complete" : null } def getPreferencesString() { def listString = "" - if (forceClean) listString += "• Force clean after ${forceCleanDelay} days\n" - if (autoDock) listString += "• Auto Dock after ${autoDockDelay} seconds\n" - if (autoSHM) listString += "• Automatically set Smart Home Monitor\n" + if (settings.forceClean) listString += "• Force clean after ${settings.forceCleanDelay} days\n" + if (settings.autoDock) listString += "• Auto Dock after ${settings.autoDockDelay} seconds\n" + if (settings.autoSHM) listString += "• Automatically set Smart Home Monitor\n" if (listString != "") listString = listString.substring(0, listString.length() - 1) return listString } def notificationsSelected() { - return ((location.contactBookEnabled && recipients) || sendPush || sendSMS != null) && (sendBotvacOn || sendBotvacOff || sendBotvacError || sendBotvacBin) ? "complete" : null + return ((location.contactBookEnabled && settings.recipients) || settings.sendPush || settings.sendSMS != null) && (settings.sendBotvacOn || settings.sendBotvacOff || settings.sendBotvacError || settings.sendBotvacBin) ? "complete" : null } def getNotificationsString() { - def listString = "" - if (location.contactBookEnabled && recipients) listString += "• Send notifications to " + recipients + "\n" - if (sendPush) listString += "• Send Push\n" - if (sendSMS != null) listString += "• Send SMS to ${sendSMS}\n" - if (sendBotvacOn) listString += "• Botvac On Notification\n" - if (sendBotvacOff) listString += "• Botvac Off Notification\n" - if (sendBotvacError) listString += "• Botvac Error Notification\n" - if (sendBotvacBin) listString += "• Bin Full Notification\n" - if (ssNotification) listString += "• SmartSchedule Notifications\n" + def listString = " " + if (location.contactBookEnabled && settings.recipients) listString += "• Send notifications to " + settings.recipients + "\n" + if (settings.sendPush) listString += "• Send Push\n" + if (settings.sendSMS != null) listString += "• Send SMS to ${settings.sendSMS}\n" + if ((location.contactBookEnabled && settings.recipients) || settings.sendPush || settings.sendSMS != null) { + if (settings.sendBotvacOn) listString += "• Botvac On Notification\n" + if (settings.sendBotvacOff) listString += "• Botvac Off Notification\n" + if (settings.sendBotvacError) listString += "• Botvac Error Notification\n" + if (settings.sendBotvacBin) listString += "• Bin Full Notification\n" + if (settings.ssNotification) listString += "• SmartSchedule Notifications\n" + } if (listString != "") listString = listString.substring(0, listString.length() - 1) return listString } @@ -655,8 +665,8 @@ def eventHandler(evt) { if (evt.value == "paused") { log.trace "Setting auto dock for ${evt.displayName}" //If configured, set to dock automatically after one minute. - if (autoDock == true) { - runIn(autoDockDelay, scheduleAutoDock) + if (settings.autoDock) { + runIn(settings.autoDockDelay, scheduleAutoDock) } } else if (evt.value == "error") { @@ -665,7 +675,7 @@ def eventHandler(evt) { sendEvent(linkText:app.label, name:"${evt.displayName}", value:"error",descriptionText:"${evt.displayName} has an error", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} has an error" msg = "${evt.displayName} has an error" - if (sendBotvacError == true) { + if (settings.sendBotvacError) { messageHandler(msg, false) } } @@ -674,16 +684,17 @@ def eventHandler(evt) { //Increase poll interval during cleaning schedule("0 0/1 * * * ?", pollOn) //Record last cleaning time for device - state.lastClean[evt.deviceNetworkId] = now() + log.debug "$evt.device.deviceNetworkId has started cleaning" + state.lastClean[evt.device.deviceNetworkId] = now() //Remove SmartSchedule flag - state.smartSchedule[evt.deviceNetworkId] = false + state.smartSchedule[evt.device.deviceNetworkId] = false sendEvent(linkText:app.label, name:"${evt.displayName}", value:"on",descriptionText:"${evt.displayName} is on", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} is on" msg = "${evt.displayName} is on" - if (sendBotvacOn == true) { + if (settings.sendBotvacOn) { messageHandler(msg, false) } - if (autoSHM) { + if (settings.autoSHM) { if (location.currentState("alarmSystemStatus")?.value == "away") { sendEvent(linkText:app.label, name:"Smart Home Monitor", value:"stay",descriptionText:"Smart Home Monitor was set to stay", eventType:"SOLUTION_EVENT", displayed: true) log.trace "Smart Home Monitor is set to stay" @@ -699,7 +710,7 @@ def eventHandler(evt) { sendEvent(linkText:app.label, name:"${evt.displayName}", value:"bin full",descriptionText:"${evt.displayName} bin is full", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} bin is full" msg = "${evt.displayName} bin is full" - if (sendBotvacBin == true) { + if (settings.sendBotvacBin) { messageHandler(msg, false) } } @@ -709,7 +720,7 @@ def eventHandler(evt) { sendEvent(linkText:app.label, name:"${evt.displayName}", value:"off",descriptionText:"${evt.displayName} is off", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} is off" msg = "${evt.displayName} is off" - if (sendBotvacOff == true) { + if (settings.sendBotvacOff) { messageHandler(msg, false) } } @@ -737,7 +748,7 @@ def smartScheduleHandler(evt) { //Remove existing SmartSchedule flag state.smartSchedule[childDevice.deviceNetworkId] = false } - if (ssNotification) { + if (settings.ssNotification) { messageHandler("Neato SmartSchedule has reset all Botvac schedules as override switch ${evt.displayName} is on.", false) } } @@ -745,12 +756,12 @@ def smartScheduleHandler(evt) { else { //Check current mode is in Away list //Check time & day - allOK() - if ((location.mode in ssAwayModes) && allOk) { + if ((location.mode in settings.ssAwayModes) && allOk) { //For each vacuum getChildDevices().each { childDevice -> //If smartSchedule flag has been set, start clean. if (state.smartSchedule[childDevice.deviceNetworkId]) { - if (ssNotification) { + if (settings.ssNotification) { messageHandler("Neato SmartSchedule has started ${childDevice.displayName} cleaning.", false) } childDevice.on() @@ -778,19 +789,19 @@ def pollOn() { state.pollState = now() childDevice.poll() //Force on if last clean was a long time ago - if (childDevice.currentSwitch == "off" && forceClean && state.lastClean != null && state.lastClean[childDevice.deviceNetworkId] != null) { + if (childDevice.currentSwitch == "off" && settings.forceClean && state.lastClean != null && state.lastClean[childDevice.deviceNetworkId] != null) { def t = now() - state.lastClean[childDevice.deviceNetworkId] //Set SmartSchedule flag if SmartSchedule has not been set already, interval has elapsed and user is at home - if ((!(location.mode in ssAwayModes)) && (t > (ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { + if ((!(location.mode in settings.ssAwayModes)) && (t > (settings.ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { state.smartSchedule[childDevice.deviceNetworkId] = true - if (ssNotification) { + if (settings.ssNotification) { messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean when you're next away (date and time restrictions permitting). Please clear obstacles and leave internal doors open when you next leave the house.", false) } } log.debug "$childDevice.displayName last cleaned at " + state.lastClean[childDevice.deviceNetworkId] + ". ${t/86400000} days has elapsed since." - if (t > (forceCleanDelay * 86400000)) { + if (t > (settings.forceCleanDelay * 86400000)) { log.debug "Force clean activated as $t milliseconds has elapsed" - messageHandler(childDevice.displayName + " has not cleaned for " + forceCleanDelay + " days. Forcing a clean.", true) + messageHandler(childDevice.displayName + " has not cleaned for " + settings.forceCleanDelay + " days. Forcing a clean.", true) childDevice.on() } } @@ -801,7 +812,7 @@ def pollOn() { } if (!activeCleaners) { - if (autoSHM) { + if (settings.autoSHM) { if (location.currentState("alarmSystemStatus")?.value == "stay" && state.autoSHMchange == "y"){ sendEvent(linkText:app.label, name:"Smart Home Monitor", value:"away",descriptionText:"Smart Home Monitor was set back to away", eventType:"SOLUTION_EVENT", displayed: true) log.trace "Smart Home Monitor is set back to away" @@ -822,11 +833,11 @@ def pollOn() { def messageHandler(msg, forceFlag) { if (settings.sendSMS != null && !forceFlag) { - sendSms(sendSMS, msg) + sendSms(settings.sendSMS, msg) } - if (location.contactBookEnabled && recipients) { - sendNotificationToContacts(msg, recipients) - } else if (settings.sendPush == true || forceFlag) { + if (location.contactBookEnabled && settings.recipients) { + sendNotificationToContacts(msg, settings.recipients) + } else if (settings.sendPush || forceFlag) { sendPush(msg) } } @@ -905,7 +916,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1b\nDate: 17102016(2046)" + def text = "Neato (Connect)\nVersion: 1.1c\nDate: 18102016(1122)" } private def textCopyright() { From 98a379fa242f569c7abd4dfe82fef1a85407c8eb Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 11:34:38 +0100 Subject: [PATCH 224/685] Minor fix --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 0b549a457a4..655b65c42cb 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -182,7 +182,9 @@ def notificationsPAGE() { input "sendBotvacOff", "bool", title: "Notify when Botvacs are off?", required: false, defaultValue: false input "sendBotvacError", "bool", title: "Notify on Botvacs have an error?", required: false, defaultValue: true input "sendBotvacBin", "bool", title: "Notify when Botvacs have a full bin?", required: false, defaultValue: true - input "ssNotification", "bool", title: "Enable SmartSchedule notifications?", required: false, defaultValue: true + if (settings.smartScheduleEnabled) { + input "ssNotification", "bool", title: "Enable SmartSchedule notifications?", required: false, defaultValue: true + } } } } From 42f2f3dde55a6abd05e12671b55724332b05935a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 11:56:58 +0100 Subject: [PATCH 225/685] v1.1d - Bug fix. Custom state validation errors and error saving page message when upgrading from 1.0 to 1.1. --- .../neato-connect.src/neato-connect.groovy | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 655b65c42cb..c43a81e690d 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,7 +13,8 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY - * 18-10-2016: 1.1c - Bug fix attempt. + * 18-10-2016: 1.1d - Bug fix. Custom state validation errors and error saving page message when upgrading from 1.0 to 1.1. + * 18-10-2016: 1.1c - Bug fix. Smart schedule was not updating last clean time properly when Botvac was activated. * 17-10-2016: 1.1b - Set last clean value to new devices for smart schedule. * 17-10-2016: 1.1 - SmartSchedule functionality and minor fixes * 15-10-2016: 1.0c - Fix to auto SHM mode not triggering @@ -87,31 +88,34 @@ def authPage() { section ("Choose your Neato Botvacs:") { href("selectDevicePAGE", title: null, description: devicesSelected() ? "Devices:\n " + getDevicesSelectedString() : "Tap to select your Neato Botvacs", state: devicesSelected()) } - section ("SmartSchedule Configuration:") { - href("smartSchedulePAGE", title: null, description: smartScheduleSelected() ? getSmartScheduleString() : "Tap to configure SmartSchedule", state: smartScheduleSelected()) - } - section ("Preferences:") { - href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure preferences", state: preferencesSelected()) - } - section ("Notifications:") { - href("notificationsPAGE", title: null, description: notificationsSelected() ? getNotificationsString() : "Tap to configure notifications", state: notificationsSelected()) - } - def botvacList = "" - selectedBotvacs.each() { - def childDevice = getChildDevice("${it}") - try { - botvacList += "${childDevice.displayName} is ${childDevice.currentStatus}. Battery is ${childDevice.currentBattery}%\n" - } - catch (e) { - log.trace "Error checking status." - log.trace e + if (devicesSelected() == "complete") { + section ("SmartSchedule Configuration:") { + href("smartSchedulePAGE", title: null, description: smartScheduleSelected() ? getSmartScheduleString() : "Tap to configure SmartSchedule", state: smartScheduleSelected()) } - if (botvacList) { - section("Botvac Status:") { - paragraph image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato_botvac_image.png", botvacList.trim() + section ("Preferences:") { + href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure preferences", state: preferencesSelected()) + } + section ("Notifications:") { + href("notificationsPAGE", title: null, description: notificationsSelected() ? getNotificationsString() : "Tap to configure notifications", state: notificationsSelected()) + } + + def botvacList = "" + selectedBotvacs.each() { + def childDevice = getChildDevice("${it}") + try { + botvacList += "${childDevice.displayName} is ${childDevice.currentStatus}. Battery is ${childDevice.currentBattery}%\n" } - } - } + catch (e) { + log.trace "Error checking status." + log.trace e + } + if (botvacList) { + section("Botvac Status:") { + paragraph image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato_botvac_image.png", botvacList.trim() + } + } + } + } } } } @@ -165,8 +169,8 @@ def smartSchedulePAGE() { page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) { section { - input "starting", "time", title: "Starting", required: true - input "ending", "time", title: "Ending", required: true + input "starting", "time", title: "Starting", required: false + input "ending", "time", title: "Ending", required: false } } @@ -918,7 +922,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1c\nDate: 18102016(1122)" + def text = "Neato (Connect)\nVersion: 1.1d\nDate: 18102016(1155)" } private def textCopyright() { From 93f02ac09f2e9d6d5b6e229bb62fd0f3a320cb37 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 12:00:43 +0100 Subject: [PATCH 226/685] Minor fixes --- .../alyc100/neato-connect.src/neato-connect.groovy | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index c43a81e690d..5c0d385b962 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -148,7 +148,7 @@ def smartSchedulePAGE() { section("SmartSchedule restrictions:") { //Define time of day paragraph "Set SmartSchedule restrictions so that your Botvacs don't start in the middle of the night." - href "timeIntervalInput", title: "Operate Botvacs only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true + href "timeIntervalInput", title: "Operate Botvacs only during a certain time", description: getTimeLabel(settings.starting, settings.ending), state: greyedOutTime(settings.starting, settings.ending), refreshAfterSelection:true //Define allowed days of operation input ("days", "enum", title: "Operate Botvacs only on certain days of the week", multiple: true, required: false, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]) @@ -453,7 +453,7 @@ def initialize() { } subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) - if (settings.starting && settings.ending) { + if (settings.starting) { schedule(settings.starting, smartScheduleHandler) } else { @@ -871,10 +871,10 @@ private getDaysOk() { private getTimeOk() { def result = true - if (starting && ending) { + if (settings.starting && settings.ending) { def currTime = now() - def start = timeToday(starting).time - def stop = timeToday(ending).time + def start = timeToday(settings.starting).time + def stop = timeToday(settings.ending).time result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start } log.trace "timeOk = $result" From 57bfe81bc1693da08eaaf295c27f5fbeac79fea6 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 12:14:54 +0100 Subject: [PATCH 227/685] Variable consistency --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 5c0d385b962..a0d3546036b 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -854,7 +854,7 @@ private getAllOk() { private getDaysOk() { def result = true - if (days) { + if (settings.days) { def df = new java.text.SimpleDateFormat("EEEE") if (location.timeZone) { df.setTimeZone(location.timeZone) @@ -863,7 +863,7 @@ private getDaysOk() { df.setTimeZone(TimeZone.getTimeZone("Europe/London")) } def day = df.format(new Date()) - result = days.contains(day) + result = settings.days.contains(day) } log.trace "daysOk = $result" result From 4035adf1c3b61cc7aca2b2ca74c10f46c42b553f Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 12:32:11 +0100 Subject: [PATCH 228/685] Minor fix --- .../alyc100/neato-connect.src/neato-connect.groovy | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index a0d3546036b..04ff8e895b0 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -17,6 +17,7 @@ * 18-10-2016: 1.1c - Bug fix. Smart schedule was not updating last clean time properly when Botvac was activated. * 17-10-2016: 1.1b - Set last clean value to new devices for smart schedule. * 17-10-2016: 1.1 - SmartSchedule functionality and minor fixes + * * 15-10-2016: 1.0c - Fix to auto SHM mode not triggering * 14-10-2016: 1.0b - Minor fix to preference list * 14-10-2016: 1.0 - Initial Version @@ -83,6 +84,10 @@ def authPage() { } } else { updateDevices() + //Disable push option if contact book is enabled + if (location.contactBookEnabled) { + settings.sendPush = false + } dynamicPage(name: "auth", uninstall: false, install: false) { section { headerSECTION() } section ("Choose your Neato Botvacs:") { @@ -461,12 +466,6 @@ def initialize() { } subscribe(ssOverrideSwitch, "switch.on", smartScheduleHandler, [filterEvents: false]) } - - //Disable push option if contact book is enabled - if (location.contactBookEnabled) { - settings.sendPush = false - } - } def uninstalled() { From 6d2cba5da33286c5b22ffb5f1d544422bdd47a7a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 12:32:13 +0100 Subject: [PATCH 229/685] Minor fix From 1de5e2c4af10489c25b6651c1f1115c6daee80ff Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 21:04:40 +0100 Subject: [PATCH 230/685] v1.1.1 - Allow smart schedule to also be triggered on presence and switch events. Add option to specify how override switches work (all or any). --- .../neato-connect.src/neato-connect.groovy | 192 ++++++++++++++---- 1 file changed, 151 insertions(+), 41 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 04ff8e895b0..d03d14d2ef1 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,8 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 18-10-2016: 1.1.1 - Allow smart schedule to also be triggered on presence and switch events. Add option to specify how override switches work (all or any). + * * 18-10-2016: 1.1d - Bug fix. Custom state validation errors and error saving page message when upgrading from 1.0 to 1.1. * 18-10-2016: 1.1c - Bug fix. Smart schedule was not updating last clean time properly when Botvac was activated. * 17-10-2016: 1.1b - Set last clean value to new devices for smart schedule. @@ -88,6 +90,10 @@ def authPage() { if (location.contactBookEnabled) { settings.sendPush = false } + //Migrate settings from v1.1 and earlier to v1.2 + if (settings.smartScheduleEnabled && settings.ssScheduleTrigger == null) { + settings.ssScheduleTrigger = "mode" + } dynamicPage(name: "auth", uninstall: false, install: false) { section { headerSECTION() } section ("Choose your Neato Botvacs:") { @@ -143,12 +149,24 @@ def smartSchedulePAGE() { input "smartScheduleEnabled", "bool", title: "Enable SmartSchedule?", required: false, defaultValue: false, submitOnChange: true } if (settings.smartScheduleEnabled) { - section("Configure your Away Modes and cleaning interval:") { + section("Configure your cleaning interval and schedule triggers:") { //SmartSchedule configuration options. //Configure regular cleaning interval in days input ("ssCleaningInterval", "number", title: "Set your ideal cleaning interval in days", required: true, defaultValue: 3) - //Define your away modes - input ("ssAwayModes", "mode", title:"Specify your Away Modes:", multiple: true, required: true) + + //Define smart schedule trigger + input(name: "ssScheduleTrigger", title: "How do you want to trigger the schedule?", description: null, multiple: false, required: true, submitOnChange: true, type: "enum", options: ["mode": "Away Modes", "switch": "Switches", "presence": "Presence"], defaultValue: "mode") + + //Define your away modes + if (ssScheduleTrigger == "mode") { input ("ssAwayModes", "mode", title:"Specify your away modes:", multiple: true, required: true) } + if (ssScheduleTrigger == "switch") { + input ("ssSwitchTrigger", "capability.switch", title:"Which switches?", multiple: true, required: true) + input ("ssSwitchTriggerCondition", "enum", title:"Trigger schedule when:", multiple: false, required: true, options: ["any": "Any switch turns on", "all": "All switches are on"], defaultValue: "any") + } + if (ssScheduleTrigger == "presence") { + input ("ssPeopleAway", "capability.presenceSensor", title:"Which presence sensors?", multiple: true, required: true) + input ("ssPeopleAwayCondition", "enum", title:"Trigger schedule when:", multiple: false, required: true, options: ["any": "Someone leaves", "all": "Everyone is away"], defaultValue: "all") + } } section("SmartSchedule restrictions:") { //Define time of day @@ -161,7 +179,8 @@ def smartSchedulePAGE() { section("SmartSchedule overrides:") { //Define override switches to restart SmartSchedule countdown paragraph "Routine override switches/buttons will cancel the next scheduled clean and reset the interval countdown when switched on." - input ("ssOverrideSwitch", "capability.switch", title:"Set SmartSchedule override switches", multiple: true, required: false) + input ("ssOverrideSwitch", "capability.switch", title:"Set SmartSchedule override switches", multiple: true, required: false) + input ("ssOverrideSwitchCondition", "enum", title:"Override schedule when:", multiple: false, required: true, options: ["any": "Any switch turns on", "all": "All switches are on"], defaultValue: "any") } section("Notifications:") { paragraph "Turn on SmartSchedule notifications. You can configure specific recipients via Notification settings section." @@ -457,14 +476,17 @@ def initialize() { if (state.lastClean[childDevice.deviceNetworkId] == null) state.lastClean[childDevice.deviceNetworkId] = now() } - subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) + if (settings.ssScheduleTrigger == "mode") { subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) } + else if (settings.ssScheduleTrigger == "switch") { subscribe(settings.ssSwitchTrigger, "switch.on", smartScheduleHandler, [filterEvents: false]) } + else if (settings.ssScheduleTrigger == "presence") { subscribe(settings.ssPeopleAway, "presence", smartScheduleHandler, [filterEvents: false]) } + if (settings.starting) { schedule(settings.starting, smartScheduleHandler) } else { schedule("29 0 0 1/1 * ? *", smartScheduleHandler) } - subscribe(ssOverrideSwitch, "switch.on", smartScheduleHandler, [filterEvents: false]) + subscribe(settings.ssOverrideSwitch, "switch.on", smartScheduleHandler, [filterEvents: false]) } } @@ -580,10 +602,34 @@ def smartScheduleSelected() { def getSmartScheduleString() { def listString = "" if (smartScheduleEnabled) { - listString += "SmartSchedule set for every ${settings.ssCleaningInterval} days\n• When mode is ${settings.ssAwayModes}\n" + listString += "SmartSchedule set for every ${settings.ssCleaningInterval} days " + if (settings.ssScheduleTrigger == "mode") {listString += "when mode is ${settings.ssAwayModes}."} + else if (settings.ssScheduleTrigger == "switch") { + if (settings.ssSwitchTriggerCondition == "any") { + listString += "when any of ${settings.ssSwitchTrigger} turns on." + } else { + listString += "when ${settings.ssSwitchTrigger} are all on." + } + } + + else if (settings.ssScheduleTrigger == "presence") { + if (settings.ssPeopleAwayCondition == "any") { + listString += "when one of ${settings.ssPeopleAway} leaves." + } else { + listString += "when ${settings.ssPeopleAway} are all away." + } + } + + listString += "\n\nThe following restrictions apply:\n" if (settings.starting) listString += "• ${getTimeLabel(settings.starting, settings.ending)}\n" if (settings.days) listString += "• Only on $settings.days.\n" - if (settings.ssOverrideSwitch) listString += "• Override schedule if any of ${settings.ssOverrideSwitch} turns on." + if (settings.ssOverrideSwitch) { + if (settings.ssOverrideSwitchCondition == "any") { + listString += "• Override schedule if any of ${settings.ssOverrideSwitch} turns on." + } else { + listString += "• Override schedule if ${settings.ssOverrideSwitch} are all on." + } + } } return listString } @@ -607,16 +653,28 @@ def notificationsSelected() { } def getNotificationsString() { - def listString = " " - if (location.contactBookEnabled && settings.recipients) listString += "• Send notifications to " + settings.recipients + "\n" - if (settings.sendPush) listString += "• Send Push\n" - if (settings.sendSMS != null) listString += "• Send SMS to ${settings.sendSMS}\n" + def listString = "" + if (location.contactBookEnabled && settings.recipients) { + listString += "Send the following notifications to " + settings.recipients + } + else if (settings.sendPush) { + listString += "Send the following notifications" + } + + if (!settings.recipients && !settings.sendPush && settings.sendSMS != null) { + listString += "Send the following SMS to ${settings.sendSMS}" + } + else if (settings.sendSMS != null) { + listString += " and SMS to ${settings.sendSMS}" + } + if ((location.contactBookEnabled && settings.recipients) || settings.sendPush || settings.sendSMS != null) { - if (settings.sendBotvacOn) listString += "• Botvac On Notification\n" - if (settings.sendBotvacOff) listString += "• Botvac Off Notification\n" - if (settings.sendBotvacError) listString += "• Botvac Error Notification\n" - if (settings.sendBotvacBin) listString += "• Bin Full Notification\n" - if (settings.ssNotification) listString += "• SmartSchedule Notifications\n" + listString += ":\n" + if (settings.sendBotvacOn) listString += "• Botvac On\n" + if (settings.sendBotvacOff) listString += "• Botvac Off\n" + if (settings.sendBotvacError) listString += "• Botvac Error\n" + if (settings.sendBotvacBin) listString += "• Bin Full\n" + if (settings.ssNotification) listString += "• SmartSchedule\n" } if (listString != "") listString = listString.substring(0, listString.length() - 1) return listString @@ -735,33 +793,45 @@ def smartScheduleHandler(evt) { if (evt != null) { log.debug "Executing 'smartScheduleHandler' for ${evt.displayName}" } else { - log.debug "Executing 'smartScheduleHandler' for schedule event" + log.debug "Executing 'smartScheduleHandler' for scheduled event" } //If switch on for override event - if (evt != null && evt.name == "switch") { + if (evt != null && evt.name == "switch" && evt.device in settings.ssOverrideSwitch) { + def executeOverride = true + //If override switch condition is ALL... + if (ssOverrideSwitchCondition == "all") { + //Check all switches in override switch settings are on + for (switchVal in settings.ssOverrideSwitch.currentSwitch) { + if (switchVal == "off") { + executeOverride = false + break + } + } + } //For each vacuum - getChildDevices().each { childDevice -> - //Reset last clean date to current time - state.lastClean[childDevice.deviceNetworkId] = now() + if (executeOverride) { + getChildDevices().each { childDevice -> + //Reset last clean date to current time + state.lastClean[childDevice.deviceNetworkId] = now() - //DEBUG PURPOSES ONLY. FAKE TIME ON OVERRIDE SWITCH AND INCREASE POLL - //state.lastClean[childDevice.deviceNetworkId] = Date.parseToStringDate("Thu Oct 13 01:23:45 UTC 2016").getTime() - //unschedule(pollOn) - //schedule("0 0/1 * * * ?", pollOn) - //log.debug "Fake data loaded.... " + (now() - state.lastClean[childDevice.deviceNetworkId])/86400000 - - //Remove existing SmartSchedule flag - state.smartSchedule[childDevice.deviceNetworkId] = false - } - if (settings.ssNotification) { - messageHandler("Neato SmartSchedule has reset all Botvac schedules as override switch ${evt.displayName} is on.", false) + //Remove existing SmartSchedule flag + state.smartSchedule[childDevice.deviceNetworkId] = false + + //DEBUG PURPOSES ONLY. FAKE TIME ON OVERRIDE SWITCH AND INCREASE POLL + //state.lastClean[childDevice.deviceNetworkId] = Date.parseToStringDate("Thu Oct 13 01:23:45 UTC 2016").getTime() + //unschedule(pollOn) + //schedule("0 0/1 * * * ?", pollOn) + //log.debug "Fake data loaded.... " + (now() - state.lastClean[childDevice.deviceNetworkId])/86400000 + } + if (settings.ssNotification) { + messageHandler("Neato SmartSchedule has reset all Botvac schedules as override switch ${evt.displayName} is on.", false) + } } } - //If mode change event OR schedule trigger + //If mode change event, schedule trigger or presence trigger else { - //Check current mode is in Away list - //Check time & day - allOK() - if ((location.mode in settings.ssAwayModes) && allOk) { + //Check conditions, time andd day have been met + if (allOk) { //For each vacuum getChildDevices().each { childDevice -> //If smartSchedule flag has been set, start clean. @@ -796,11 +866,15 @@ def pollOn() { //Force on if last clean was a long time ago if (childDevice.currentSwitch == "off" && settings.forceClean && state.lastClean != null && state.lastClean[childDevice.deviceNetworkId] != null) { def t = now() - state.lastClean[childDevice.deviceNetworkId] + //Set SmartSchedule flag if SmartSchedule has not been set already, interval has elapsed and user is at home - if ((!(location.mode in settings.ssAwayModes)) && (t > (settings.ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { + if ((!triggerConditionsOk) && (t > (settings.ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { state.smartSchedule[childDevice.deviceNetworkId] = true if (settings.ssNotification) { - messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean when you're next away (date and time restrictions permitting). Please clear obstacles and leave internal doors open when you next leave the house.", false) + def reason = "you're next away" + if (settings.ssScheduleTrigger == "switch") { reason = "your selected switches turn on" } + else if (settings.ssScheduleTrigger == "presence") { reason = "your selected presence sensors leave"} + messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean when " + reason + " (date and time restrictions permitting). Please clear obstacles and leave internal doors open when you next leave the house.", false) } } log.debug "$childDevice.displayName last cleaned at " + state.lastClean[childDevice.deviceNetworkId] + ". ${t/86400000} days has elapsed since." @@ -847,8 +921,44 @@ def messageHandler(msg, forceFlag) { } } + private getAllOk() { - daysOk && timeOk + triggerConditionsOk && daysOk && timeOk +} + +private getTriggerConditionsOk() { + //Calculate, depending on smart schedule trigger mode, whether conditions currently match + def result = false + + if (settings.ssScheduleTrigger == "mode") { + result = location.mode in settings.ssAwayModes + } else if (settings.ssScheduleTrigger == "switch") { + if (settings.ssSwitchTriggerCondition == "any") { + result = "on" in settings.ssSwitchTrigger.currentSwitch + } else { + result = true + for (switchVal in settings.ssSwitchTrigger.currentSwitch) { + if (switchVal == "off") { + result = false + break + } + } + } + } else if (settings.ssScheduleTrigger == "presence") { + if (settings.ssPeopleAwayCondition == "any") { + result = "not present" in settings.ssPeopleAway.currentPresence + } else { + result = true + for (person in settings.ssPeopleAway) { + if (person.currentPresence == "present") { + result = false + break + } + } + } + } + log.trace "triggerConditionsOk = $result" + result } private getDaysOk() { @@ -921,7 +1031,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1d\nDate: 18102016(1155)" + def text = "Neato (Connect)\nVersion: 1.1.1\nDate: 18102016(2200)" } private def textCopyright() { From 944cc2c110745e0ea3bf7665e4251f8623cb090d Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 18 Oct 2016 21:06:31 +0100 Subject: [PATCH 231/685] Correction to build date --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index d03d14d2ef1..c02f1bd1364 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -1031,7 +1031,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.1\nDate: 18102016(2200)" + def text = "Neato (Connect)\nVersion: 1.1.1\nDate: 18102016(2100)" } private def textCopyright() { From 7ee7d113bbf989238169d01a559827717a8776e9 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 19 Oct 2016 10:22:11 +0100 Subject: [PATCH 232/685] v1.1.1b - Unschedule auto dock if cleaning is resumed. --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index c02f1bd1364..7bc1e201892 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 19-10-2016: 1.1.1b - Unschedule auto dock if cleaning is resumed. * 18-10-2016: 1.1.1 - Allow smart schedule to also be triggered on presence and switch events. Add option to specify how override switches work (all or any). * * 18-10-2016: 1.1d - Bug fix. Custom state validation errors and error saving page message when upgrading from 1.0 to 1.1. @@ -734,6 +735,7 @@ def eventHandler(evt) { } else if (evt.value == "error") { unschedule(pollOn) + unschedule(scheduleAutoDock) runEvery5Minutes('pollOn') sendEvent(linkText:app.label, name:"${evt.displayName}", value:"error",descriptionText:"${evt.displayName} has an error", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} has an error" @@ -744,6 +746,7 @@ def eventHandler(evt) { } else if (evt.value == "cleaning") { unschedule(pollOn) + unschedule(scheduleAutoDock) //Increase poll interval during cleaning schedule("0 0/1 * * * ?", pollOn) //Record last cleaning time for device @@ -779,6 +782,7 @@ def eventHandler(evt) { } else if (evt.value == "ready") { unschedule(pollOn) + unschedule(scheduleAutoDock) runEvery5Minutes('pollOn') sendEvent(linkText:app.label, name:"${evt.displayName}", value:"off",descriptionText:"${evt.displayName} is off", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} is off" @@ -1031,7 +1035,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.1\nDate: 18102016(2100)" + def text = "Neato (Connect)\nVersion: 1.1.1b\nDate: 19102016(1000)" } private def textCopyright() { From 50f14561134e1485d81fd8a808e1d92fda8d9ca5 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 19 Oct 2016 16:28:21 +0100 Subject: [PATCH 233/685] v1.1.2 - Option to specify "no trigger" in SmartSchedule. Notification when Force clean is due in 24 hours. Separate Smart schedule time markers from force clean time markers. --- .../neato-connect.src/neato-connect.groovy | 112 ++++++++++++++---- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 7bc1e201892..1dcbe68e360 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,9 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 19-10-2016: 1.1.2 - Option to specify "no trigger" in SmartSchedule. Notification when Force clean is due in 24 hours. + Seperate Smart schedule time markers from force clean time markers. + * * 19-10-2016: 1.1.1b - Unschedule auto dock if cleaning is resumed. * 18-10-2016: 1.1.1 - Allow smart schedule to also be triggered on presence and switch events. Add option to specify how override switches work (all or any). * @@ -156,7 +159,7 @@ def smartSchedulePAGE() { input ("ssCleaningInterval", "number", title: "Set your ideal cleaning interval in days", required: true, defaultValue: 3) //Define smart schedule trigger - input(name: "ssScheduleTrigger", title: "How do you want to trigger the schedule?", description: null, multiple: false, required: true, submitOnChange: true, type: "enum", options: ["mode": "Away Modes", "switch": "Switches", "presence": "Presence"], defaultValue: "mode") + input(name: "ssScheduleTrigger", title: "How do you want to trigger the schedule?", description: null, multiple: false, required: true, submitOnChange: true, type: "enum", options: ["mode": "Away Modes", "switch": "Switches", "presence": "Presence", "none": "No Triggers"], defaultValue: "mode") //Define your away modes if (ssScheduleTrigger == "mode") { input ("ssAwayModes", "mode", title:"Specify your away modes:", multiple: true, required: true) } @@ -441,7 +444,6 @@ def updated() { } def initialize() { - // TODO: subscribe to attributes, devices, locations, etc. //Initialise variables if (state.lastClean == null) { state.lastClean = [:] @@ -449,7 +451,17 @@ def initialize() { if (state.smartSchedule == null) { state.smartSchedule = [:] } + if (state.forceCleanNotificationSent == null) { + state.forceCleanNotificationSent = [:] + } + if (state.botvacOnTimeMarker == null) { + state.botvacOnTimeMarker = [:] + } if (selectedBotvacs) addBotvacs() + + getChildDevices().each { childDevice -> + if (state.botvacOnTimeMarker[childDevice.deviceNetworkId] == null) state.botvacOnTimeMarker[childDevice.deviceNetworkId] = now() + } unschedule() runEvery5Minutes('pollOn') // Asynchronously refresh devices so we don't block @@ -467,14 +479,24 @@ def initialize() { subscribe(childDevice, "status.error", eventHandler, [filterEvents: false]) subscribe(childDevice, "status.paused", eventHandler, [filterEvents: false]) subscribe(childDevice, "bin.full", eventHandler, [filterEvents: false]) + if (state.forceCleanNotificationSent[childDevice.deviceNetworkId] == null) state.forceCleanNotificationSent[childDevice.deviceNetworkId] = false } } //subscribe to events for smartSchedule if (smartScheduleEnabled) { + //store last mode selected + if (!state.lastTriggerMode) state.lastTriggerMode = "" + getChildDevices().each { childDevice -> //Initialize flags for Smart Schedule if (state.smartSchedule[childDevice.deviceNetworkId] == null) state.smartSchedule[childDevice.deviceNetworkId] = false if (state.lastClean[childDevice.deviceNetworkId] == null) state.lastClean[childDevice.deviceNetworkId] = now() + //Trigger has changed so reset all smart schedule flags + if (state.lastTriggerMode != settings.ssScheduleTrigger) { + log.debug "Smart schedule trigger mode has changed. Resetting smart schedule flag." + state.smartSchedule[childDevice.deviceNetworkId] = false + state.lastTriggerMode = settings.ssScheduleTrigger + } } if (settings.ssScheduleTrigger == "mode") { subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) } @@ -752,6 +774,8 @@ def eventHandler(evt) { //Record last cleaning time for device log.debug "$evt.device.deviceNetworkId has started cleaning" state.lastClean[evt.device.deviceNetworkId] = now() + state.botvacOnTimeMarker[evt.device.deviceNetworkId] = now() + state.forceCleanNotificationSent[evt.device.deviceNetworkId] = false //Remove SmartSchedule flag state.smartSchedule[evt.device.deviceNetworkId] = false sendEvent(linkText:app.label, name:"${evt.displayName}", value:"on",descriptionText:"${evt.displayName} is on", eventType:"SOLUTION_EVENT", displayed: true) @@ -817,7 +841,7 @@ def smartScheduleHandler(evt) { getChildDevices().each { childDevice -> //Reset last clean date to current time state.lastClean[childDevice.deviceNetworkId] = now() - + state.forceCleanNotificationSent[evt.device.deviceNetworkId] = false //Remove existing SmartSchedule flag state.smartSchedule[childDevice.deviceNetworkId] = false @@ -834,18 +858,9 @@ def smartScheduleHandler(evt) { } //If mode change event, schedule trigger or presence trigger else { - //Check conditions, time andd day have been met - if (allOk) { - //For each vacuum - getChildDevices().each { childDevice -> - //If smartSchedule flag has been set, start clean. - if (state.smartSchedule[childDevice.deviceNetworkId]) { - if (settings.ssNotification) { - messageHandler("Neato SmartSchedule has started ${childDevice.displayName} cleaning.", false) - } - childDevice.on() - } - } + //Check conditions, time andd day have been met and execute clean. If no trigger is specified rely on pollOn method to start clean. + if (settings.ssScheduleTrigger != "none") { + startConditionalClean() } } } @@ -864,30 +879,62 @@ def pollOn() { def activeCleaners = false log.debug "Last clean states: ${state.lastClean}" log.debug "Smart schedule states: ${state.smartSchedule}" + log.debug "Botvac ON time markers: ${state.botvacOnTimeMarker}" getChildDevices().each { childDevice -> state.pollState = now() childDevice.poll() //Force on if last clean was a long time ago if (childDevice.currentSwitch == "off" && settings.forceClean && state.lastClean != null && state.lastClean[childDevice.deviceNetworkId] != null) { def t = now() - state.lastClean[childDevice.deviceNetworkId] + def timeSinceOn = now() - state.botvacOnTimeMarker[childDevice.deviceNetworkId] + log.debug "$childDevice.displayName ON time marker at " + state.botvacOnTimeMarker[childDevice.deviceNetworkId] + ". ${timeSinceOn/86400000} days has elapsed since. ${settings.forceCleanDelay - (timeSinceOn/86400000)} days to force clean." + log.debug "$childDevice.displayName schedule marker at " + state.lastClean[childDevice.deviceNetworkId] + ". ${t/86400000} days has elapsed since. ${settings.ssCleaningInterval - (t/86400000)} days to scheduled clean." //Set SmartSchedule flag if SmartSchedule has not been set already, interval has elapsed and user is at home - if ((!triggerConditionsOk) && (t > (settings.ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { + if ((settings.ssScheduleTrigger == "none") && ((settings.ssCleaningInterval - (t/86400000)) < 1) && (!state.smartSchedule[childDevice.deviceNetworkId])) { + //hour calculation for notification of next clean + def hours = "24" + if (settings.starting) { + def currTime = now() + def start = timeToday(settings.starting).time + if (start < currTime) start += 86400000 + hours = Math.round(new BigDecimal((start - currTime)/3600000)).toString() + } + state.smartSchedule[childDevice.deviceNetworkId] = true + if (settings.ssNotification) { + messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean in ${hours} hours (date and time restrictions permitting). Please clear obstacles and leave internal doors open ready for the clean.", false) + } + } else if ((!triggerConditionsOk) && (t > (settings.ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { state.smartSchedule[childDevice.deviceNetworkId] = true if (settings.ssNotification) { def reason = "you're next away" if (settings.ssScheduleTrigger == "switch") { reason = "your selected switches turn on" } else if (settings.ssScheduleTrigger == "presence") { reason = "your selected presence sensors leave"} - messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean when " + reason + " (date and time restrictions permitting). Please clear obstacles and leave internal doors open when you next leave the house.", false) + messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean when " + reason + " (date and time restrictions permitting). Please clear obstacles and leave internal doors open ready for the clean.", false) } } - log.debug "$childDevice.displayName last cleaned at " + state.lastClean[childDevice.deviceNetworkId] + ". ${t/86400000} days has elapsed since." - if (t > (settings.forceCleanDelay * 86400000)) { - log.debug "Force clean activated as $t milliseconds has elapsed" + + //Create 24 hour warning for force clean. + if ((state.forceCleanNotificationSent != null) && (!state.forceCleanNotificationSent[childDevice.deviceNetworkId]) && ((settings.forceCleanDelay - (timeSinceOn/86400000)) < 1)) { + //Send notification when force clean is due + log.debug "Force clean due within 24 hours" + messageHandler(childDevice.displayName + " has not cleaned for " + (settings.forceCleanDelay - 1) + " days. Forcing a clean in 24 hours. Please clear obstacles and leave internal doors open ready for the clean.", true) + state.forceCleanNotificationSent[childDevice.deviceNetworkId] = true + } + + //Execute force clean (no conditions need checking) + if (timeSinceOn > (settings.forceCleanDelay * 86400000)) { + log.debug "Force clean activated as ${timeSinceOn/86400000} days has elapsed" messageHandler(childDevice.displayName + " has not cleaned for " + settings.forceCleanDelay + " days. Forcing a clean.", true) childDevice.on() } } + + //If no trigger has been set for smart schedule, execute clean when interval time has elapsed + if ((settings.ssScheduleTrigger == "none") && state.smartSchedule[childDevice.deviceNetworkId] && (t > (settings.ssCleaningInterval * 86400000))) { + startConditionalClean() + } + //Search for active cleaners if (childDevice.latestState('status').stringValue == 'cleaning') { activeCleaners = true @@ -913,6 +960,20 @@ def pollOn() { } //Helper methods +def startConditionalClean() { + if (allOk) { + //For each vacuum + getChildDevices().each { childDevice -> + //If smartSchedule flag has been set, start clean. + if (state.smartSchedule[childDevice.deviceNetworkId]) { + if (settings.ssNotification) { + messageHandler("Neato SmartSchedule has started ${childDevice.displayName} cleaning.", false) + } + childDevice.on() + } + } + } +} def messageHandler(msg, forceFlag) { if (settings.sendSMS != null && !forceFlag) { @@ -932,7 +993,7 @@ private getAllOk() { private getTriggerConditionsOk() { //Calculate, depending on smart schedule trigger mode, whether conditions currently match - def result = false + def result = true if (settings.ssScheduleTrigger == "mode") { result = location.mode in settings.ssAwayModes @@ -940,7 +1001,6 @@ private getTriggerConditionsOk() { if (settings.ssSwitchTriggerCondition == "any") { result = "on" in settings.ssSwitchTrigger.currentSwitch } else { - result = true for (switchVal in settings.ssSwitchTrigger.currentSwitch) { if (switchVal == "off") { result = false @@ -952,7 +1012,6 @@ private getTriggerConditionsOk() { if (settings.ssPeopleAwayCondition == "any") { result = "not present" in settings.ssPeopleAway.currentPresence } else { - result = true for (person in settings.ssPeopleAway) { if (person.currentPresence == "present") { result = false @@ -960,7 +1019,8 @@ private getTriggerConditionsOk() { } } } - } + } + log.trace "triggerConditionsOk = $result" result } @@ -1033,9 +1093,9 @@ def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/c def getBuildRedirectUrl() { return "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" } def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } -def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } +def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.1b\nDate: 19102016(1000)" + def text = "Neato (Connect)\nVersion: 1.1.2\nDate: 19102016(1620)" } private def textCopyright() { From 688955e296ef559e003e389d10d727e107d3cc7d Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 20 Oct 2016 09:54:08 +0100 Subject: [PATCH 234/685] v1.1.2b - Bug fix. SmartSchedule does not operate if force clean option is disabled. --- .../neato-connect.src/neato-connect.groovy | 112 +++++++++--------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 1dcbe68e360..9db49db2b6c 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,8 +13,9 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 20-10-2016: 1.1.2b - Bug fix. SmartSchedule does not operate if force clean option is disabled. * 19-10-2016: 1.1.2 - Option to specify "no trigger" in SmartSchedule. Notification when Force clean is due in 24 hours. - Seperate Smart schedule time markers from force clean time markers. + Separate Smart schedule time markers from force clean time markers. * * 19-10-2016: 1.1.1b - Unschedule auto dock if cleaning is resumed. * 18-10-2016: 1.1.1 - Allow smart schedule to also be triggered on presence and switch events. Add option to specify how override switches work (all or any). @@ -467,7 +468,7 @@ def initialize() { runEvery5Minutes('pollOn') // Asynchronously refresh devices so we don't block //subscribe to events for notifications if activated - if (smartScheduleEnabled || preferencesSelected() == "complete" || notificationsSelected() == "complete") { + if (settings.smartScheduleEnabled || preferencesSelected() == "complete" || notificationsSelected() == "complete") { getChildDevices().each { childDevice -> subscribe(childDevice, "status.cleaning", eventHandler, [filterEvents: false]) } @@ -483,7 +484,7 @@ def initialize() { } } //subscribe to events for smartSchedule - if (smartScheduleEnabled) { + if (settings.smartScheduleEnabled) { //store last mode selected if (!state.lastTriggerMode) state.lastTriggerMode = "" @@ -619,12 +620,12 @@ def getDevicesSelectedString() { } def smartScheduleSelected() { - return smartScheduleEnabled ? "complete" : null + return settings.smartScheduleEnabled ? "complete" : null } def getSmartScheduleString() { def listString = "" - if (smartScheduleEnabled) { + if (settings.smartScheduleEnabled) { listString += "SmartSchedule set for every ${settings.ssCleaningInterval} days " if (settings.ssScheduleTrigger == "mode") {listString += "when mode is ${settings.ssAwayModes}."} else if (settings.ssScheduleTrigger == "switch") { @@ -883,56 +884,61 @@ def pollOn() { getChildDevices().each { childDevice -> state.pollState = now() childDevice.poll() - //Force on if last clean was a long time ago - if (childDevice.currentSwitch == "off" && settings.forceClean && state.lastClean != null && state.lastClean[childDevice.deviceNetworkId] != null) { - def t = now() - state.lastClean[childDevice.deviceNetworkId] - def timeSinceOn = now() - state.botvacOnTimeMarker[childDevice.deviceNetworkId] - log.debug "$childDevice.displayName ON time marker at " + state.botvacOnTimeMarker[childDevice.deviceNetworkId] + ". ${timeSinceOn/86400000} days has elapsed since. ${settings.forceCleanDelay - (timeSinceOn/86400000)} days to force clean." - log.debug "$childDevice.displayName schedule marker at " + state.lastClean[childDevice.deviceNetworkId] + ". ${t/86400000} days has elapsed since. ${settings.ssCleaningInterval - (t/86400000)} days to scheduled clean." - - //Set SmartSchedule flag if SmartSchedule has not been set already, interval has elapsed and user is at home - if ((settings.ssScheduleTrigger == "none") && ((settings.ssCleaningInterval - (t/86400000)) < 1) && (!state.smartSchedule[childDevice.deviceNetworkId])) { - //hour calculation for notification of next clean - def hours = "24" - if (settings.starting) { - def currTime = now() - def start = timeToday(settings.starting).time - if (start < currTime) start += 86400000 - hours = Math.round(new BigDecimal((start - currTime)/3600000)).toString() - } - state.smartSchedule[childDevice.deviceNetworkId] = true - if (settings.ssNotification) { - messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean in ${hours} hours (date and time restrictions permitting). Please clear obstacles and leave internal doors open ready for the clean.", false) - } - } else if ((!triggerConditionsOk) && (t > (settings.ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { - state.smartSchedule[childDevice.deviceNetworkId] = true - if (settings.ssNotification) { - def reason = "you're next away" - if (settings.ssScheduleTrigger == "switch") { reason = "your selected switches turn on" } - else if (settings.ssScheduleTrigger == "presence") { reason = "your selected presence sensors leave"} - messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean when " + reason + " (date and time restrictions permitting). Please clear obstacles and leave internal doors open ready for the clean.", false) - } - } + + if (childDevice.currentSwitch == "off") { + //Update smart schedule state. Create notification when clean is due. + if (settings.smartScheduleEnabled && state.lastClean != null && state.lastClean[childDevice.deviceNetworkId] != null) { + def t = now() - state.lastClean[childDevice.deviceNetworkId] + log.debug "$childDevice.displayName schedule marker at " + state.lastClean[childDevice.deviceNetworkId] + ". ${t/86400000} days has elapsed since. ${settings.ssCleaningInterval - (t/86400000)} days to scheduled clean." - //Create 24 hour warning for force clean. - if ((state.forceCleanNotificationSent != null) && (!state.forceCleanNotificationSent[childDevice.deviceNetworkId]) && ((settings.forceCleanDelay - (timeSinceOn/86400000)) < 1)) { - //Send notification when force clean is due - log.debug "Force clean due within 24 hours" - messageHandler(childDevice.displayName + " has not cleaned for " + (settings.forceCleanDelay - 1) + " days. Forcing a clean in 24 hours. Please clear obstacles and leave internal doors open ready for the clean.", true) - state.forceCleanNotificationSent[childDevice.deviceNetworkId] = true + //Set SmartSchedule flag if SmartSchedule has not been set already, interval has elapsed and trigger conditions are not met + if ((settings.ssScheduleTrigger == "none") && ((settings.ssCleaningInterval - (t/86400000)) < 1) && (!state.smartSchedule[childDevice.deviceNetworkId])) { + //hour calculation for notification of next clean + def hours = "24" + if (settings.starting) { + def currTime = now() + def start = timeToday(settings.starting).time + if (start < currTime) start += 86400000 + hours = Math.round(new BigDecimal((start - currTime)/3600000)).toString() + } + state.smartSchedule[childDevice.deviceNetworkId] = true + if (settings.ssNotification) { + messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean in ${hours} hours (date and time restrictions permitting). Please clear obstacles and leave internal doors open ready for the clean.", false) + } + } else if ((!triggerConditionsOk) && (t > (settings.ssCleaningInterval * 86400000)) && (!state.smartSchedule[childDevice.deviceNetworkId])) { + state.smartSchedule[childDevice.deviceNetworkId] = true + if (settings.ssNotification) { + def reason = "you're next away" + if (settings.ssScheduleTrigger == "switch") { reason = "your selected switches turn on" } + else if (settings.ssScheduleTrigger == "presence") { reason = "your selected presence sensors leave"} + messageHandler("Neato SmartSchedule has scheduled ${childDevice.displayName} for a clean when " + reason + " (date and time restrictions permitting). Please clear obstacles and leave internal doors open ready for the clean.", false) + } + } + //If no trigger has been set for smart schedule, execute clean when interval time has elapsed + if ((settings.ssScheduleTrigger == "none") && state.smartSchedule[childDevice.deviceNetworkId] && (t > (settings.ssCleaningInterval * 86400000))) { + startConditionalClean() + } } + //Update force clean state and create notification when clean is due. + if (settings.forceClean && state.botvacOnTimeMarker != null && state.botvacOnTimeMarker[childDevice.deviceNetworkId] != null) { + def t = now() - state.botvacOnTimeMarker[childDevice.deviceNetworkId] + log.debug "$childDevice.displayName ON time marker at " + state.botvacOnTimeMarker[childDevice.deviceNetworkId] + ". ${t/86400000} days has elapsed since. ${settings.forceCleanDelay - (t/86400000)} days to force clean." + + //Create 24 hour warning for force clean. + if ((state.forceCleanNotificationSent != null) && (!state.forceCleanNotificationSent[childDevice.deviceNetworkId]) && ((settings.forceCleanDelay - (t/86400000)) < 1)) { + //Send notification when force clean is due + log.debug "Force clean due within 24 hours" + messageHandler(childDevice.displayName + " has not cleaned for " + (settings.forceCleanDelay - 1) + " days. Forcing a clean in 24 hours. Please clear obstacles and leave internal doors open ready for the clean.", true) + state.forceCleanNotificationSent[childDevice.deviceNetworkId] = true + } - //Execute force clean (no conditions need checking) - if (timeSinceOn > (settings.forceCleanDelay * 86400000)) { - log.debug "Force clean activated as ${timeSinceOn/86400000} days has elapsed" - messageHandler(childDevice.displayName + " has not cleaned for " + settings.forceCleanDelay + " days. Forcing a clean.", true) - childDevice.on() - } - } - - //If no trigger has been set for smart schedule, execute clean when interval time has elapsed - if ((settings.ssScheduleTrigger == "none") && state.smartSchedule[childDevice.deviceNetworkId] && (t > (settings.ssCleaningInterval * 86400000))) { - startConditionalClean() + //Execute force clean (no conditions need checking) + if (t > (settings.forceCleanDelay * 86400000)) { + log.debug "Force clean activated as ${t/86400000} days has elapsed" + messageHandler(childDevice.displayName + " has not cleaned for " + settings.forceCleanDelay + " days. Forcing a clean.", true) + childDevice.on() + } + } } //Search for active cleaners @@ -1095,7 +1101,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.2\nDate: 19102016(1620)" + def text = "Neato (Connect)\nVersion: 1.1.2b\nDate: 20102016(0950)" } private def textCopyright() { From ff61d2e82bb0246860d1a05c74d348f0e03c0d06 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 20 Oct 2016 13:18:44 +0100 Subject: [PATCH 235/685] Minor wording alteration within smart app --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 9db49db2b6c..6fdb6bc9a7a 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -150,7 +150,7 @@ def selectDevicePAGE() { def smartSchedulePAGE() { return dynamicPage(name: "smartSchedulePAGE", title: "SmartSchedule Configuration", install: false, uninstall: false) { section() { - paragraph "Configure a dymanic schedule for your Botvacs so that they can clean whilst you're out of the house." + paragraph "Configure a dymanic schedule for your Botvacs so that they can clean on a regular interval but based on mode, presence sensor and switch triggers." input "smartScheduleEnabled", "bool", title: "Enable SmartSchedule?", required: false, defaultValue: false, submitOnChange: true } if (settings.smartScheduleEnabled) { @@ -1101,7 +1101,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.2b\nDate: 20102016(0950)" + def text = "Neato (Connect)\nVersion: 1.1.2b\nDate: 20102016(1320)" } private def textCopyright() { From 99b6f374c1fbb33183ccd5897c958bcb3a4820bb Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 20 Oct 2016 13:22:07 +0100 Subject: [PATCH 236/685] Very minor wording change --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 6fdb6bc9a7a..c8c3fd419e0 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -150,7 +150,7 @@ def selectDevicePAGE() { def smartSchedulePAGE() { return dynamicPage(name: "smartSchedulePAGE", title: "SmartSchedule Configuration", install: false, uninstall: false) { section() { - paragraph "Configure a dymanic schedule for your Botvacs so that they can clean on a regular interval but based on mode, presence sensor and switch triggers." + paragraph "Configure a dymanic schedule for your Botvacs so that they can clean on a regular interval but based on mode, presence sensor or switch triggers." input "smartScheduleEnabled", "bool", title: "Enable SmartSchedule?", required: false, defaultValue: false, submitOnChange: true } if (settings.smartScheduleEnabled) { @@ -1101,7 +1101,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.2b\nDate: 20102016(1320)" + def text = "Neato (Connect)\nVersion: 1.1.2b\nDate: 20102016(0950)" } private def textCopyright() { From c5aa33e0c3d16f73fce6ce3d7858327da65a35e4 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 20 Oct 2016 13:23:46 +0100 Subject: [PATCH 237/685] Update build number --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index c8c3fd419e0..21c632cca0c 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -1101,7 +1101,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.2b\nDate: 20102016(0950)" + def text = "Neato (Connect)\nVersion: 1.1.2b\nDate: 20102016(1320)" } private def textCopyright() { From c5e9437bd485c273ec536cad2ba01c64da04e183 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Thu, 20 Oct 2016 17:20:21 +0100 Subject: [PATCH 238/685] Update neato-connect.groovy --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 21c632cca0c..7db40fabdb3 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * * 20-10-2016: 1.1.2b - Bug fix. SmartSchedule does not operate if force clean option is disabled. * 19-10-2016: 1.1.2 - Option to specify "no trigger" in SmartSchedule. Notification when Force clean is due in 24 hours. Separate Smart schedule time markers from force clean time markers. @@ -1122,4 +1123,4 @@ def clientSecret() { } else { return appSettings.clientSecret } -} \ No newline at end of file +} From bd8004bca4a15fd5b31babe0082b1a6c52c5c863 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 20 Oct 2016 22:56:11 +0100 Subject: [PATCH 239/685] v1.1 - Added smart schedule and force clean status messages. Added smart schedule reset button. Disable Neato Robot Schedule if SmartSchedule is enabled --- .../neato-botvac-connected-series.groovy | 75 ++++++++++++++++--- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy index 2afe40b7487..a16e1b44b2c 100644 --- a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy +++ b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy @@ -13,6 +13,9 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 20-10-2016: 1.1 - Added smart schedule and force clean status messages. Added smart schedule reset button. + * Disable Neato Robot Schedule if SmartSchedule is enabled + * * 14-10-2016: 1.0 - Initial Version * */ @@ -31,6 +34,7 @@ metadata { command "dock" command "enableSchedule" command "disableSchedule" + command "resetSmartSchedule" attribute "network","string" attribute "bin","string" @@ -51,7 +55,13 @@ metadata { attributeState "statusMsg", label:'${currentValue}' } } - + valueTile("smartScheduleStatusMessage", "device.smartScheduleStatusMessage", decoration: "flat", width: 3, height: 1) { + state "default", label: '${currentValue}' + } + + valueTile("forceCleanStatusMessage", "device.forceCleanStatusMessage", decoration: "flat", width: 3, height: 1) { + state "default", label: '${currentValue}' + } valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { state "battery", label:'${currentValue}% battery', unit:"" } @@ -70,8 +80,8 @@ metadata { }*/ standardTile("network", "device.network", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { state ("default", label:'unknown', icon: "st.unknown.unknown.unknown") - state ("Connected", label:'Link Good', icon: "st.Health & Wellness.health9", backgroundColor: "#79b821") - state ("Not Connected", label:'Link Bad', icon: "st.Health & Wellness.health9", backgroundColor: "#bc2323") + state ("Connected", label:'Online', icon: "st.Health & Wellness.health9", backgroundColor: "#79b821") + state ("Not Connected", label:'Offline', icon: "st.Health & Wellness.health9", backgroundColor: "#bc2323") } standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { state("default", label:'refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon") @@ -84,15 +94,15 @@ metadata { state ("paused", label:'${currentValue}', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png") } - standardTile("dockStatus", "device.dockStatus", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { + standardTile("dockStatus", "device.dockStatus", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { state ("docked", label:'DOCKED', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/auto-charge-resume.png") state ("dockable", label:'DOCK', action: "dock", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato_staub.png") state ("undocked", label:'UNDOCKED', icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png") } - standardTile("scheduled", "device.scheduled", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { - state ("true", label:'Sched', action: "disableSchedule", icon:"st.Office.office7") - state ("false", label:'Manual', action: "enableSchedule", icon: "st.Appliances.appliances13") + standardTile("scheduled", "device.scheduled", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false, decoration: "flat") { + state ("true", label:'enable manual', action: "disableSchedule", icon:"st.Office.office7") + state ("false", label:'enable auto', action: "enableSchedule", icon: "st.Appliances.appliances13") } standardTile("dockHasBeenSeen", "device.dockHasBeenSeen", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { @@ -100,8 +110,12 @@ metadata { state ("false", label:'SEARCHING', backgroundColor: "#E5E500", icon:"st.Transportation.transportation13") } + standardTile("resetSmartSchedule", "device.resetSmartSchedule", inactiveLabel: false, width: 2, height: 2, decoration: "flat") { + state("default", label:'reset schedule', action:"resetSmartSchedule", icon:"st.Office.office6") + } + main("clean") - details(["clean","status","battery","bin","network", "dockStatus", "charging", "dockHasBeenSeen", "scheduled", "refresh"]) + details(["clean","smartScheduleStatusMessage", "forceCleanStatusMessage", "status","battery","bin","network", "dockStatus", "charging", "dockHasBeenSeen", "scheduled", "resetSmartSchedule", "refresh"]) } } @@ -165,6 +179,12 @@ def setOffline() { sendEvent(name: 'network', value: "Not Connected" as String) } +def resetSmartSchedule() { + log.debug "Executing 'resetSmartSchedule'" + parent.resetSmartScheduleForDevice(device.deviceNetworkId) + runIn(2, refresh) +} + def poll() { log.debug "Executing 'poll'" def resp = nucleoPOST("/messages", '{"reqId":"1", "cmd":"getRobotState"}') @@ -196,7 +216,7 @@ def poll() { case "1": sendEvent(name: "status", value: "ready") sendEvent(name: "switch", value: "off") - statusMsg += 'READY TO CLEAN' + statusMsg += "READY TO CLEAN" break; case "2": sendEvent(name: "status", value: "cleaning") @@ -207,6 +227,8 @@ def poll() { sendEvent(name: "status", value: "paused") sendEvent(name: "switch", value: "off") statusMsg += 'PAUSED' + def t = parent.autoDockDelayValue() + if (t != -1) { statusMsg += " - Auto dock set to $t seconds." } break; case "4": sendEvent(name: "status", value: "error") @@ -318,15 +340,48 @@ def poll() { sendEvent(name: 'dockStatus', value: "dockable") } } - if (binFullFlag) { sendEvent(name: 'bin', value: "full" as String) } else { sendEvent(name: 'bin', value: "empty" as String) } + def smartScheduleStatus = "" + def t = parent.timeToSmartScheduleClean(device.deviceNetworkId) + if (t != -1) { + if (t >= 86400000) { + smartScheduleStatus += "SmartSchedule activating in ${Math.round(new BigDecimal(t/86400000)).toString()} days." + } else if ((t >= 0) && (t <= 86400000)) { + smartScheduleStatus += "SmartSchedule activating in ${Math.round(new BigDecimal(t/3600000)).toString()} hours." + } else { + smartScheduleStatus += "SmartSchedule waiting for configured trigger." + } + } else { + smartScheduleStatus += "SmartSchedule is disabled. Configure in Neato (Connect) smart app." + } + def forceCleanStatus = "" + t = parent.timeToForceClean(device.deviceNetworkId) + if (t != -1) { + if (t >= 86400000) { + forceCleanStatus += "Force clean due in ${Math.round(new BigDecimal(t/86400000)).toString()} days." + } else if ((t >= 0) && (t <= 86400000)) { + forceCleanStatus += "Force clean due in ${Math.round(new BigDecimal(t/3600000)).toString()} hours." + } else { + forceCleanStatus += "Force clean imminent." + } + } else { + forceCleanStatus += "Force clean is disabled. Configure in Neato (Connect) smart app." + } + sendEvent(name: 'smartScheduleStatusMessage', value: smartScheduleStatus, displayed: false) + sendEvent(name: 'forceCleanStatusMessage', value: forceCleanStatus, displayed: false) } sendEvent(name: 'statusMsg', value: statusMsg, displayed: false) + //If smart schedule is enabled, disable Neato schedule to avoid conflict + if (parent.isSmartScheduleEnabled && result.details.isScheduleEnabled) { + log.debug "Disable Neato scheduling system as SmartSchedule is enabled" + nucleoPOST("/messages", '{"reqId":"1", "cmd":"disableSchedule"}') + sendEvent(name: 'scheduled', value: "false") + } } def refresh() { From b304253f3109d98fb0daf18dce563a4e493b7fc3 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 20 Oct 2016 22:57:42 +0100 Subject: [PATCH 240/685] v1.1.3 - Allow device handler to display smart scheduling information. --- .../neato-connect.src/neato-connect.groovy | 143 ++++++++++++------ 1 file changed, 94 insertions(+), 49 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 7db40fabdb3..a30e40cf293 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 20-10-2016: 1.1.3 - Allow device handler to display smart scheduling information. * * 20-10-2016: 1.1.2b - Bug fix. SmartSchedule does not operate if force clean option is disabled. * 19-10-2016: 1.1.2 - Option to specify "no trigger" in SmartSchedule. Notification when Force clean is due in 24 hours. @@ -461,35 +462,42 @@ def initialize() { } if (selectedBotvacs) addBotvacs() - getChildDevices().each { childDevice -> - if (state.botvacOnTimeMarker[childDevice.deviceNetworkId] == null) state.botvacOnTimeMarker[childDevice.deviceNetworkId] = now() - } + //subscribe to events for smartSchedule + if (settings.smartScheduleEnabled) { + //store last mode selected + if (!state.lastTriggerMode) state.lastTriggerMode = "" - unschedule() - runEvery5Minutes('pollOn') // Asynchronously refresh devices so we don't block - - //subscribe to events for notifications if activated - if (settings.smartScheduleEnabled || preferencesSelected() == "complete" || notificationsSelected() == "complete") { - getChildDevices().each { childDevice -> - subscribe(childDevice, "status.cleaning", eventHandler, [filterEvents: false]) + if (settings.ssScheduleTrigger == "mode") { subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) } + else if (settings.ssScheduleTrigger == "switch") { subscribe(settings.ssSwitchTrigger, "switch.on", smartScheduleHandler, [filterEvents: false]) } + else if (settings.ssScheduleTrigger == "presence") { subscribe(settings.ssPeopleAway, "presence", smartScheduleHandler, [filterEvents: false]) } + + if (settings.starting) { + schedule(settings.starting, smartScheduleHandler) } + else { + schedule("29 0 0 1/1 * ? *", smartScheduleHandler) + } + subscribe(settings.ssOverrideSwitch, "switch.on", smartScheduleHandler, [filterEvents: false]) } - if (preferencesSelected() == "complete" || notificationsSelected() == "complete") { - getChildDevices().each { childDevice -> - subscribe(childDevice, "status.ready", eventHandler, [filterEvents: false]) + getChildDevices().each { childDevice -> + if (state.botvacOnTimeMarker[childDevice.deviceNetworkId] == null) state.botvacOnTimeMarker[childDevice.deviceNetworkId] = now() + //subscribe to events for notifications if activated + if (settings.smartScheduleEnabled || preferencesSelected() == "complete" || notificationsSelected() == "complete") { + subscribe(childDevice, "status.cleaning", eventHandler, [filterEvents: false]) + } + if (preferencesSelected() == "complete" || notificationsSelected() == "complete") { + subscribe(childDevice, "status.ready", eventHandler, [filterEvents: false]) subscribe(childDevice, "status.error", eventHandler, [filterEvents: false]) subscribe(childDevice, "status.paused", eventHandler, [filterEvents: false]) subscribe(childDevice, "bin.full", eventHandler, [filterEvents: false]) - if (state.forceCleanNotificationSent[childDevice.deviceNetworkId] == null) state.forceCleanNotificationSent[childDevice.deviceNetworkId] = false - } - } - //subscribe to events for smartSchedule - if (settings.smartScheduleEnabled) { - //store last mode selected - if (!state.lastTriggerMode) state.lastTriggerMode = "" - - getChildDevices().each { childDevice -> + } + //initialise force clean flags + if (settings.forceClean) { + if (state.forceCleanNotificationSent[childDevice.deviceNetworkId] == null) state.forceCleanNotificationSent[childDevice.deviceNetworkId] = false + } + //subscribe to events for smartSchedule + if (settings.smartScheduleEnabled) { //Initialize flags for Smart Schedule if (state.smartSchedule[childDevice.deviceNetworkId] == null) state.smartSchedule[childDevice.deviceNetworkId] = false if (state.lastClean[childDevice.deviceNetworkId] == null) state.lastClean[childDevice.deviceNetworkId] = now() @@ -500,19 +508,9 @@ def initialize() { state.lastTriggerMode = settings.ssScheduleTrigger } } - - if (settings.ssScheduleTrigger == "mode") { subscribe(location, "mode", smartScheduleHandler, [filterEvents: false]) } - else if (settings.ssScheduleTrigger == "switch") { subscribe(settings.ssSwitchTrigger, "switch.on", smartScheduleHandler, [filterEvents: false]) } - else if (settings.ssScheduleTrigger == "presence") { subscribe(settings.ssPeopleAway, "presence", smartScheduleHandler, [filterEvents: false]) } - - if (settings.starting) { - schedule(settings.starting, smartScheduleHandler) - } - else { - schedule("29 0 0 1/1 * ? *", smartScheduleHandler) - } - subscribe(settings.ssOverrideSwitch, "switch.on", smartScheduleHandler, [filterEvents: false]) } + unschedule() + runEvery5Minutes('pollOn') // Asynchronously refresh devices so we don't block } def uninstalled() { @@ -521,7 +519,6 @@ def uninstalled() { removeChildDevices(getChildDevices()) } - def updateDevices() { log.debug "Executing 'updateDevices'" if (!state.devices) { @@ -777,7 +774,7 @@ def eventHandler(evt) { log.debug "$evt.device.deviceNetworkId has started cleaning" state.lastClean[evt.device.deviceNetworkId] = now() state.botvacOnTimeMarker[evt.device.deviceNetworkId] = now() - state.forceCleanNotificationSent[evt.device.deviceNetworkId] = false + if (settings.forceClean) { state.forceCleanNotificationSent[evt.device.deviceNetworkId] = false } //Remove SmartSchedule flag state.smartSchedule[evt.device.deviceNetworkId] = false sendEvent(linkText:app.label, name:"${evt.displayName}", value:"on",descriptionText:"${evt.displayName} is on", eventType:"SOLUTION_EVENT", displayed: true) @@ -820,6 +817,7 @@ def eventHandler(evt) { } def smartScheduleHandler(evt) { + log.debug "Executing 'smartScheduleHandler' for ${evt.displayName}" if (evt != null) { log.debug "Executing 'smartScheduleHandler' for ${evt.displayName}" } else { @@ -842,16 +840,7 @@ def smartScheduleHandler(evt) { if (executeOverride) { getChildDevices().each { childDevice -> //Reset last clean date to current time - state.lastClean[childDevice.deviceNetworkId] = now() - state.forceCleanNotificationSent[evt.device.deviceNetworkId] = false - //Remove existing SmartSchedule flag - state.smartSchedule[childDevice.deviceNetworkId] = false - - //DEBUG PURPOSES ONLY. FAKE TIME ON OVERRIDE SWITCH AND INCREASE POLL - //state.lastClean[childDevice.deviceNetworkId] = Date.parseToStringDate("Thu Oct 13 01:23:45 UTC 2016").getTime() - //unschedule(pollOn) - //schedule("0 0/1 * * * ?", pollOn) - //log.debug "Fake data loaded.... " + (now() - state.lastClean[childDevice.deviceNetworkId])/86400000 + resetSmartScheduleForDevice(childDevice.deviceNetworkId) } if (settings.ssNotification) { messageHandler("Neato SmartSchedule has reset all Botvac schedules as override switch ${evt.displayName} is on.", false) @@ -868,6 +857,7 @@ def smartScheduleHandler(evt) { } def scheduleAutoDock() { + log.debug "Executing 'scheduleAutoDock'" getChildDevices().each { childDevice -> if (childDevice.latestState('status').stringValue == 'paused') { childDevice.dock() @@ -966,8 +956,63 @@ def pollOn() { } } +//Access methods for device type +def isSmartScheduleEnabled() { + return settings.smartScheduleEnabled +} + +def timeToSmartScheduleClean(deviceNetworkId) { + log.debug "Executing 'timeToSmartScheduleClean' with device $deviceNetworkId" + def result = -1 + if (settings.smartScheduleEnabled && state.lastClean != null && state.lastClean[deviceNetworkId] != null) { + result = (state.lastClean[deviceNetworkId] + (settings.ssCleaningInterval * 86400000)) - now() + } + log.debug "Time to smart schedule clean: $result milliseconds" + result +} + +def timeToForceClean(deviceNetworkId) { + log.debug "Executing 'timeToForceClean' with device $deviceNetworkId" + def result = -1 + if (settings.forceClean && state.botvacOnTimeMarker != null && state.botvacOnTimeMarker[deviceNetworkId] != null) { + result = (state.botvacOnTimeMarker[deviceNetworkId] + (settings.forceCleanDelay * 86400000)) - now() + } + log.debug "Time to force clean: $result milliseconds" + result +} + +def autoDockDelayValue() { + log.debug "Executing 'autoDockDelayValue'" + def result = -1 + if (settings.autoDock) { + result = settings.autoDockDelay + } + log.debug "Auto dock delay: $result seconds" + result +} + +def resetSmartScheduleForDevice(deviceNetworkId) { + log.debug "Executing 'resetSmartScheduleForDevice' with device $deviceNetworkId" + if (settings.smartScheduleEnabled && state.lastClean != null && state.smartSchedule != null) { + //Reset last clean date to current time + state.lastClean[deviceNetworkId] = now() + //Remove existing SmartSchedule flag + state.smartSchedule[deviceNetworkId] = false + } + /* + //DEBUG PURPOSES ONLY. FAKE TIME ON OVERRIDE SWITCH AND INCREASE POLL + //state.lastClean[deviceNetworkId] = Date.parseToStringDate("Thu Oct 13 01:23:45 UTC 2016").getTime() + state.lastClean[deviceNetworkId] = 1476868627993 + state.botvacOnTimeMarker[deviceNetworkId] = 1476889942741 + //unschedule(pollOn) + //schedule("0 0/1 * * * ?", pollOn) + log.debug "Fake data loaded.... " + (now() - state.lastClean[deviceNetworkId])/86400000 + */ +} + //Helper methods def startConditionalClean() { + log.debug "Executing 'startConditionalClean'" if (allOk) { //For each vacuum getChildDevices().each { childDevice -> @@ -983,6 +1028,7 @@ def startConditionalClean() { } def messageHandler(msg, forceFlag) { + log.debug "Executing 'messageHandler'" if (settings.sendSMS != null && !forceFlag) { sendSms(settings.sendSMS, msg) } @@ -993,7 +1039,6 @@ def messageHandler(msg, forceFlag) { } } - private getAllOk() { triggerConditionsOk && daysOk && timeOk } @@ -1102,7 +1147,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.2b\nDate: 20102016(1320)" + def text = "Neato (Connect)\nVersion: 1.1.3\nDate: 20102016(2210)" } private def textCopyright() { @@ -1123,4 +1168,4 @@ def clientSecret() { } else { return appSettings.clientSecret } -} +} \ No newline at end of file From 784bc15cb7c70b7e76d04376c8b91164b536d448 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Fri, 21 Oct 2016 18:12:31 +0100 Subject: [PATCH 241/685] 1.1b - Minor display tweak for offline condition --- .../neato-botvac-connected-series.groovy | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy index a16e1b44b2c..420c8fa3194 100644 --- a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy +++ b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy @@ -13,8 +13,9 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 20-10-2016: 1.1b - Minor display tweak for offline condition. * 20-10-2016: 1.1 - Added smart schedule and force clean status messages. Added smart schedule reset button. - * Disable Neato Robot Schedule if SmartSchedule is enabled + * Disable Neato Robot Schedule if SmartSchedule is enabled. * * 14-10-2016: 1.0 - Initial Version * @@ -50,7 +51,8 @@ metadata { tileAttribute("device.switch", key:"PRIMARY_CONTROL", canChangeBackground: true){ attributeState("off", label: 'STOPPED', action: "switch.on", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png", backgroundColor: "#ffffff", nextState:"on") attributeState("on", label: 'CLEANING', action: "switch.off", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/best-pet-hair-cleaning.png", backgroundColor: "#79b821", nextState:"off") - } + attributeState("offline", label:'${name}', icon:"https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png", backgroundColor:"#bc2323") + } tileAttribute ("statusMsg", key: "SECONDARY_CONTROL") { attributeState "statusMsg", label:'${currentValue}' } @@ -114,7 +116,13 @@ metadata { state("default", label:'reset schedule', action:"resetSmartSchedule", icon:"st.Office.office6") } - main("clean") + standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { + state("off", label: 'STOPPED', action: "switch.on", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png", backgroundColor: "#ffffff", nextState:"on") + state("on", label: 'CLEANING', action: "switch.off", icon: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/best-pet-hair-cleaning.png", backgroundColor: "#79b821", nextState:"off") + state("offline", label:'${name}', icon:"https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/laser-guided-navigation.png", backgroundColor:"#bc2323") + } + + main("switch") details(["clean","smartScheduleStatusMessage", "forceCleanStatusMessage", "status","battery","bin","network", "dockStatus", "charging", "dockHasBeenSeen", "scheduled", "resetSmartSchedule", "refresh"]) } } @@ -177,6 +185,7 @@ def disableSchedule() { def setOffline() { sendEvent(name: 'network', value: "Not Connected" as String) + sendEvent(name: "switch", value: "offline") } def resetSmartSchedule() { @@ -437,4 +446,4 @@ Map nucleoRequestHeaders(date, HMACsignature) { ] } -def nucleoURL(path = '/') { return "https://nucleo.neatocloud.com:4443/vendors/neato/robots/${device.deviceNetworkId.tokenize("|")[0]}${path}" } \ No newline at end of file +def nucleoURL(path = '/') { return "https://nucleo.neatocloud.com:4443/vendors/neato/robots/${device.deviceNetworkId.tokenize("|")[0]}${path}" } From f4d5afce769247cc21e9053eac0c03ba035a4e89 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Fri, 21 Oct 2016 18:14:14 +0100 Subject: [PATCH 242/685] v1.1.3b - Force poll on settings update. --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index a30e40cf293..6efd1bdd7ea 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 21-10-2016: 1.1.3b - Force poll on settings update. * 20-10-2016: 1.1.3 - Allow device handler to display smart scheduling information. * * 20-10-2016: 1.1.2b - Bug fix. SmartSchedule does not operate if force clean option is disabled. @@ -508,6 +509,7 @@ def initialize() { state.lastTriggerMode = settings.ssScheduleTrigger } } + childDevice.poll() } unschedule() runEvery5Minutes('pollOn') // Asynchronously refresh devices so we don't block @@ -1147,7 +1149,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.3\nDate: 20102016(2210)" + def text = "Neato (Connect)\nVersion: 1.1.3b\nDate: 21102016(1720)" } private def textCopyright() { @@ -1168,4 +1170,4 @@ def clientSecret() { } else { return appSettings.clientSecret } -} \ No newline at end of file +} From aaaae004fbe6f52315c1a4874a4424150a3b3e30 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 23 Oct 2016 18:20:56 +0100 Subject: [PATCH 243/685] v1.1.4 - Improve error notification from device status. --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 6efd1bdd7ea..1df2641a1da 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,8 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 23-10-2016: 1.1.4 - Improve error notification from device status. + * * 21-10-2016: 1.1.3b - Force poll on settings update. * 20-10-2016: 1.1.3 - Allow device handler to display smart scheduling information. * @@ -762,7 +764,7 @@ def eventHandler(evt) { runEvery5Minutes('pollOn') sendEvent(linkText:app.label, name:"${evt.displayName}", value:"error",descriptionText:"${evt.displayName} has an error", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} has an error" - msg = "${evt.displayName} has an error" + msg = "${evt.displayName} has an error" + evt.device.latestState('statusMsg').stringValue.minus('HAS A PROBLEM') if (settings.sendBotvacError) { messageHandler(msg, false) } @@ -877,7 +879,6 @@ def pollOn() { getChildDevices().each { childDevice -> state.pollState = now() childDevice.poll() - if (childDevice.currentSwitch == "off") { //Update smart schedule state. Create notification when clean is due. if (settings.smartScheduleEnabled && state.lastClean != null && state.lastClean[childDevice.deviceNetworkId] != null) { @@ -1149,7 +1150,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.3b\nDate: 21102016(1720)" + def text = "Neato (Connect)\nVersion: 1.1.4\nDate: 23102016(1800)" } private def textCopyright() { @@ -1170,4 +1171,4 @@ def clientSecret() { } else { return appSettings.clientSecret } -} +} \ No newline at end of file From a3397d4a149750c8668f03f6e35fbcab89c176dd Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 24 Oct 2016 13:43:01 +0100 Subject: [PATCH 244/685] v1.2 - Add option to select Turbo or Eco clean modes --- .../neato-botvac-connected-series.groovy | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy index 420c8fa3194..91a22e66e6b 100644 --- a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy +++ b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy @@ -13,6 +13,8 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 23-10-2016: 1.2 - Add option to select Turbo or Eco clean modes + * * 20-10-2016: 1.1b - Minor display tweak for offline condition. * 20-10-2016: 1.1 - Added smart schedule and force clean status messages. Added smart schedule reset button. * Disable Neato Robot Schedule if SmartSchedule is enabled. @@ -24,6 +26,10 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidKeyException; +preferences { + input ("startCleaningMode", "enum", title:"Botvac Cleaning Mode", multiple: false, required: true, options: ["turbo": "Turbo", "eco": "Eco"], defaultValue: "turbo") +} + metadata { definition (name: "Neato Botvac Connected Series", namespace: "alyc100", author: "Alex Lee Yuk Cheung") { capability "Battery" @@ -145,11 +151,14 @@ def initialize() { def on() { log.debug "Executing 'on'" - if (device.latestState('status').stringValue == 'paused') { + def currentState = device.latestState('status').stringValue + if (currentState == 'paused') { nucleoPOST("/messages", '{"reqId":"1", "cmd":"resumeCleaning"}') } - else { - nucleoPOST("/messages", '{"reqId":"1", "cmd":"startCleaning", "params":{"category": 2, "mode": 2, "modifier": 2}}') + else if (currentState != 'error') { + def modeParam = 1 + if (isTurboCleanMode()) modeParam = 2 + nucleoPOST("/messages", '{"reqId":"1", "cmd":"startCleaning", "params":{"category": 2, "mode": ' + modeParam + ', "modifier": 2}}') } runIn(2, refresh) } @@ -398,6 +407,15 @@ def refresh() { poll() } +def isTurboCleanMode() { + def result + if (settings.startCleaningMode == "eco") { + result = false + } + log.debug "$device.displayName cleaning mode: $settings.startCleaningMode" + result +} + def nucleoPOST(path, body) { try { log.debug("Beginning API POST: ${nucleoURL(path)}, ${body}") @@ -446,4 +464,4 @@ Map nucleoRequestHeaders(date, HMACsignature) { ] } -def nucleoURL(path = '/') { return "https://nucleo.neatocloud.com:4443/vendors/neato/robots/${device.deviceNetworkId.tokenize("|")[0]}${path}" } +def nucleoURL(path = '/') { return "https://nucleo.neatocloud.com:4443/vendors/neato/robots/${device.deviceNetworkId.tokenize("|")[0]}${path}" } \ No newline at end of file From ef6d2caaa3c2d0a9a5fd02bf73d757835f680267 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 24 Oct 2016 13:47:14 +0100 Subject: [PATCH 245/685] v1.1.4b - Bug fix. Override switch handler fix to prevent false negatives. --- .../neato-connect.src/neato-connect.groovy | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 1df2641a1da..810b3497ae1 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 24-10-2016: 1.1.4b - Bug fix. Override switch handler fix to prevent false negatives. * 23-10-2016: 1.1.4 - Improve error notification from device status. * * 21-10-2016: 1.1.3b - Force poll on settings update. @@ -821,33 +822,44 @@ def eventHandler(evt) { } def smartScheduleHandler(evt) { - log.debug "Executing 'smartScheduleHandler' for ${evt.displayName}" if (evt != null) { log.debug "Executing 'smartScheduleHandler' for ${evt.displayName}" } else { log.debug "Executing 'smartScheduleHandler' for scheduled event" } //If switch on for override event - if (evt != null && evt.name == "switch" && evt.device in settings.ssOverrideSwitch) { - def executeOverride = true - //If override switch condition is ALL... - if (ssOverrideSwitchCondition == "all") { - //Check all switches in override switch settings are on - for (switchVal in settings.ssOverrideSwitch.currentSwitch) { - if (switchVal == "off") { - executeOverride = false - break - } - } + if (evt != null && evt.name == "switch") { + def switchInList = false + for (switchName in settings.ssOverrideSwitch.name) { + if (switchName == evt.device.name) { + switchInList = true + break + } } - //For each vacuum - if (executeOverride) { - getChildDevices().each { childDevice -> - //Reset last clean date to current time - resetSmartScheduleForDevice(childDevice.deviceNetworkId) - } - if (settings.ssNotification) { - messageHandler("Neato SmartSchedule has reset all Botvac schedules as override switch ${evt.displayName} is on.", false) + log.debug "Swtich found in override switch list: $switchInList" + if (switchInList) { + def executeOverride = true + //If override switch condition is ALL... + if (ssOverrideSwitchCondition == "all") { + //Check all switches in override switch settings are on + for (switchVal in settings.ssOverrideSwitch.currentSwitch) { + if (switchVal == "off") { + executeOverride = false + break + } + } + } + + //For each vacuum + if (executeOverride) { + getChildDevices().each { childDevice -> + //Reset last clean date to current time + resetSmartScheduleForDevice(childDevice.deviceNetworkId) + childDevice.poll() + } + if (settings.ssNotification) { + messageHandler("Neato SmartSchedule has reset all Botvac schedules as override switch ${evt.displayName} is on.", false) + } } } } @@ -1150,7 +1162,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.4\nDate: 23102016(1800)" + def text = "Neato (Connect)\nVersion: 1.1.4b\nDate: 24102016(1345)" } private def textCopyright() { From 67acafdd4191448cbeb6e8808f51c7cb86b9285a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 24 Oct 2016 21:56:09 +0100 Subject: [PATCH 246/685] Minor error notification message improvement --- smartapps/alyc100/neato-connect.src/neato-connect.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/neato-connect.src/neato-connect.groovy b/smartapps/alyc100/neato-connect.src/neato-connect.groovy index 810b3497ae1..a673efd006c 100644 --- a/smartapps/alyc100/neato-connect.src/neato-connect.groovy +++ b/smartapps/alyc100/neato-connect.src/neato-connect.groovy @@ -765,7 +765,7 @@ def eventHandler(evt) { runEvery5Minutes('pollOn') sendEvent(linkText:app.label, name:"${evt.displayName}", value:"error",descriptionText:"${evt.displayName} has an error", eventType:"SOLUTION_EVENT", displayed: true) log.trace "${evt.displayName} has an error" - msg = "${evt.displayName} has an error" + evt.device.latestState('statusMsg').stringValue.minus('HAS A PROBLEM') + msg = "${evt.displayName} has an error: " + evt.device.latestState('statusMsg').stringValue.minus('HAS A PROBLEM - ') if (settings.sendBotvacError) { messageHandler(msg, false) } @@ -1162,7 +1162,7 @@ def getApiEndpoint() { return "https://apps.neatorobotics.com" } def getSmartThingsClientId() { return appSettings.clientId } def beehiveURL(path = '/') { return "https://beehive.neatocloud.com${path}" } private def textVersion() { - def text = "Neato (Connect)\nVersion: 1.1.4b\nDate: 24102016(1345)" + def text = "Neato (Connect)\nVersion: 1.1.4b\nDate: 24102016(2150)" } private def textCopyright() { From b3bc966921079c8873333c3eb94ef16f269cca5b Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 25 Oct 2016 19:05:53 +0100 Subject: [PATCH 247/685] v1.2b - Very silly bug fix. Stop mode always reporting as Eco. Also display mode in Device Handler. --- .../neato-botvac-connected-series.groovy | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy index 91a22e66e6b..5ca0537eb0c 100644 --- a/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy +++ b/devicetypes/alyc100/neato-botvac-connected-series.src/neato-botvac-connected-series.groovy @@ -13,6 +13,7 @@ * for the specific language governing permissions and limitations under the License. * * VERSION HISTORY + * 25-10-2016: 1.2b - Very silly bug fix. Stop mode always reporting as Eco. Also display mode in Device Handler. * 23-10-2016: 1.2 - Add option to select Turbo or Eco clean modes * * 20-10-2016: 1.1b - Minor display tweak for offline condition. @@ -234,7 +235,7 @@ def poll() { case "1": sendEvent(name: "status", value: "ready") sendEvent(name: "switch", value: "off") - statusMsg += "READY TO CLEAN" + statusMsg += "READY TO CLEAN - ${settings.startCleaningMode.toUpperCase()} MODE" break; case "2": sendEvent(name: "status", value: "cleaning") @@ -408,7 +409,7 @@ def refresh() { } def isTurboCleanMode() { - def result + def result = true if (settings.startCleaningMode == "eco") { result = false } From aa595821635dc77b0ae17d381aaae49e3b8b68d7 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Tue, 25 Oct 2016 22:36:23 +0100 Subject: [PATCH 248/685] Neato icons --- devicetypes/alyc100/neato_eco_icon.png | Bin 0 -> 1885 bytes devicetypes/alyc100/neato_no_schedule_icon.png | Bin 0 -> 979 bytes devicetypes/alyc100/neato_schedule_icon.png | Bin 0 -> 1210 bytes devicetypes/alyc100/neato_turbo_icon.png | Bin 0 -> 2130 bytes devicetypes/alyc100/reset_schedule_icon.png | Bin 0 -> 3408 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 devicetypes/alyc100/neato_eco_icon.png create mode 100644 devicetypes/alyc100/neato_no_schedule_icon.png create mode 100644 devicetypes/alyc100/neato_schedule_icon.png create mode 100644 devicetypes/alyc100/neato_turbo_icon.png create mode 100644 devicetypes/alyc100/reset_schedule_icon.png diff --git a/devicetypes/alyc100/neato_eco_icon.png b/devicetypes/alyc100/neato_eco_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0018407de63b12050990e42cd95f8f347bc3ef19 GIT binary patch literal 1885 zcmbVNYfuwc6izT8UU2;XBZ|Cy0a|Gli`ZIhRm2XhB5LhM!S;vKAKjVVd(XY+JLfyE z*$wfrv;2I=`f@lNKb;m&VAqN4JHgwF{hGIaeUx278FdOXk1{dY1Wj_HjZ``b=q$t% zGJzzFIm=tfC=O@DGIL@IlM*urHBc5I;nop4EH)O+;Y7`J*a*Wik^$1mC1$G<>^pfH z1k6Sym@JEdVr&?hVb2RP}YEjWC*}v$+X&0hZ1~c7iIVE zV-X0vQel=U!PlfxV&VaeqDep|j1(B4NI4)^2*onF9F|W9B#>Apf}|oTB#=l^7(!tQ zF!X?|H`-`I6L8ItFIG~584P1XMWXENY+<%kNYP6~VueB>f+Qk|M8IkY>^WA3a0smS z@L>x$X*bYj8)K%dfZHOGPGvDlkY)Nh1dA;u=8a*ieJD|E%0v#rCK3xFk;USU>y@^h zNg)5B@m6hnVvdazC6IP1i#D+JFoh3;+1&lNBex>U292Z5Y*C0zoHArtNGqeml^}Z~ zG@6Ylq()?N2!Syv94Qv7r3i#!8a1Xs6fnf*6C1YiHY_5SV2DPH%M=Qj#mW#34kH?c z9KzHvE>SCnu{x`rA*=>+*sq!OJA}pl6N_RrNiYxO<90f2}aFtAwU{s5-8m$6Ua=FgaDR^(*=Y~0Sh20jK~SZ zB$uYk!6Cfy|0GVt5)!#PeqDkydtWXm&pO9j!Onu`AZ$(TR>5 z=eDJ4lje?I-+HM;wO1#fedS(-YHsv{dqTIT!@K7gRXUiK zKRP6?D1tYxE}+e4Y2E7hkz)(jJ`39u;E}%v^eCBK{N&N4hSqRrXrk;i^@7~c=IP{z zd0hu){dw)kn38>k2~jTIs5%|L<)>f9a>>1%&w2~q_gwts6JFS0oo0QuV9W$=Q-gES zOjGn`Mt9V=mvTo{?x_;RJ$BO+@kH! zwVM}uyQhA+UU1b_z`5Hrug=#QZ1<1oH??=vhD3Ay3-Y-8x*uuR?!SfeJhw`d4|;rk zscfO@Wpc8DTcm?(3V)ahT-F@PoK>-tGcbEy%ZPV7CUkz#6Kai~F~IXeikmA7PE~ko z(^{HuIr#$&Ij*`t<#ufS?U3g`7Tld2CF;Nr&b%mq7X9{@&kXwxymD=Rbz%}5$VFP>X_wGb-HJLZA7 z?!_;Sb#4kPfupCWT*VrsZ~ow*soSq}=w>M_2120{*G(nTgiR=6$9#?W) z->oTGVb21CqHC&J^GaGO@yq_xH)n)@JngOwIB~2oC@HzKy=tSrDy{mnF~OnF1j({1 zjkQ2){Ruz1O@{1@Lj!ldb1I_pizoFSZD$XpPCKM4m@h;PzuiT63u*f&_i=63s0F+` z^+XlM*NSUZfpl4C&*Q^i1-G9ab-=rs4yvp8X8Z#auO@xz%v??Ox^nH@(<$7^_AbIK(Q~&t# zhBpur000A;NklxV0-v6BW-!td#09930 z`WXiPZ324uPs%JTk($0>Dbp7$wX}g9pMO76e+q;3I^Q?9@aXAtL#MNsDlKoM=d&Si z70F47n*IUNdFt!80BbgR2r7!@jjh5t-ocHFKJ9=Sh5Cj@H9cdAT3K~lc2-rXD#%}B zW3-z-o;@@4+p+(Unx3&l*UeB<6Ve>Ak(UQ+*UENzxw%jrJQ!(DohsKK@6d4&Ffa6d z`=ssjK~q!g`PJ1>S0`+z6ZY;ky_H5&v2sfJhqugX{R}|+kVLX(&DWhbqN|fzFCUZ8 z)d|3L=M+jtj@F&q@Z^D}PZO#0CyyGEIyrV+uRZLiLP2{#cb=#9OD~~MDMJkj8Nx{D z^$iAM4i~-4rK-VTQJ1ixME%ChFG>tXat02YHA|Ki;AS}^+t%>EUc4COoUUX zWc$DY=*ekKP2v!MAiiab<>l>>0?5vmDZ=lU(F#u$hYyPfumRoAOf>XPH#6{+_DNNtI+uAowjJ${vh8^P7#^`61qBj2>~2Wn{0Pkz+|KhpRTP9 z&Ycslum=||P`Y_@T%_VFU4{>btgOhs)YQ0^gg7(>&`^3*p0;j;maky-oP`7iX_q#m zCx|mrpE4FnJ7p*!^yO*&qC4MgS!`cZ)zy$+D{QZqv6-1LZ5jYME$_&A@kG2{8?YBC zMG;A}2X42>+#ZkY3(xF-gjD#6^Lk}O3Jc{NpAViJm(;OfS?KdZ3Z<`}0uUNBm;|>w zV!X&Pk(`!y0JLW=#yw+hgy?@u@+|n++-u(*H%X%#->6hV-6V$nRM_}kzr2YFOQlO~ ztt?5LPHov@G%pbNO{I&XKuL*g$NJ!AXM@WnY`-7M$}9!SJgJ%*S#!HwqOOXC6DOoy zT-|B_|~TP#! literal 0 HcmV?d00001 diff --git a/devicetypes/alyc100/neato_schedule_icon.png b/devicetypes/alyc100/neato_schedule_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..842b93fff0450c5329434d09b2e933b7df7040ed GIT binary patch literal 1210 zcmeAS@N?(olHy`uVBq!ia0vp^sz9v4!2~3~B`xU!Qj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07?`y(Ln2Bde0{8v^KKTB%1XJkii(hGOE?jkSNl+@ny;uz{4yi0i z)elN7&Mz%W21Z<(GRUd|E9aur#FG4?ko^1{SSSW$ucqiS6q^qmz?V9Vygr+NiQ=q#mdRV!qnWz%+18e)x^-y)y&e&+0fO^ z%-q7+)WpEmzyzk(B|o_|H#M&WrZ)wy*W3W7UQm+AEdbi=l3J8mmYU*Ll%J~r_PSLj zZnwDLG!Lpb1*cogTyg5v2RcR{6v0Rl4if^VA`lave1RNz21v~Vrurga@{aXZP-I|W zRP%Ik45_%4^ymM7duCb2$17?}ww=FlPuOFg{f@a>r)EgJ;C_)S&n@-;+1%>i{PKrG z%#;5KCG|K5i^)ozIDNR+_}{#}6_WF&{C)a5JnN^MuWRPhJ1aGu{!cvc^0PSa8=Gls zqWX0>L>L$SZ(&%rscDs)PjAE{|Jcw@t$D%Wfl4yr6ujQ@OWzyE#7&;A)q{QGA;l&fF-b8g)~XbP0l+XkKOUtNH literal 0 HcmV?d00001 diff --git a/devicetypes/alyc100/neato_turbo_icon.png b/devicetypes/alyc100/neato_turbo_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..21b9de8fa9567ba319d1c5a5fb031c6b8f1badcc GIT binary patch literal 2130 zcmbVNc~nzp7S9|3m8C32L

Dl*YMtZ()wl&0Q-?mpeT= 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 From aa7bd14fe674bcab3ea3540ef2a52d8a9c4e788f Mon Sep 17 00:00:00 2001 From: alyc100 Date: Wed, 16 Mar 2016 15:40:47 +0000 Subject: [PATCH 181/685] Delete 17-battery-50-plugged-512x512.png --- .../alyc100/17-battery-50-plugged-512x512.png | Bin 6308 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 devicetypes/alyc100/17-battery-50-plugged-512x512.png diff --git a/devicetypes/alyc100/17-battery-50-plugged-512x512.png b/devicetypes/alyc100/17-battery-50-plugged-512x512.png deleted file mode 100644 index 6469f23935cc349e05d4582b57954bc03a746c1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6308 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 Date: Wed, 16 Mar 2016 15:43:37 +0000 Subject: [PATCH 182/685] Icon updates --- .../thinking-cleanerer-neato-botvac-edition.groovy | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy b/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy index 79394af8449..55d2bb23902 100644 --- a/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy +++ b/smartapps/sidjohn1/thinking-cleanerer-neato-botvac-edition.src/thinking-cleanerer-neato-botvac-edition.groovy @@ -23,7 +23,7 @@ * * Modified by Alex Lee Yuk Cheung for Neato Botvac Devices * Version: 1.0 - Initial Version - Added Auto Dock On Pause, Force Clean Option, Changes to Polling behaviour - * Version: 1.1 - Fix to setting SHM state when error has occured whilst cleaning + * Version: 1.1 - Fix to setting SHM state when error has occured whilst cleaning */ definition( @@ -32,9 +32,9 @@ definition( author: "Sidney Johnson", description: "Handles polling and job notification for Neato Botvac", category: "Convenience", - iconUrl: "http://cdn.device-icons.smartthings.com/Appliances/appliances13-icn.png", - iconX2Url: "http://cdn.device-icons.smartthings.com/Appliances/appliances13-icn@2x.png", - iconX3Url: "http://cdn.device-icons.smartthings.com/Appliances/appliances13-icn@3x.png", + iconUrl: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato-icons_1x.png", + iconX2Url: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato-icons_1x.png", + iconX3Url: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/devicetypes/alyc100/neato-icons_1x.png", singleInstance: true) preferences { @@ -106,7 +106,7 @@ def updated() { } def initialize() { - log.info "Thinking Cleanerer Neaeto Botvac Edition ${textVersion()} ${textCopyright()}" + log.info "Thinking Cleanerer Neato Botvac Edition ${textVersion()} ${textCopyright()}" subscribe(switch1, "status.cleaning", eventHandler) subscribe(switch1, "status.ready", eventHandler) subscribe(switch1, "status.error", eventHandler) From 80a8f87c60eab9b256e59d640bda82560e26d8f7 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Tue, 22 Mar 2016 00:32:50 +0000 Subject: [PATCH 183/685] Neato charging icons --- devicetypes/alyc100/neato_charging.png | Bin 0 -> 3647 bytes devicetypes/alyc100/neato_notcharging.png | Bin 0 -> 17434 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 devicetypes/alyc100/neato_charging.png create mode 100644 devicetypes/alyc100/neato_notcharging.png diff --git a/devicetypes/alyc100/neato_charging.png b/devicetypes/alyc100/neato_charging.png new file mode 100644 index 0000000000000000000000000000000000000000..0bfb8ddecbc8c3403d7594dfb9a6b59e4da4c057 GIT binary patch literal 3647 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 literal 0 HcmV?d00001 diff --git a/devicetypes/alyc100/neato_notcharging.png b/devicetypes/alyc100/neato_notcharging.png new file mode 100644 index 0000000000000000000000000000000000000000..13c959ccd14eb045a62d3b4b60ae173720a28b06 GIT binary patch literal 17434 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 WVd@|}u|xXbJG`jAloGc!8UF)Qv&WAB literal 0 HcmV?d00001 From 4da9d841c099f15623f6a8725e10b337ec23f254 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 22 Mar 2016 13:33:51 +0000 Subject: [PATCH 184/685] v1.4 - Option added to restrict Auto Mode rules to specific days and time of day. --- .../thermostat-mode-automation.groovy | 115 ++++++++++++++++-- 1 file changed, 104 insertions(+), 11 deletions(-) diff --git a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy index 140122a940d..2e45b843c21 100644 --- a/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy +++ b/smartapps/alyc100/thermostatmodeautomation/thermostat-mode-automation.src/thermostat-mode-automation.groovy @@ -40,6 +40,7 @@ * v1.3.1 - Bug fixes. * v1.3.2 - Stop possible infinite loop when handlers create events themselves. * v1.3.3 - Label change for Boost + * v1.4 - Option added to restrict Auto Mode rules to specific days and time of day. */ definition( @@ -112,8 +113,11 @@ def configurePage() { } section( "Additional configuration" ) { - input ("temp", "number", title: "If setting to Manual, set the temperature to this", required: false, defaultValue: 21) - } + 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?", @@ -127,6 +131,13 @@ def configurePage() { } } +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 + } +} + def installed() { log.debug "Installed with settings: ${settings}" //set up initial thermostat state and force thermostat into correct mode @@ -183,12 +194,14 @@ def updated() { //Handler and action for switch detection def switchHandler(evt) { - log.debug "evt.value: $evt.value" - log.debug "state.internalSwitchEvent: $state.internalSwitchEvent" - if (state.internalSwitchEvent == false) { - takeActionForSwitch(evt.value) + if(allOk) { + log.debug "evt.value: $evt.value" + log.debug "state.internalSwitchEvent: $state.internalSwitchEvent" + if (state.internalSwitchEvent == false) { + takeActionForSwitch(evt.value) + } + state.internalSwitchEvent = false } - state.internalSwitchEvent = false } def takeActionForSwitch(switchState) { @@ -242,8 +255,10 @@ def takeActionForSwitch(switchState) { //Handler and action for mode detection def modeeventHandler(evt) { - log.debug "evt.value: $evt.value" - takeActionForMode(evt.value) + if(allOk) { + log.debug "evt.value: $evt.value" + takeActionForMode(evt.value) + } } def takeActionForMode(mode) { @@ -318,7 +333,7 @@ def thermostateventHandler(evt) { theSwitch.on() } } - } + } //If boost mode is selected as resumed state, need to set thermostat mode as per preference if (state.boostingReset) { @@ -374,4 +389,82 @@ private send(msg) { } log.debug msg -} \ No newline at end of file +} + +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 +} From d3f777237430770453869b4b10eaaf60cdaec8da Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Mon, 28 Mar 2016 11:44:27 +0100 Subject: [PATCH 185/685] v2.2.2 - Change current hour logic to accommodate GMT/BST. --- .../ovo-energy-meter-v2-0.groovy | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index 663a116e57c..a4b4b11db7f 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -20,6 +20,7 @@ * v2.2 - Percentage comparison from previous cost values added into display * v2.2.1 - Add current consumption price based on unit price from OVO account API not OVO live API * v2.2.1b - Remove double negative on percentage values. + * v2.2.2 - Change current hour logic to accommodate GMT/BST. */ preferences { @@ -138,7 +139,14 @@ def refreshLiveData() { } //Get current hour //data.hour = null - def currentHour = new Date().getAt(Calendar.HOUR_OF_DAY) + def df = new java.text.SimpleDateFormat("kk") + if (location.timeZone) { + df.setTimeZone(location.timeZone) + } + else { + df.setTimeZone(TimeZone.getTimeZone("Europe/London")) + } + def currentHour = df.format(new Date()) if ((data.hour == null) || (data.hour != currentHour)) { //Reset at midnight or initial call if ((data.hour == null) || (currentHour == 0)) { From d5230a10d6387ca5b1fda879c97e8863b8d073f4 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 29 Mar 2016 00:05:00 +0100 Subject: [PATCH 186/685] v2.2.2b - Alter Simple Date Format hour string --- .../ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index a4b4b11db7f..f71f09ef162 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -21,6 +21,7 @@ * v2.2.1 - Add current consumption price based on unit price from OVO account API not OVO live API * v2.2.1b - Remove double negative on percentage values. * v2.2.2 - Change current hour logic to accommodate GMT/BST. + * v2.2.2b - Alter Simple Date Format hour string */ preferences { @@ -139,7 +140,7 @@ def refreshLiveData() { } //Get current hour //data.hour = null - def df = new java.text.SimpleDateFormat("kk") + def df = new java.text.SimpleDateFormat("HH") if (location.timeZone) { df.setTimeZone(location.timeZone) } From e6d91ee7ac58c0a0d2538e3c3ae7ff5006f8ee30 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 29 Mar 2016 00:11:35 +0100 Subject: [PATCH 187/685] Bug fixes --- .../ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy index f71f09ef162..60c25136c1d 100644 --- a/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy +++ b/devicetypes/alyc100/ovo-energy-meter-v2-0.src/ovo-energy-meter-v2-0.groovy @@ -147,7 +147,7 @@ def refreshLiveData() { else { df.setTimeZone(TimeZone.getTimeZone("Europe/London")) } - def currentHour = df.format(new Date()) + def currentHour = df.format(new Date()).toInteger() if ((data.hour == null) || (data.hour != currentHour)) { //Reset at midnight or initial call if ((data.hour == null) || (currentHour == 0)) { From 8c80ada1809ba7e8b9c03cfde6a516c7a7dfddc2 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 30 Mar 2016 13:25:02 +0100 Subject: [PATCH 188/685] Neato Version: 1.0.4b - Display error message when serial/secret on Pi is incorrect. --- .../neato-botvac-connected.groovy | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy index c3d766c8fde..d6256f1fd1b 100644 --- a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy +++ b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy @@ -29,6 +29,7 @@ * Neato Version: 1.0.2 - Added Please Clear My Path Error message * Neato Version: 1.0.3 - Added Navigation No Progress Error message * Neato Version: 1.0.4 - Added Neato icons + * Neato Version: 1.0.4b - Display error message when serial/secret on Pi is incorrect. */ import groovy.json.JsonSlurper @@ -135,12 +136,22 @@ def parse(String description) { slurper = new JsonSlurper() result = slurper.parseText(bodyString) log.debug result - + //Show error if the Pi app cannot find robot with provided serial/secret credentials + if (result.containsKey("message")) { + switch (result.message) { + case "Could not find robot_serial for specified vendor_name": + setOffline() + sendEvent(name: 'status', value: "error" as String) + statusMsg += 'Robot serial and/or secret is not correct' + log.debug headerString + break; + } + } if (result.containsKey("state")) { - //state 1 - Ready to clean - //state 2 - Cleaning - //state 3 - Paused - //state 4 - Error + //state 1 - Ready to clean + //state 2 - Cleaning + //state 3 - Paused + //state 4 - Error switch (result.state) { case "1": sendEvent(name: 'status', value: "ready" as String) From 5736557373d33e985a48eb77ce2cd59721e470fa Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 13 Apr 2016 17:00:35 +0100 Subject: [PATCH 189/685] 1.0.4c - Stop continuous error message on bad connection. --- .../neato-botvac-connected.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy index d6256f1fd1b..c793e42fe84 100644 --- a/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy +++ b/devicetypes/alyc100/neato-botvac-connected.src/neato-botvac-connected.groovy @@ -30,6 +30,7 @@ * Neato Version: 1.0.3 - Added Navigation No Progress Error message * Neato Version: 1.0.4 - Added Neato icons * Neato Version: 1.0.4b - Display error message when serial/secret on Pi is incorrect. + * Neato Version: 1.0.4c - Stop continuous error message on bad link. */ import groovy.json.JsonSlurper @@ -129,9 +130,7 @@ def parse(String description) { unschedule('setOffline') map = stringToMap(description) headerString = new String(map.headers.decodeBase64()) - if (headerString.contains("200 OK")) { - - sendEvent(name: 'network', value: "Connected" as String) + if (headerString.contains("200 OK")) { bodyString = new String(map.body.decodeBase64()) slurper = new JsonSlurper() result = slurper.parseText(bodyString) @@ -148,6 +147,7 @@ def parse(String description) { } } if (result.containsKey("state")) { + sendEvent(name: 'network', value: "Connected" as String) //state 1 - Ready to clean //state 2 - Cleaning //state 3 - Paused From 7a2735424f5655e8f1ddd282879d75b0fdd5e6db Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 3 May 2016 01:17:37 +0100 Subject: [PATCH 190/685] v2.1.5g - Changes to tile display for iOS app v2.1.2 --- .../hive-heating-v2-0.src/hive-heating-v2-0.groovy | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 4976ffe1e14..3248189d4e0 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -25,9 +25,10 @@ * v2.1.5 - Option to disable Hive Heating Device for summer. Disable mode stops any automation commands from other smart apps reactivating Hive Heating. * v2.1.5b - Bug fix when desired heat set point is null, control stops working. * v2.1.5c - Fix multitile button behaviour that has changed since ST app 2.1.0. Add colour code to temperature reporting in activity feed. - * v2.1.6d - Fix blank temperature readings on Android ST app - * v2.1.6e - Another attempt to fix blank temperature reading on Android. - * v2.1.6f - Allow decimal value for boost temperature. Changes to VALUE_CONTROL method to match latest ST docs. + * v2.1.5d - Fix blank temperature readings on Android ST app + * v2.1.5e - Another attempt to fix blank temperature reading on Android. + * v2.1.5f - Allow decimal value for boost temperature. Changes to VALUE_CONTROL method to match latest ST docs. + * v2.1.5g - Changes to tile display for iOS app v2.1.2 */ preferences { @@ -401,7 +402,7 @@ def poll() { data.nodes = resp.data.nodes //Construct status message - def statusMsg = "Mode is" + def statusMsg = "" //Boost button label if (state.boostLength == null || state.boostLength == '') @@ -415,7 +416,7 @@ def poll() { def temperature = data.nodes.attributes.temperature.reportedValue[0] def heatingSetpoint = data.nodes.attributes.targetHeatTemperature.reportedValue[0] temperature = String.format("%2.1f",temperature) - heatingSetpoint = convertTemperatureIfNeeded(heatingSetpoint, "C", 1) + heatingSetpoint = String.format("%2.1f",heatingSetpoint) // convert temperature reading of 1 degree to 7 as Hive app does if (heatingSetpoint == "1.0") { @@ -454,7 +455,7 @@ def poll() { mode = 'emergency heat' def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] boostLabel = "Restart\n$state.boostLength Min Boost" - statusMsg = "BOOSTING - " + boostTime + " mins remaining" + statusMsg = boostTime + " MINS" sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") } else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { From a909ddc60566867c76b9595bf84ee6ad9dd577d9 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 3 May 2016 10:35:02 +0100 Subject: [PATCH 191/685] Minor display updates --- .../hive-heating-v2-0.src/hive-heating-v2-0.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index 3248189d4e0..d58ed1ba77a 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -449,21 +449,21 @@ def poll() { } else if (activeHeatCoolMode == "OFF") { mode = 'off' - statusMsg = statusMsg + " OFF" + //statusMsg = statusMsg + " OFF" } else if (activeHeatCoolMode == "BOOST") { mode = 'emergency heat' def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] boostLabel = "Restart\n$state.boostLength Min Boost" - statusMsg = boostTime + " MINS" + statusMsg = "Boost " + boostTime + "min" sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") } else if (activeHeatCoolMode == "HEAT" && activeScheduleLock) { mode = 'heat' - statusMsg = statusMsg + " MANUAL" + statusMsg = statusMsg + " Manual" } else { - statusMsg = statusMsg + " SCHEDULE" + statusMsg = statusMsg + " Schedule" } if (settings.disableDevice != null && settings.disableDevice == true) { From a88e2d9880c65c57a0ed148bd79c63e101fb2aab Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 17 Aug 2016 10:03:03 +0100 Subject: [PATCH 192/685] v2.1.3 - Fix null pointer on state variable corruption --- .../hive-connect.src/hive-connect.groovy | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index b33e58b2fcc..85cbc708f4c 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -18,7 +18,10 @@ * v2.0.1 BETA - Fix bug for accounts that do not have capabilities attribute against thermostat nodes. * 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 + * v2.1.2 - Move external icon references into Github\ + * + * 17.08.2016 + * v2.1.3 - Fix null pointer on state variable corruption * */ definition( @@ -76,7 +79,7 @@ def firstPage() { def headerSECTION() { return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png", - "Hive (Connect)\nVersion: 2.1.2\nDate: 16032016(1130)") + "Hive (Connect)\nVersion: 2.1.3\nDate: 17082016(1000)") } def stateTokenPresent() { @@ -96,21 +99,32 @@ def preferencesSelected() { } def getDevicesSelectedString() { + if (state.hiveHeatingDevices == null || state.hiveHotWaterDevices == null) { + updateDevices() + } def listString = "" selectedHeating.each { childDevice -> if (listString == "") { - listString += state.hiveHeatingDevices[childDevice] + if (null != state.hiveHeatingDevices) { + listString += state.hiveHeatingDevices[childDevice] + } } else { - listString += "\n" + state.hiveHeatingDevices[childDevice] + if (null != state.hiveHeatingDevices) { + listString += "\n" + state.hiveHeatingDevices[childDevice] + } } } selectedHotWater.each { childDevice -> if (listString == "") { - listString += state.hiveHotWaterDevices[childDevice] + if (null != state.hiveHotWaterDevices) { + listString += state.hiveHotWaterDevices[childDevice] + } } else { - listString += "\n" + state.hiveHotWaterDevices[childDevice] + if (null != state.hiveHotWaterDevices) { + listString += "\n" + state.hiveHotWaterDevices[childDevice] + } } } return listString From 680f0152153ca5fd5ed6275cb3c22db1dcbbec2a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 17 Aug 2016 11:16:22 +0100 Subject: [PATCH 193/685] v2.2.2 - Fix device failure on API timeout --- .../ovo-energy-connect.groovy | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy index 9fb58514ba3..1be4d766505 100644 --- a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy +++ b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy @@ -20,6 +20,9 @@ * Send notification for specified daily cost level breach. * v2.2.1 - Move external icon references to Github * + * 17.08.2016 + * v2.2.2 - Fix device failure on API timeout + * */ definition( name: "OVO Energy (Connect)", @@ -94,13 +97,20 @@ def devicesSelected() { } def getDevicesSelectedString() { + if (state.smartMeterDevices == null) { + updateDevices() + } def listString = "" selectedMeters.each { childDevice -> if (listString == "") { - listString += state.smartMeterDevices[childDevice] + if (null != state.smartMeterDevices) { + listString += state.smartMeterDevices[childDevice] + } } else { - listString += "\n" + state.smartMeterDevices[childDevice] + if (null != state.smartMeterDevices) { + listString += "\n" + state.smartMeterDevices[childDevice] + } } } return listString @@ -489,14 +499,14 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { try { return c() } catch (groovyx.net.http.HttpResponseException e) { - options.logObject.error("got error: ${e}, body: ${e.getResponse().getData()}") + log.error("got error: ${e}, body: ${e.getResponse().getData()}") if (e.statusCode == 401) { // token is expired state.remove("ovoAccessToken") - options.logObject.warn "Access token is not valid" + log.warn "Access token is not valid" } return options.errorReturn } catch (java.net.SocketTimeoutException e) { - options.logObject.warn "Connection timed out, not much we can do here" + log.warn "Connection timed out, not much we can do here" return options.errorReturn } } \ No newline at end of file From bcd6130a166534e90e5b552f6483b4e4d21eada8 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 17 Aug 2016 11:18:08 +0100 Subject: [PATCH 194/685] v2.1.3b - Fix device failure on API timeout --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 85cbc708f4c..7aebb2d9371 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -22,6 +22,7 @@ * * 17.08.2016 * v2.1.3 - Fix null pointer on state variable corruption + * v2.1.3b - Fix device failure on API timeout * */ definition( @@ -601,14 +602,14 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { try { return c() } catch (groovyx.net.http.HttpResponseException e) { - options.logObject.error("got error: ${e}, body: ${e.getResponse().getData()}") + log.error("got error: ${e}, body: ${e.getResponse().getData()}") if (e.statusCode == 401) { // token is expired state.remove("hiveAccessToken") - options.logObject.warn "Access token is not valid" + log.warn "Access token is not valid" } return options.errorReturn } catch (java.net.SocketTimeoutException e) { - options.logObject.warn "Connection timed out, not much we can do here" + log.warn "Connection timed out, not much we can do here" return options.errorReturn } } \ No newline at end of file From f023d8639f6c0282a116a15086e7cc70b316f3c2 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Wed, 17 Aug 2016 21:26:44 +0100 Subject: [PATCH 195/685] Minor header fixes --- .../alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy index 1be4d766505..60b60e58346 100644 --- a/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy +++ b/smartapps/alyc100/ovo-energy-connect.src/ovo-energy-connect.groovy @@ -81,7 +81,7 @@ def firstPage() { def headerSECTION() { return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/icon175x175.jpeg", - "OVO Energy (Connect)\nVersion: 2.2.1\nDate: 16032016(1130)") + "OVO Energy (Connect)\nVersion: 2.2.2\nDate: 16082016(1100)") } def stateTokenPresent() { From 507e94c6cac58363974e0589c86514480436998a Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Thu, 1 Sep 2016 14:55:01 +0100 Subject: [PATCH 196/685] v2.2 - Integrate auto mode functionality from Auto Mode for Thermostat smart app --- .../hive-connect.src/hive-connect.groovy | 591 ++++++++++++++++-- 1 file changed, 548 insertions(+), 43 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 7aebb2d9371..cc1baf46040 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -1,7 +1,7 @@ /** * Hive (Connect) * - * Copyright 2015 Alex Lee Yuk Cheung + * Copyright 2015,2016 Alex Lee Yuk Cheung * * 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: @@ -13,6 +13,7 @@ * 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 nodes. @@ -24,6 +25,8 @@ * 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 */ definition( name: "Hive (Connect)", @@ -36,18 +39,38 @@ definition( ) preferences { - page(name:"firstPage", title:"Hive Device Setup", content:"firstPage", install: true) + //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 apiURL(path = '/') { return "https://api.prod.bgchprod.info:443/omnia${path}" } -def firstPage() { - log.debug "firstPage" +def startPage() { + if (parent) { + atomicState?.isParent = false + tmaConfigurePAGE() + } else { + atomicState?.isParent = true + mainPage() + } +} + +//Hive Connect App Pages + +def mainPage() { + log.debug "mainPage" if (username == null || username == '' || password == null || password == '') { - return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) { section { headerSECTION() href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter Hive crednentials", state: authenticated()) @@ -57,18 +80,22 @@ def firstPage() { else { log.debug "next phase" - return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { + return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) { section { headerSECTION() - href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter Hive credentials", state: authenticated()) + href("loginPAGE", title: "Authenticated as", description: authenticated() ? username : "Tap to enter Hive credentials", state: authenticated()) } if (stateTokenPresent()) { section ("Choose your devices:") { - href("selectDevicePAGE", title: null, description: devicesSelected() ? "Devices: " + getDevicesSelectedString() : "Tap to select devices", state: devicesSelected()) + 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 "There was a problem connecting to Hive. Check your user credentials and error logs in SmartThings web console.\n\n${state.loginerrors}" @@ -80,7 +107,7 @@ def firstPage() { def headerSECTION() { return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png", - "Hive (Connect)\nVersion: 2.1.3\nDate: 17082016(1000)") + "Hive (Connect)\nVersion: 2.2\nDate: 01092016(1200)") } def stateTokenPresent() { @@ -99,6 +126,22 @@ def preferencesSelected() { return (sendPush || sendSMS != null) && (maxtemp != null || mintemp != null || sendBoost || sendOff || sendManual || sendSchedule) ? "complete" : null } +def tmaDescription() { + def tmaApp = findChildAppByName( appName() ) + if(tmaApp) { + 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) { updateDevices() @@ -217,11 +260,124 @@ def preferencesPAGE() { } } +def tmaPAGE() { + dynamicPage(name: "tmaPAGE", title: "", nextPage: !parent ? "startPage" : "tmaPAGE", install: false) { + def tmaApp = findChildAppByName( appName() ) + if(tmaApp) { + 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 @@ -231,7 +387,7 @@ def installed() { } // called after settings are changed -def updated() { +def updatedParent() { log.debug "updated" unsubscribe() initialize() @@ -239,7 +395,7 @@ def updated() { runEvery10Minutes('refreshDevices') } -def uninstalled() { +def uninstalledParent() { log.info("Uninstalling, removing child devices...") unschedule() removeChildDevices(getChildDevices()) @@ -251,33 +407,91 @@ private removeChildDevices(devices) { } } +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() { - log.debug "initialize" - if (selectedHeating) - addHeating() + if (parent) { } + else { + log.debug "initialize" + if (selectedHeating) + addHeating() - if (selectedHotWater) - addHotWater() + if (selectedHotWater) + addHotWater() - runIn(10, 'refreshDevices') // Asynchronously refresh devices so we don't block + 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) - } - if (childDevice.typeName == "Hive Heating V2.0") { - subscribe(childDevice, "temperature", tempHandler) - } + //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]) + } + } } - } - state.maxNotificationSent = false - state.minNotificationSent = false + state.maxNotificationSent = false + state.minNotificationSent = false + } } +//Event Handler for Connect App def tempHandler(evt) { def msg log.trace "temperature: $evt.value, $evt" @@ -316,22 +530,301 @@ def tempHandler(evt) { def modeHandler(evt) { def msg - if (evt.value == "heat") { - msg = "${evt.displayName} is set to Manual" - if (settings.sendSchedule) generateNotification(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) } - else if (evt.value == "off") { - msg = "${evt.displayName} is turned Off" - if (settings.sendOff) generateNotification(msg) +} + +//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 } - else if (evt.value == "auto") { - msg = "${evt.displayName} is set to Schedule" - if (settings.sendManual) generateNotification(msg) +} + +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." + } } - else if (evt.value == "emergency heat") { - msg = "${evt.displayName} is in Boost mode" - if (settings.sendBoost) generateNotification(msg) - } +} + +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) { @@ -593,6 +1086,16 @@ def isLoggedIn() { return state.hiveAccessToken_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}") @@ -612,4 +1115,6 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { log.warn "Connection timed out, not much we can do here" return options.errorReturn } -} \ No newline at end of file +} + +def appName() { return "${parent ? "Hive Mode Automation" : "Hive (Connect)"}" } \ No newline at end of file From c1b68dd5a19cc0656b12c92af9264665be3a3a89 Mon Sep 17 00:00:00 2001 From: Simon Green Date: Sun, 4 Sep 2016 18:02:58 +0100 Subject: [PATCH 197/685] WIP adding contact sensors --- .../hive-contact-sensor.groovy | 89 +++ .../hive-connect.src/hive-connect.groovy | 596 +++++++++--------- 2 files changed, 389 insertions(+), 296 deletions(-) create mode 100644 devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy diff --git a/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy b/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy new file mode 100644 index 00000000000..9e24d8507bb --- /dev/null +++ b/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy @@ -0,0 +1,89 @@ +/** + * Hive Window or Door Sensor V1.0 + * + * Copyright 2016 Simon Green + * + * 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 + * 4th Sept. '16 + * v1.0 Initial Release + */ + +metadata { + definition (name: "Hive Window or Door Sensor V1.0", namespace: "simonjgreen", author: "Simon Green") { + capability "Actuator" + capability "Polling" + capability "Refresh" +// capability "Temperature Measurement" + capability "Contact Sensor" + } + + simulator { + status "open": "open/closed: open" + status "closed": "open/closed: closed" + } + + tiles(scale: 2){ + standardTile("state", "device.state", width: 2, height: 2) { + state "open", label: "open", icon: "st.contact.contact.open" + state "closed", label: "closed", icon: "st.contact.contact.closed" + } + } +} + +// parse events into attributes +def parse(String description) { + log.debug "Parsing '${description}'" + // TODO: handle 'switch' attribute + // TODO: handle 'thermostatMode' attribute +} + +def installed() { + log.debug "Executing 'installed'" + state.boostLength = 60 +} + +def poll() { + log.debug "Executing 'poll'" + def resp = parent.apiGET("/nodes/${device.deviceNetworkId}") + if (resp.status != 200) { + log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") + return [] + } + data.nodes = resp.data.nodes + + //Construct status message + def statusMsg = "Currently" + + // determine contact sensor state + def state = data.nodes.attributes.state.reportedValue[0] + + log.debug "state: $state" + + if (state == "OPEN") { + mode = 'off' + statusMsg = statusMsg + " set to OFF" + } + else if (activeHeatCoolMode == "BOOST") { + mode = 'emergency heat' + statusMsg = statusMsg + " set to BOOST" + def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] + boostLabel = "Boosting for \n" + boostTime + " mins" + sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") + } + + sendEvent(name: 'state', value: state) +} + +def refresh() { + log.debug "Executing 'refresh'" + poll() +} diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index cc1baf46040..f9a3fc8b189 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -35,22 +35,22 @@ definition( 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 -) + singleInstance: true +) preferences { //startPage page(name: "startPage") - - //Connect Pages + + //Connect Pages page(name:"mainPage", title:"Hive Device Setup", content:"mainPage", install: true) - page(name: "loginPAGE") - page(name: "selectDevicePAGE") + page(name: "loginPAGE") + page(name: "selectDevicePAGE") page(name: "preferencesPAGE") - page(name: "tmaPAGE") - - //Thermostat Mode Automation Pages - page(name: "tmaConfigurePAGE") + page(name: "tmaPAGE") + + //Thermostat Mode Automation Pages + page(name: "tmaConfigurePAGE") } def apiURL(path = '/') { return "https://api.prod.bgchprod.info:443/omnia${path}" } @@ -72,37 +72,34 @@ def mainPage() { if (username == null || username == '' || password == null || password == '') { return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) { section { - headerSECTION() - href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter Hive crednentials", state: authenticated()) - } - } - } - else - { - log.debug "next phase" - return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) { + headerSECTION() + href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter Hive crednentials", state: authenticated()) + } + } + } else { + log.debug "next phase" + return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) { section { - headerSECTION() - href("loginPAGE", title: "Authenticated as", description: authenticated() ? username : "Tap to enter Hive credentials", state: authenticated()) - } - if (stateTokenPresent()) { - section ("Choose your devices:") { + headerSECTION() + href("loginPAGE", title: "Authenticated as", description: authenticated() ? username : "Tap to enter Hive credentials", 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:") { + } + section("Hive Mode Automations:") { href "tmaPAGE", title: "Hive Mode Automations...", description: (tmaDescription() ? tmaDescription() : "Tap to Configure..."), state: (tmaDescription() ? "complete" : null) } - section ("Notifications:") { + section ("Notifications:") { href("preferencesPAGE", title: null, description: preferencesSelected() ? getPreferencesString() : "Tap to configure notifications", state: preferencesSelected()) - } - - } else { - section { - paragraph "There was a problem connecting to Hive. Check your user credentials and error logs in SmartThings web console.\n\n${state.loginerrors}" - } - } - } - } + } + } else { + section { + paragraph "There was a problem connecting to Hive. Check your user credentials and error logs in SmartThings web console.\n\n${state.loginerrors}" + } + } + } + } } def headerSECTION() { @@ -131,12 +128,12 @@ def tmaDescription() { if(tmaApp) { def str = "" str += "Thermostat Automations:" - + childApps?.each { a -> def name = a?.getLabel() str += "\n• $name" } - + return str } return null @@ -147,7 +144,7 @@ def getDevicesSelectedString() { updateDevices() } def listString = "" - selectedHeating.each { childDevice -> + selectedHeating.each { childDevice -> if (listString == "") { if (null != state.hiveHeatingDevices) { listString += state.hiveHeatingDevices[childDevice] @@ -159,7 +156,7 @@ def getDevicesSelectedString() { } } } - selectedHotWater.each { childDevice -> + selectedHotWater.each { childDevice -> if (listString == "") { if (null != state.hiveHotWaterDevices) { listString += state.hiveHotWaterDevices[childDevice] @@ -170,6 +167,16 @@ def getDevicesSelectedString() { listString += "\n" + state.hiveHotWaterDevices[childDevice] } } + selectedContactSensor.each { childDevice -> + if (listString == "") { + if (null != state.hiveContactSensorDevices) { + listString += state.hiveContactSensorDevices[childDevice] + } + } else { + if (null != state.hiveContactSensorDevices) { + listString += "\n" + state.hiveContactSensorDevices[childDevice] + } + } } return listString } @@ -186,78 +193,73 @@ def getPreferencesString() { if (sendSchedule) listString += "Schedule, " if (listString != "") listString = listString.substring(0, listString.length() - 2) return listString - - + + } def loginPAGE() { if (username == null || username == '' || password == null || password == '') { return dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) { - section { headerSECTION() } - section { paragraph "Enter your Hive credentials below to enable SmartThings and Hive integration." } - section("Hive Credentials:") { + section { headerSECTION() } + section { paragraph "Enter your Hive credentials below to enable SmartThings and Hive integration." } + section("Hive Credentials:") { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)", required: true) - input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) - } - } - } - else { - getHiveAccessToken() - dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) { - section { headerSECTION() } - section { paragraph "Enter your Hive credentials below to enable SmartThings and Hive integration." } - section("Hive Credentials:") { + input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) + } + } + } else { + getHiveAccessToken() + dynamicPage(name: "loginPAGE", title: "Login", uninstall: false, install: false) { + section { headerSECTION() } + section { paragraph "Enter your Hive credentials below to enable SmartThings and Hive integration." } + section("Hive Credentials:") { input("username", "text", title: "Username", description: "Your Hive username (usually an email address)", required: true) - input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) - } - - if (stateTokenPresent()) { - section { - paragraph "You have successfully connected to Hive. Click 'Done' to select your Hive devices." - } - } - else { - section { - paragraph "There was a problem connecting to Hive. Check your user credentials and error logs in SmartThings web console.\n\n${state.loginerrors}" - } - } - } - } + input("password", "password", title: "Password", description: "Your Hive password", required: true, submitOnChange: true) + } + if (stateTokenPresent()) { + section { + paragraph "You have successfully connected to Hive. Click 'Done' to select your Hive devices." + } + } else { + section { + paragraph "There was a problem connecting to Hive. Check your user credentials 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:") { + 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 "selectedContactSensor", "enum", required:false, title:"Select Hive Contact Sensors \n(${state.hiveContactSensorDevices.size() ?: 0} found)", multiple:true, options:state.hiveContactSensorDevices } - } + } } 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:") { - + 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 + 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 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) } + } } def tmaPAGE() { @@ -283,79 +285,67 @@ 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, + } + 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 - } + } + 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 + input "starting", "time", title: "Starting", required: false + input "ending", "time", title: "Ending", required: false } } @@ -363,18 +353,18 @@ page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfte // 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 + 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 + 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 + 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() { @@ -382,17 +372,17 @@ def installedParent() { initialize() // Check for new devices every 3 hours runEvery3Hours('updateDevices') - // execute handlerMethod every 10 minutes. - runEvery10Minutes('refreshDevices') + // execute handlerMethod every 10 minutes. + runEvery10Minutes('refreshDevices') } // called after settings are changed def updatedParent() { log.debug "updated" - unsubscribe() + unsubscribe() initialize() - unschedule('refreshDevices') - runEvery10Minutes('refreshDevices') + unschedule('refreshDevices') + runEvery10Minutes('refreshDevices') } def uninstalledParent() { @@ -408,57 +398,56 @@ private removeChildDevices(devices) { } 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]) - } + 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]) + 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) } - else { - if (theSwitch.currentSwitch == "on") { - takeActionForSwitch(theSwitch.currentSwitch) - } - subscribe(theSwitch, "switch", switcheventHandlerForTMA, [filterEvents: false]) - } + subscribe(theSwitch, "switch", switcheventHandlerForTMA, [filterEvents: false]) + } } // called after Done is hit after selecting a Location @@ -466,36 +455,36 @@ def initialize() { if (parent) { } else { log.debug "initialize" - if (selectedHeating) - addHeating() + if (selectedHeating) + addHeating() - if (selectedHotWater) + if (selectedHotWater) { addHotWater() + } - 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]) - } + 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]) } - } - state.maxNotificationSent = false - state.minNotificationSent = false - } - + if (childDevice.typeName == "Hive Heating V2.0") { + subscribe(childDevice, "temperature", tempHandler, [filterEvents: false]) + } + } + } + state.maxNotificationSent = false + state.minNotificationSent = false + } } //Event Handler for Connect App def tempHandler(evt) { def msg log.trace "temperature: $evt.value, $evt" - + if (settings.maxtemp != null) { def maxTemp = settings.maxtemp if (evt.doubleValue >= maxTemp) { @@ -530,7 +519,7 @@ def tempHandler(evt) { def modeHandler(evt) { def msg - if (evt.value == "heat") { + if (evt.value == "heat") { msg = "${evt.displayName} is set to Manual" if (settings.sendSchedule) generateNotification(msg) } @@ -543,10 +532,10 @@ def modeHandler(evt) { if (settings.sendManual) generateNotification(msg) } else if (evt.value == "emergency heat") { - msg = "${evt.displayName} is in Boost mode" + msg = "${evt.displayName} is in Boost mode" if (settings.sendBoost) generateNotification(msg) - } - + } + } //Event Handlers for Thermostat Mode Automation @@ -554,7 +543,7 @@ def modeHandler(evt) { def modeeventHandlerForTMA(evt) { if(allOk) { log.debug "evt.value: $evt.value" - takeActionForMode(evt.value) + takeActionForMode(evt.value) } } @@ -564,7 +553,7 @@ def switcheventHandlerForTMA(evt) { log.debug "evt.value: $evt.value" log.debug "state.internalSwitchEvent: $state.internalSwitchEvent" if (state.internalSwitchEvent == false) { - takeActionForSwitch(evt.value) + takeActionForSwitch(evt.value) } state.internalSwitchEvent = false } @@ -577,28 +566,28 @@ def thermostateventHandlerForTMA(evt) { 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 (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.boostingReset = false } } } @@ -614,7 +603,7 @@ def takeActionForSwitch(switchState) { 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 @@ -633,17 +622,17 @@ def takeActionForSwitch(switchState) { { //Check if user wants to reset thermostats if (resetThermostats == "true") - { - log.debug "Thermostats have been altered, turning back to $resumedThermostatMode" + { + 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 @@ -663,12 +652,12 @@ def takeActionForMode(mode) { 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") { @@ -685,16 +674,16 @@ def takeActionForMode(mode) { 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") + 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 @@ -718,7 +707,7 @@ private changeAllThermostatsModes(thermostats, newThermostatMode, reason) { message = "SmartThings has reset $thermostat.label to $thermostatModeDetail because $reason." log.info message send(message) - log.debug "Setting $thermostat.label to $thermostatModeDetail" + log.debug "Setting $thermostat.label to $thermostatModeDetail" if (newThermostatMode == "Set to Manual") { thermostat.heat() thermostat.setHeatingSetpoint(temp) @@ -793,7 +782,7 @@ private hhmm(time, fmt = "h:mm a") def getTimeLabel(starting, ending){ def timeLabel = "Tap to set" - + if(starting && ending){ timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending) } @@ -814,7 +803,7 @@ private hideOptionsSection() { def greyedOutSettings(){ def result = "" if (starting || ending || days || falseAlarmThreshold) { - result = "complete" + result = "complete" } result } @@ -822,15 +811,15 @@ def greyedOutSettings(){ def greyedOutTime(starting, ending){ def result = "" if (starting || ending) { - result = "complete" + result = "complete" } result } def generateNotification(msg) { if (settings.sendSMS != null) { - sendSms(sendSMS, msg) - } + sendSms(sendSMS, msg) + } if (settings.sendPush == true) { sendPush(msg) } @@ -843,8 +832,9 @@ def updateDevices() { def devices = devicesList() state.hiveHeatingDevices = [:] state.hiveHotWaterDevices = [:] + state.hiveContactSensorDevices = [:] def selectors = [] - devices.each { device -> + devices.each { device -> selectors.add("${device.id}") if (device.attributes.activeHeatCoolMode != null) { def parentNode = devices.find { d -> d.id == device.parentNodeId } @@ -852,38 +842,52 @@ def updateDevices() { def value = "${parentNode.name} Hive Heating" def key = device.id state.hiveHeatingDevices["${key}"] = value - + //Update names of devices with Hive def childDevice = getChildDevice("${device.id}") - if (childDevice) { + if (childDevice) { //Update name of device if different. if(childDevice.name != parentNode.name + " Hive Heating") { childDevice.name = parentNode.name + " Hive Heating" log.debug "Device's name has changed." } } - + } else if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == true)) { def value = "${parentNode.name} Hive Hot Water" def key = device.id state.hiveHotWaterDevices["${key}"] = value - + //Update names of devices def childDevice = getChildDevice("${device.id}") - if (childDevice) { + if (childDevice) { //Update name of device if different. if(childDevice.name != parentNode.name + " Hive Hot Water") { childDevice.name = parentNode.name + " Hive Hot Water" log.debug "Device's name has changed." } } - - } - // Support for more Hive Device Types can be added here in the future. - } - } - + + } else if (device.attributes.state != null) { + log.debug "Found: ${parentNode.name} Hive Contact Sensor" + def value = "${parentNode.name} Hive Contact Sensor" + def key = device.id + state.hiveContactSensorDevices["${key}"] = value + //Update names of devices + def childDevice = getChildDevice("${device.id}") + if (childDevice) { + //Update name of device if different. + if(childDevice.name != parentNode.name + " Hive Contact Sensor") { + childDevice.name = parentNode.name + " Hive Contact Sensor" + log.debug "Device's name has changed." + } + } + } + // Support for more Hive Device Types can be added here in the future. + } + } + //Remove devices if does not exist on the Hive platform getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}") @@ -894,31 +898,31 @@ def updateDevices() { } catch (physicalgraph.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) { + + if (!childDevice) { log.info("Adding Hive Heating device ${device}: ${state.hiveHeatingDevices[device]}") - + def data = [ name: state.hiveHeatingDevices[device], label: state.hiveHeatingDevices[device], ] childDevice = addChildDevice(app.namespace, "Hive Heating V2.0", "$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" } - + } } @@ -926,12 +930,12 @@ def addHotWater() { updateDevices() selectedHotWater.each { device -> - + def childDevice = getChildDevice("${device}") - - if (!childDevice) { + + if (!childDevice) { log.info("Adding Hive Hot Water device ${device}: ${state.hiveHotWaterDevices[device]}") - + def data = [ name: state.hiveHotWaterDevices[device], label: state.hiveHotWaterDevices[device], @@ -942,7 +946,7 @@ def addHotWater() { } else { log.debug "found ${state.hiveHotWaterDevices[device]} with id ${device} already exists" } - + } } @@ -966,13 +970,13 @@ def devicesList() { } def apiGET(path, body = [:]) { - try { + try { if(!isLoggedIn()) { log.debug "Need to login" getHiveAccessToken() } log.debug("Beginning API GET: ${apiURL(path)}, ${apiRequestHeaders()}") - + httpGet(uri: apiURL(path), contentType: 'application/json', headers: apiRequestHeaders()) {response -> logResponse(response) return response @@ -990,7 +994,7 @@ def apiPOST(path, body = [:]) { getHiveAccessToken() } log.debug("Beginning API POST: ${path}, ${body}") - + httpPostJson(uri: apiURL(path), body: body, headers: apiRequestHeaders() ) {response -> logResponse(response) return response @@ -1008,7 +1012,7 @@ def apiPUT(path, body = [:]) { getHiveAccessToken() } log.debug("Beginning API POST: ${path}, ${body}") - + httpPutJson(uri: apiURL(path), body: body, headers: apiRequestHeaders() ) {response -> logResponse(response) return response @@ -1019,7 +1023,7 @@ def apiPUT(path, body = [:]) { } } -def getHiveAccessToken() { +def getHiveAccessToken() { try { def params = [ uri: apiURL('/auth/sessions'), @@ -1038,17 +1042,17 @@ def getHiveAccessToken() { ] 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 "sessionid: ${response.data.sessions[0].id}" - + state.hiveAccessToken = response.data.sessions[0].id // set the expiration to 5 minutes state.hiveAccessToken_expires_at = new Date().getTime() + 300000 @@ -1063,7 +1067,7 @@ def getHiveAccessToken() { } } -Map apiRequestHeaders() { +Map apiRequestHeaders() { return [ 'Cookie': state.cookie, 'Content-Type': 'application/vnd.alertme.zoo-6.2+json', @@ -1117,4 +1121,4 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { } } -def appName() { return "${parent ? "Hive Mode Automation" : "Hive (Connect)"}" } \ No newline at end of file +def appName() { return "${parent ? "Hive Mode Automation" : "Hive (Connect)"}" } From f6ad3614d64552be0ffbbfe7f00b769e4cc1fabb Mon Sep 17 00:00:00 2001 From: Simon Green Date: Sun, 4 Sep 2016 22:07:01 +0100 Subject: [PATCH 198/685] Working --- .../hive-contact-sensor.groovy | 40 ++- .../hive-connect.src/hive-connect.groovy | 247 ++++++++++-------- 2 files changed, 156 insertions(+), 131 deletions(-) diff --git a/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy b/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy index 9e24d8507bb..10703fc40d1 100644 --- a/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy +++ b/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy @@ -32,9 +32,12 @@ metadata { } tiles(scale: 2){ - standardTile("state", "device.state", width: 2, height: 2) { - state "open", label: "open", icon: "st.contact.contact.open" - state "closed", label: "closed", icon: "st.contact.contact.closed" + standardTile("state", "device.state", width: 6, height: 4, key:"PRIMARY_CONTROL") { + state "open", label: "open", icon: "st.contact.contact.open", backgroundColor: "#FF0000" + state "closed", label: "closed", icon: "st.contact.contact.closed", backgroundColor: "#00CC00" + } + standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") } } } @@ -58,29 +61,24 @@ def poll() { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") return [] } - data.nodes = resp.data.nodes + data.nodes = resp.data.nodes - //Construct status message - def statusMsg = "Currently" + //Construct status message + def statusMsg = "Currently" - // determine contact sensor state - def state = data.nodes.attributes.state.reportedValue[0] + // determine contact sensor state + def state = data.nodes.attributes.state.reportedValue[0] - log.debug "state: $state" + log.debug "state: $state" - if (state == "OPEN") { - mode = 'off' - statusMsg = statusMsg + " set to OFF" - } - else if (activeHeatCoolMode == "BOOST") { - mode = 'emergency heat' - statusMsg = statusMsg + " set to BOOST" - def boostTime = data.nodes.attributes.scheduleLockDuration.reportedValue[0] - boostLabel = "Boosting for \n" + boostTime + " mins" - sendEvent("name":"boostTimeRemaining", "value": boostTime + " mins") - } + if (state == "OPEN") { + statusMsg = statusMsg + " Open" + } + else if (state == "CLOSED") { + statusMsg = statusMsg + " Closed" + } - sendEvent(name: 'state', value: state) + sendEvent(name: 'state', value: state) } def refresh() { diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index f9a3fc8b189..21d569056df 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -73,7 +73,7 @@ def mainPage() { return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) { section { headerSECTION() - href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter Hive crednentials", state: authenticated()) + href("loginPAGE", title: null, description: authenticated() ? "Authenticated as " +username : "Tap to enter Hive credentials", state: authenticated()) } } } else { @@ -141,60 +141,58 @@ def tmaDescription() { def getDevicesSelectedString() { if (state.hiveHeatingDevices == null || state.hiveHotWaterDevices == null) { - updateDevices() - } + updateDevices() + } def listString = "" selectedHeating.each { childDevice -> - if (listString == "") { - if (null != state.hiveHeatingDevices) { - listString += state.hiveHeatingDevices[childDevice] - } - } - else { - if (null != state.hiveHeatingDevices) { - listString += "\n" + state.hiveHeatingDevices[childDevice] - } - } - } - selectedHotWater.each { childDevice -> - if (listString == "") { - if (null != state.hiveHotWaterDevices) { - listString += state.hiveHotWaterDevices[childDevice] - } - } - else { - if (null != state.hiveHotWaterDevices) { - listString += "\n" + state.hiveHotWaterDevices[childDevice] - } - } - selectedContactSensor.each { childDevice -> if (listString == "") { - if (null != state.hiveContactSensorDevices) { - listString += state.hiveContactSensorDevices[childDevice] + if (null != state.hiveHeatingDevices) { + listString += state.hiveHeatingDevices[childDevice] } } else { - if (null != state.hiveContactSensorDevices) { - listString += "\n" + state.hiveContactSensorDevices[childDevice] + if (null != state.hiveHeatingDevices) { + listString += "\n" + state.hiveHeatingDevices[childDevice] } } - } - return listString + } + selectedHotWater.each { childDevice -> + if (listString == "") { + if (null != state.hiveHotWaterDevices) { + listString += state.hiveHotWaterDevices[childDevice] + } + } else { + if (null != state.hiveHotWaterDevices) { + listString += "\n" + state.hiveHotWaterDevices[childDevice] + } + } + } + selectedContactSensor.each { childDevice -> + if (listString == "") { + if (null != state.hiveContactSensorDevices) { + listString += state.hiveContactSensorDevices[childDevice] + } + } else { + if (null != state.hiveContactSensorDevices) { + listString += "\n" + state.hiveContactSensorDevices[childDevice] + } + } + } + return listString } + 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 (listString != "") listString = listString.substring(0, listString.length() - 2) - return 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 (listString != "") listString = listString.substring(0, listString.length() - 2) + return listString } def loginPAGE() { @@ -236,7 +234,7 @@ def selectDevicePAGE() { 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 "selectedContactSensor", "enum", required:false, title:"Select Hive Contact Sensors \n(${state.hiveContactSensorDevices.size() ?: 0} found)", multiple:true, options:state.hiveContactSensorDevices + input "selectedContactSensor", "enum", image: "https://www.hivehome.com/assets/hive-window-door-sensor-815702baa8f484d342f2ebf3eb38ab971acecba02586d0ec485c588f2646c935.jpg", required:false, title:"Select Hive Contact Sensors \n(${state.hiveContactSensorDevices.size() ?: 0} found)", multiple:true, options:state.hiveContactSensorDevices } } } @@ -455,13 +453,19 @@ def initialize() { if (parent) { } else { log.debug "initialize" - if (selectedHeating) - addHeating() + + if (selectedHeating) { + addHeating() + } if (selectedHotWater) { addHotWater() } + if (selectedContactSensor) { + addContactSensor() + } + runIn(10, 'refreshDevices') // Asynchronously refresh devices so we don't block //subscribe to events for notifications if activated @@ -771,8 +775,7 @@ private getTimeOk() { result } -private hhmm(time, fmt = "h:mm a") -{ +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)) @@ -799,7 +802,6 @@ private hideOptionsSection() { (starting || ending || days || modes) ? false : true } - def greyedOutSettings(){ def result = "" if (starting || ending || days || falseAlarmThreshold) { @@ -830,74 +832,75 @@ def updateDevices() { state.devices = [:] } def devices = devicesList() - state.hiveHeatingDevices = [:] - state.hiveHotWaterDevices = [:] - state.hiveContactSensorDevices = [:] - def selectors = [] + state.hiveHeatingDevices = [:] + state.hiveHotWaterDevices = [:] + state.hiveContactSensorDevices = [:] + def selectors = [] devices.each { device -> - selectors.add("${device.id}") - if (device.attributes.activeHeatCoolMode != null) { - def parentNode = devices.find { d -> d.id == device.parentNodeId } - if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == false) && (device.attributes.temperature != null)) { - def value = "${parentNode.name} Hive Heating" + selectors.add("${device.id}") + if (device.attributes.activeHeatCoolMode != null) { + def parentNode = devices.find { d -> d.id == device.parentNodeId } + log.debug "Found Device: ${parentNode.name}" + // Heating Control + if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == false) && (device.attributes.temperature != null)) { + log.debug "Identified: ${parentNode.name} Hive Heating" + def value = "${parentNode.name} Hive Heating" def key = device.id state.hiveHeatingDevices["${key}"] = value - //Update names of devices with Hive - def childDevice = getChildDevice("${device.id}") - if (childDevice) { - //Update name of device if different. - if(childDevice.name != parentNode.name + " Hive Heating") { - childDevice.name = parentNode.name + " Hive Heating" - log.debug "Device's name has changed." - } - } - - } - else if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == true)) { - def value = "${parentNode.name} Hive Hot Water" + //Update names of devices with Hive + def childDevice = getChildDevice("${device.id}") + if (childDevice) { + //Update name of device if different. + if(childDevice.name != parentNode.name + " Hive Heating") { + childDevice.name = parentNode.name + " Hive Heating" + log.debug "Device's name has changed." + } + } + // Water Control + } else if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == true)) { + log.debug "Identified: ${parentNode.name} Hive Hot Water" + def value = "${parentNode.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 != parentNode.name + " Hive Hot Water") { - childDevice.name = parentNode.name + " Hive Hot Water" - log.debug "Device's name has changed." - } - } - - } else if (device.attributes.state != null) { - log.debug "Found: ${parentNode.name} Hive Contact Sensor" - def value = "${parentNode.name} Hive Contact Sensor" - def key = device.id - state.hiveContactSensorDevices["${key}"] = value - //Update names of devices - def childDevice = getChildDevice("${device.id}") - if (childDevice) { - //Update name of device if different. - if(childDevice.name != parentNode.name + " Hive Contact Sensor") { - childDevice.name = parentNode.name + " Hive Contact Sensor" - log.debug "Device's name has changed." - } - } - } - // Support for more Hive Device Types can be added here in the future. - } - } - - //Remove devices if does not exist on the Hive platform - getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { - log.info("Deleting ${it.deviceNetworkId}") - try { + //Update names of devices + def childDevice = getChildDevice("${device.id}") + if (childDevice) { + //Update name of device if different. + if(childDevice.name != parentNode.name + " Hive Hot Water") { + childDevice.name = parentNode.name + " Hive Hot Water" + log.debug "Device's name has changed." + } + } + } + // Contact Sensor + } else if (device.attributes.state != null) { + log.debug "Identified: ${device.name} Hive Contact Sensor" + def value = "${device.name} Hive Contact Sensor" + def key = device.id + state.hiveContactSensorDevices["${key}"] = value + //Update names of devices + def childDevice = getChildDevice("${device.id}") + if (childDevice) { + //Update name of device if different. + if(childDevice.name != device.name + " Hive Contact Sensor") { + childDevice.name = device.name + " Hive Contact Sensor" + 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 (physicalgraph.exception.NotFoundException e) { - log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") - } catch (physicalgraph.exception.ConflictException ce) { - log.info("Device ${it.deviceNetworkId} in use. Please manually delete.") - } + } catch (physicalgraph.exception.NotFoundException e) { + log.info("Could not find ${it.deviceNetworkId}. Assuming manually deleted.") + } catch (physicalgraph.exception.ConflictException ce) { + log.info("Device ${it.deviceNetworkId} in use. Please manually delete.") + } } } @@ -950,6 +953,30 @@ def addHotWater() { } } +def addContactSensor() { + updateDevices() + + selectedContactSensor.each { device -> + + def childDevice = getChildDevice("${device}") + + if (!childDevice) { + log.info("Adding Hive Contact Sensor device ${device}: ${state.hiveContactSensorDevices[device]}") + + def data = [ + name: state.hiveContactSensorDevices[device], + label: state.hiveContactSensorDevices[device], + ] + childDevice = addChildDevice("simonjgreen", "Hive Window or Door Sensor V1.0", "$device", null, data) + childDevice.refresh() + log.debug "Created ${state.hiveContactSensorDevices[device]} with id: ${device}" + } else { + log.debug "found ${state.hiveContactSensorDevices[device]} with id ${device} already exists" + } + + } +} + def refreshDevices() { log.info("Refreshing all devices...") getChildDevices().each { device -> From ba40cab6c36575e94305c437a68bc1a17bab055a Mon Sep 17 00:00:00 2001 From: alyc100 Date: Sat, 9 Jan 2016 22:01:54 +0000 Subject: [PATCH 199/685] Remove MiHome eTRV Connect app --- .../mihome-etrv-connect.groovy | 214 ------------------ 1 file changed, 214 deletions(-) delete mode 100644 smartapps/alyc100/mihome-etrv-connect.src/mihome-etrv-connect.groovy diff --git a/smartapps/alyc100/mihome-etrv-connect.src/mihome-etrv-connect.groovy b/smartapps/alyc100/mihome-etrv-connect.src/mihome-etrv-connect.groovy deleted file mode 100644 index 481479c9857..00000000000 --- a/smartapps/alyc100/mihome-etrv-connect.src/mihome-etrv-connect.groovy +++ /dev/null @@ -1,214 +0,0 @@ -/** - * MiHome eTRV (Connect) - * - * Copyright 2015 Alex Lee Yuk Cheung - * - * 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 - * 09.01.2016 - * v1.0 - Initial Release - */ -definition( - name: "MiHome eTRV (Connect)", - namespace: "alyc100", - author: "Alex Lee Yuk Cheung", - description: "Connect your MiHome eTRV to SmartThings.", - iconUrl: "https://mihome4u.co.uk/assets/homepage/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", - iconX2Url: "https://mihome4u.co.uk/assets/homepage/mihome-icon-89db7a9bfb5c8b066ffb4e50c8d68235.png", - singleInstance: true -) - -preferences { - page(name:"firstPage", title:"MiHome Device Setup", content:"firstPage", install: true) -} - -def apiURL(path = '/') { return "https://mihome4u.co.uk/api/v1${path}" } - -def firstPage() { - log.debug "firstPage" - if (username == null || username == '' || password == null || password == '') { - return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { - section { - input("username", "text", title: "Username", description: "Your MiHome username (usually an email address)", required: true) - input("password", "password", title: "Password", description: "Your MiHome password", required: true, submitOnChange: true) - } - } - } - else - { - log.debug "next phase" - getMiHomeAccessToken() - updateDevices() - return dynamicPage(name: "firstPage", title: "", install: true, uninstall: true) { - section { - input("username", "text", title: "Username", description: "Your MiHome username (usually an email address)", required: true) - input("password", "password", title: "Password", description: "Your MiHome password", required: true, submitOnChange: true) - } - } - } -} - -// App lifecycle hooks - -def installed() { - initialize() - // Check for new devices and remove old ones every 3 hours - runEvery3Hours('updateDevices') - // execute handlerMethod every 10 minutes. - schedule("0 0/10 * * * ?", refreshDevices) -} - -// called after settings are changed -def updated() { - initialize() - unschedule('refreshDevices') - schedule("0 0/10 * * * ?", refreshDevices) -} - -def uninstalled() { - log.info("Uninstalling, removing child devices...") - unschedule('updateDevices') - unschedule('refreshDevices') - removeChildDevices(getChildDevices()) -} - -private removeChildDevices(devices) { - devices.each { - deleteChildDevice(it.deviceNetworkId) // 'it' is default - } -} - -// called after Done is hit after selecting a Location -def initialize() { - log.debug "initialize" - updateDevices() -} - - -def updateDevices() { - if (!state.devices) { - state.devices = [:] - } - def devices = devicesList() - def selectors = [] - devices.each { device -> - def childDevice = getChildDevice("${device.id}") - selectors.add("${device.id}") - if (!childDevice) { - log.info("Adding device ${device.id}: ${device.device_type}: ${device.label}: ${device.target_temperature}: ${device.last_temperature}: ${device.voltage}") - if (device.device_type == 'etrv') - { - def data = [ - name: device.label + " eTRV", - label: device.label + " eTRV", - temperature: device.last_temperature, - heatingSetpoint: device.target_temperature, - switch: device.target_temperature == 12 ? "off" : "on" - ] - childDevice = addChildDevice(app.namespace, "MiHome eTRV", "$device.id", null, data) - } - } - } - getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { - log.info("Deleting ${it.deviceNetworkId}") - deleteChildDevice(it.deviceNetworkId) - } - runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block -} - -def refreshDevices() { - log.info("Refreshing all devices...") - getChildDevices().each { device -> - device.refresh() - } -} - -def devicesList() { - logErrors([]) { - def resp = apiGET("/subdevices/list") - if (resp.status == 200) { - return resp.data.data - } else { - log.error("Non-200 from device list call. ${resp.status} ${resp.data}") - return [] - } - } -} - -def getMiHomeAccessToken() { - def resp = apiGET("/users/profile") - if (resp.status == 200) { - state.miHomeAccessToken = resp.data.data.api_key - log.debug "miHomeAccessToken: $resp.data.data.api_key" - } else { - log.error("Non-200 from device list call. ${resp.status} ${resp.data}") - return [] - } -} - -def apiGET(path) { - try { - httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response -> - logResponse(response) - return response - } - } catch (groovyx.net.http.HttpResponseException e) { - logResponse(e.response) - return e.response - } -} - -def apiPOST(path, body = [:]) { - try { - log.debug("Beginning API POST: ${path}, ${body}") - httpGet(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders() ) {response -> - logResponse(response) - return response - } - } catch (groovyx.net.http.HttpResponseException e) { - logResponse(e.response) - return e.response - } -} - -Map apiRequestHeaders() { - def userpassascii = "${username}:${password}" - if (state.miHomeAccessToken != null && state.miHomeAccessToken != '') { - userpassascii = "${username}:${state.miHomeAccessToken}" - } - def userpass = "Basic " + userpassascii.encodeAsBase64().toString() - log.debug userpassascii - - return ["User-Agent": "SmartThings Integration", - "Authorization": "$userpass" - ] -} - -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) { - options.logObject.error("got error: ${e}, body: ${e.getResponse().getData()}") - if (e.statusCode == 401) { // token is expired - state.remove("lifxAccessToken") - options.logObject.warn "Access token is not valid" - } - return options.errorReturn - } catch (java.net.SocketTimeoutException e) { - options.logObject.warn "Connection timed out, not much we can do here" - return options.errorReturn - } -} \ No newline at end of file From 306780f9fa91727492fb47783021ff676c08ed4f Mon Sep 17 00:00:00 2001 From: alyc100 Date: Sun, 4 Sep 2016 22:46:58 +0100 Subject: [PATCH 200/685] Contact Sensor Image --- ...eb38ab971acecba02586d0ec485c588f2646c935.jpg | Bin 0 -> 12145 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 smartapps/alyc100/hive-window-door-sensor-815702baa8f484d342f2ebf3eb38ab971acecba02586d0ec485c588f2646c935.jpg diff --git a/smartapps/alyc100/hive-window-door-sensor-815702baa8f484d342f2ebf3eb38ab971acecba02586d0ec485c588f2646c935.jpg b/smartapps/alyc100/hive-window-door-sensor-815702baa8f484d342f2ebf3eb38ab971acecba02586d0ec485c588f2646c935.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0c57b6bc16ec3c606d585f5552173875d2c0e57a GIT binary patch literal 12145 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 H!)^Zu`nHJz literal 0 HcmV?d00001 From 0c7c39979317cbebc104f744a21980158bca0724 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sun, 4 Sep 2016 22:56:16 +0100 Subject: [PATCH 201/685] v2.3 - Added support for Hive Contact Sensor - Author: Simon Green --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 21d569056df..19214e16cfa 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -2,6 +2,7 @@ * Hive (Connect) * * Copyright 2015,2016 Alex Lee Yuk Cheung + * Hive Contact Sensor code portions contributed by Simon Green * * 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: @@ -27,6 +28,9 @@ * * 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 */ definition( name: "Hive (Connect)", @@ -104,7 +108,7 @@ def mainPage() { def headerSECTION() { return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png", - "Hive (Connect)\nVersion: 2.2\nDate: 01092016(1200)") + "Hive (Connect)\nVersion: 2.3\nDate: 04092016(2300)") } def stateTokenPresent() { @@ -234,7 +238,7 @@ def selectDevicePAGE() { 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 "selectedContactSensor", "enum", image: "https://www.hivehome.com/assets/hive-window-door-sensor-815702baa8f484d342f2ebf3eb38ab971acecba02586d0ec485c588f2646c935.jpg", required:false, title:"Select Hive Contact Sensors \n(${state.hiveContactSensorDevices.size() ?: 0} found)", multiple:true, options:state.hiveContactSensorDevices + input "selectedContactSensor", "enum", image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/hive-window-door-sensor-815702baa8f484d342f2ebf3eb38ab971acecba02586d0ec485c588f2646c935.jpg", required:false, title:"Select Hive Contact Sensors \n(${state.hiveContactSensorDevices.size() ?: 0} found)", multiple:true, options:state.hiveContactSensorDevices } } } @@ -1148,4 +1152,4 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { } } -def appName() { return "${parent ? "Hive Mode Automation" : "Hive (Connect)"}" } +def appName() { return "${parent ? "Hive Mode Automation" : "Hive (Connect)"}" } \ No newline at end of file From 0b6ce7e1f2dad9c92defdc4e229c34c452434baa Mon Sep 17 00:00:00 2001 From: alyc100 Date: Sun, 4 Sep 2016 23:13:39 +0100 Subject: [PATCH 202/685] Minor Tidy of Contact Sensor device code --- .../hive-contact-sensor.src/hive-contact-sensor.groovy | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy b/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy index 10703fc40d1..b99df4672fc 100644 --- a/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy +++ b/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy @@ -22,7 +22,6 @@ metadata { capability "Actuator" capability "Polling" capability "Refresh" -// capability "Temperature Measurement" capability "Contact Sensor" } @@ -45,13 +44,10 @@ metadata { // parse events into attributes def parse(String description) { log.debug "Parsing '${description}'" - // TODO: handle 'switch' attribute - // TODO: handle 'thermostatMode' attribute } def installed() { log.debug "Executing 'installed'" - state.boostLength = 60 } def poll() { @@ -84,4 +80,4 @@ def poll() { def refresh() { log.debug "Executing 'refresh'" poll() -} +} \ No newline at end of file From 72e968fed8d60745f7fa655762d6a4f2cb650396 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Tue, 6 Sep 2016 12:56:49 +0100 Subject: [PATCH 203/685] v2.3.1 - Improve device detection --- .../alyc100/hive-connect.src/hive-connect.groovy | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 19214e16cfa..c975b6da708 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -31,6 +31,9 @@ * * 04.09.2016 * v2.3 - Added support for Hive Contact Sensor - Author: Simon Green + * + * 06.09.2016 + * v2.3.1 - Improve device detection */ definition( name: "Hive (Connect)", @@ -108,7 +111,7 @@ def mainPage() { def headerSECTION() { return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png", - "Hive (Connect)\nVersion: 2.3\nDate: 04092016(2300)") + "Hive (Connect)\nVersion: 2.3.1\nDate: 06092016(1250)") } def stateTokenPresent() { @@ -842,7 +845,7 @@ def updateDevices() { def selectors = [] devices.each { device -> selectors.add("${device.id}") - if (device.attributes.activeHeatCoolMode != null) { + if (device.nodeType == "http://alertme.com/schema/json/node.class.thermostat.json#" && device.attributes.activeHeatCoolMode != null) { def parentNode = devices.find { d -> d.id == device.parentNodeId } log.debug "Found Device: ${parentNode.name}" // Heating Control @@ -862,7 +865,7 @@ def updateDevices() { } } // Water Control - } else if ((device.attributes.supportsHotWater != null) && (device.attributes.supportsHotWater.reportedValue == true)) { + } else if (device.nodeType == "http://alertme.com/schema/json/node.class.thermostat.json#" && device.attributes.supportsHotWater != null && device.attributes.supportsHotWater.reportedValue == true) { log.debug "Identified: ${parentNode.name} Hive Hot Water" def value = "${parentNode.name} Hive Hot Water" def key = device.id @@ -879,7 +882,7 @@ def updateDevices() { } } // Contact Sensor - } else if (device.attributes.state != null) { + } else if (device.nodeType == "http://alertme.com/schema/json/node.class.contact.sensor.json#" && device.attributes.state != null) { log.debug "Identified: ${device.name} Hive Contact Sensor" def value = "${device.name} Hive Contact Sensor" def key = device.id From d45bc4c24f8d29e77c42edfc42ed7f1de4c95f08 Mon Sep 17 00:00:00 2001 From: Simon Green Date: Thu, 8 Sep 2016 18:21:46 +0100 Subject: [PATCH 204/685] Updated API URL based on changes at Hive's end See comments on http://www.smartofthehome.com/2016/05/hive-rest-api-v6/ for corroboration. --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index c975b6da708..2ca099a3844 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -60,7 +60,7 @@ preferences { page(name: "tmaConfigurePAGE") } -def apiURL(path = '/') { return "https://api.prod.bgchprod.info:443/omnia${path}" } +def apiURL(path = '/') { return "https://api-prod.bgchprod.info:443/omnia${path}" } def startPage() { if (parent) { @@ -1155,4 +1155,4 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { } } -def appName() { return "${parent ? "Hive Mode Automation" : "Hive (Connect)"}" } \ No newline at end of file +def appName() { return "${parent ? "Hive Mode Automation" : "Hive (Connect)"}" } From 707e0031cf55ec5aa7d07b9756e7c4bc4a4ca4fc Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 10 Sep 2016 12:20:29 +0100 Subject: [PATCH 205/685] v2.1.6 - Allow a maximum temperature threshold to be set. --- .../hive-heating-v2-0.groovy | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index d58ed1ba77a..a4dd294c6df 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -29,11 +29,15 @@ * v2.1.5e - Another attempt to fix blank temperature reading on Android. * v2.1.5f - Allow decimal value for boost temperature. Changes to VALUE_CONTROL method to match latest ST docs. * v2.1.5g - Changes to tile display for iOS app v2.1.2 + * + * 10.09.2016 + * v2.1.6 - Allow a maximum temperature threshold to be set. */ preferences { input( "boostInterval", "number", title: "Boost Interval (minutes)", description: "Boost interval amount in minutes", required: false, defaultValue: 10 ) input( "boostTemp", "decimal", title: "Boost Temperature (°C)", description: "Boost interval amount in Centigrade", required: false, defaultValue: 22, range: "5..32" ) + input( "maxTempThreshold", "decimal", title: "Max Temperature Threshold (°C)", description: "Set the maximum temperature threshold in Centigrade", required: false, defaultValue: 32, range: "5..32" ) input( "disableDevice", "bool", title: "Disable Hive Heating?", required: false, defaultValue: false ) } @@ -261,12 +265,19 @@ def getBoostIntervalValue() { } def getBoostTempValue() { - if (settings.boostInterval == null) { + if (settings.boostTemp == null) { return "22" } return settings.boostTemp } +def getMaxTempThreshold() { + if (settings.maxTempThreshold == null) { + return "32" + } + return settings.maxTempThreshold +} + def boostTimeUp() { log.debug "Executing 'boostTimeUp'" //Round down result @@ -418,6 +429,21 @@ def poll() { temperature = String.format("%2.1f",temperature) heatingSetpoint = String.format("%2.1f",heatingSetpoint) + //Check heating set point against maximum threshold value. + log.debug "Maximum temperature threshold set to: " + getMaxTempThreshold() + if ((getMaxTempThreshold() as BigDecimal) < (heatingSetpoint as BigDecimal)) + { + log.debug "Maximum temperature threshold exceeded. " + heatingSetpoint + " is higher than " + getMaxTempThreshold() + //Force temperature threshold to Hive API. + // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} + def args = [ + nodes: [ [attributes: [targetHeatTemperature: [targetValue: getMaxTempThreshold()]]]] + ] + + parent.apiPUT("/nodes/${device.deviceNetworkId}", args) + heatingSetpoint = String.format("%2.1f", getMaxTempThreshold()) + } + // convert temperature reading of 1 degree to 7 as Hive app does if (heatingSetpoint == "1.0") { heatingSetpoint = "7.0" From 2291dc9a05774e10eff4e77b9225e7645b81f28e Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 10 Sep 2016 16:13:21 +0100 Subject: [PATCH 206/685] v2.1.6b - Added event for maximum temperature threshold breach. --- .../alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy index a4dd294c6df..263ed45dbd2 100644 --- a/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy +++ b/devicetypes/alyc100/hive-heating-v2-0.src/hive-heating-v2-0.groovy @@ -32,6 +32,7 @@ * * 10.09.2016 * v2.1.6 - Allow a maximum temperature threshold to be set. + * v2.1.6b - Added event for maximum temperature threshold breach. */ preferences { @@ -434,6 +435,7 @@ def poll() { if ((getMaxTempThreshold() as BigDecimal) < (heatingSetpoint as BigDecimal)) { log.debug "Maximum temperature threshold exceeded. " + heatingSetpoint + " is higher than " + getMaxTempThreshold() + sendEvent(name: 'maxtempthresholdbreach', value: heatingSetpoint, unit: "C", displayed: false) //Force temperature threshold to Hive API. // {"nodes":[{"attributes":{"targetHeatTemperature":{"targetValue":11}}}]} def args = [ @@ -441,7 +443,7 @@ def poll() { ] parent.apiPUT("/nodes/${device.deviceNetworkId}", args) - heatingSetpoint = String.format("%2.1f", getMaxTempThreshold()) + heatingSetpoint = String.format("%2.1f", getMaxTempThreshold()) } // convert temperature reading of 1 degree to 7 as Hive app does From a83c605ae8909597559152841c69ceab49701e74 Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 10 Sep 2016 16:35:05 +0100 Subject: [PATCH 207/685] v2.3.2 - Added notification option for maximum temperature threshold breach for Hive heating devices. --- .../hive-connect.src/hive-connect.groovy | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index c975b6da708..55c96a11fa3 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -34,6 +34,9 @@ * * 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. */ definition( name: "Hive (Connect)", @@ -127,7 +130,7 @@ def devicesSelected() { } def preferencesSelected() { - return (sendPush || sendSMS != null) && (maxtemp != null || mintemp != null || sendBoost || sendOff || sendManual || sendSchedule) ? "complete" : null + return (sendPush || sendSMS != null) && (maxtemp != null || mintemp != null || sendBoost || sendOff || sendManual || sendSchedule || sendMaxThresholdBreach) ? "complete" : null } def tmaDescription() { @@ -198,6 +201,7 @@ def getPreferencesString() { 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 } @@ -264,6 +268,9 @@ def preferencesPAGE() { 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 + } } } @@ -483,6 +490,7 @@ def initialize() { } if (childDevice.typeName == "Hive Heating V2.0") { subscribe(childDevice, "temperature", tempHandler, [filterEvents: false]) + subscribe(childDevice, "maxtempthresholdbreach", evtHandler, [filterEvents: false]) } } } @@ -492,6 +500,14 @@ def initialize() { } //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" From 82aa35639ca894159b32c58787b2481cdb0ddcaf Mon Sep 17 00:00:00 2001 From: Alex Lee Yuk Cheung Date: Sat, 10 Sep 2016 16:37:16 +0100 Subject: [PATCH 208/685] Header update --- smartapps/alyc100/hive-connect.src/hive-connect.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartapps/alyc100/hive-connect.src/hive-connect.groovy b/smartapps/alyc100/hive-connect.src/hive-connect.groovy index 55c96a11fa3..56e1385fdee 100644 --- a/smartapps/alyc100/hive-connect.src/hive-connect.groovy +++ b/smartapps/alyc100/hive-connect.src/hive-connect.groovy @@ -114,7 +114,7 @@ def mainPage() { def headerSECTION() { return paragraph (image: "https://raw.githubusercontent.com/alyc100/SmartThingsPublic/master/smartapps/alyc100/10457773_334250273417145_3395772416845089626_n.png", - "Hive (Connect)\nVersion: 2.3.1\nDate: 06092016(1250)") + "Hive (Connect)\nVersion: 2.3.2\nDate: 10092016(1630)") } def stateTokenPresent() { From 231ef343eb47d951c54b84b6b1bdb45c31adfd89 Mon Sep 17 00:00:00 2001 From: Simon Green Date: Sat, 10 Sep 2016 20:15:02 +0100 Subject: [PATCH 209/685] v1.1 Added support for temperature (the contact sensor has a temperature sensor in it) and battery level reading. Properly defined capabilities and attributes for use in other smartapps. --- .../hive-contact-sensor.groovy | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy b/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy index b99df4672fc..b16580ffe25 100644 --- a/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy +++ b/devicetypes/alyc100/hive-contact-sensor.src/hive-contact-sensor.groovy @@ -15,14 +15,22 @@ * VERSION HISTORY * 4th Sept. '16 * v1.0 Initial Release +* + * 10th Sept. '16 + * v1.1 Added support for temperature (the contact sensor has a temperature sensor in it) and battery level reading. Properly defined capabilities and attributes for use in other smartapps. */ metadata { definition (name: "Hive Window or Door Sensor V1.0", namespace: "simonjgreen", author: "Simon Green") { - capability "Actuator" capability "Polling" capability "Refresh" - capability "Contact Sensor" + capability "Contact Sensor" // "contact" string ("open" | "closed") + capability "Temperature Measurement" // "temperature" number + capability "Battery" // "battery" any + + attribute "contact", "string", ["open", "closed"] + attribute "temperature", "number" + attribute "battery", "string" } simulator { @@ -31,10 +39,16 @@ metadata { } tiles(scale: 2){ - standardTile("state", "device.state", width: 6, height: 4, key:"PRIMARY_CONTROL") { + standardTile("contact", "device.contact", width: 6, height: 4, key:"PRIMARY_CONTROL") { state "open", label: "open", icon: "st.contact.contact.open", backgroundColor: "#FF0000" state "closed", label: "closed", icon: "st.contact.contact.closed", backgroundColor: "#00CC00" } + standardTile("temperature", "device.temperature", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("temperature", label: '${currentValue} °C', icon:"st.Weather.weather2") + } + standardTile("battery", "device.battery", inactiveLabel: true, decoration: "flat", width: 2, height: 2) { + state("battery", label: '${currentValue}', icon:"st.Appliances.appliances17") + } standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state("default", label:'refresh', action:"polling.poll", icon:"st.secondary.refresh-icon") } @@ -63,21 +77,29 @@ def poll() { def statusMsg = "Currently" // determine contact sensor state - def state = data.nodes.attributes.state.reportedValue[0] + def contact = data.nodes.attributes.state.reportedValue[0] + def temperature = data.nodes.attributes.temperature.reportedValue[0] + def battery = data.nodes.attributes.batteryState.reportedValue[0] - log.debug "state: $state" + log.debug "contact: $contact" + log.debug "temperature: $temperature" + log.debug "battery: $battery" - if (state == "OPEN") { + if (contact == "OPEN") { statusMsg = statusMsg + " Open" + contact = "open" } - else if (state == "CLOSED") { + else if (contact == "CLOSED") { statusMsg = statusMsg + " Closed" + contact = "closed" } - sendEvent(name: 'state', value: state) + sendEvent(name: 'contact', value: contact) + sendEvent(name: 'temperature', value: temperature) + sendEvent(name: 'battery', value: battery) } def refresh() { log.debug "Executing 'refresh'" poll() -} \ No newline at end of file +} From 9e19b1d73ca6a4fb2cd73c3138b9d72d9726cce2 Mon Sep 17 00:00:00 2001 From: alyc100 Date: Thu, 13 Oct 2016 10:16:43 +0100 Subject: [PATCH 210/685] Add files via upload --- smartapps/alyc100/neato_icon.png | Bin 0 -> 10857 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 smartapps/alyc100/neato_icon.png diff --git a/smartapps/alyc100/neato_icon.png b/smartapps/alyc100/neato_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f18079915f49bf482e65b13b18bbceb349ae7846 GIT binary patch literal 10857 zcmeHt`8$-+|Mv`vk}O#&Wywe>Lkbbo*fMrmi-wS$lr_sRDp?|1j3p#%3E49kWz81G zGWIFk*oVOk2J^l9e4gw1BcAUw*EPSqXYO;(`kQ|BnJ9kh=G!^!+50HBAzN#Q6TsCP; zB7L!bm_VR>5jY5Bz{UkapJ8GF{do-ngKmrdF9=2Ps`zm` zL)x>{YcFatZ z?}+2;A~nYEXjtB9AO;B|)#;%ZvEONXpKNeK&Ttkswc=62wQ$Zt-OX2wfF_(LOgfkn zCcrPOyIIMZf16}-^+mQW%!x5DzM*zm$>&7W^@>yt{zEKnWrhc7m#Wc(`${X1(MWmp zJ6OntT*ZKJon&EqDV33MKqn(?_pOXKLV68@2I&VLS9{VO1JA_5gQNy*yLEliJQ41@ zUls_971wlo=w>`p)b6k!B5DZJqjzrYo%?eqCB z552t>Qj@% zRX$9>>?$v4+g1|kkF=nruW?B7|Mp-)Q%Z7o)L5x+uV`Q_kS5J3OC}v zD4BtpdD^LRqjSYYx!L10dT;`{=_{YI`(^@R*CL!M+K#@)#De2Xno2~ip6-NsWz1Q> z(0~y0a`T#@{7Jjxg1UCIYvP-Y!rFQEk%uQA1Iwyo@=JF;jTPcej7>yQgtDA&rE3&J zzvwE_w1+cDmKl7c$RaRs|8qwU_9QRRMk^X5