Compare commits
1 Commits
prod
...
MSA-2824-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30c9b4828e |
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
TP-Link Plug and Switch Device Handler, 2018, Version 2
|
||||
Copyright 2018 Dave Gutheinz
|
||||
|
||||
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.
|
||||
|
||||
Discalimer: This Service Manager and the associated Device
|
||||
Handlers are in no way sanctioned or supported by TP-Link.
|
||||
All development is based upon open-source data on the
|
||||
TP-Link devices; primarily various users on GitHub.com.
|
||||
|
||||
===== History =============================================
|
||||
2018-01-31 Update to Version 2
|
||||
a. Common file content for all bulb implementations,
|
||||
using separate files by model only.
|
||||
b. User file-internal selection of Energy Monitor
|
||||
function enabling.
|
||||
2018-02-17 Updated Energy Monitor Functions
|
||||
a. Allowed for full month collection in previous month
|
||||
b. Cleaned-up algorithm to use Groovy date.
|
||||
2018-02-19 Completed Energy Monitor tuning
|
||||
a. Fixed March 1, 2 issue where data would not be
|
||||
captured
|
||||
b. Update remaining code.
|
||||
2018-04-22 Update setCurrentDate to eliminate error for some users.
|
||||
// ===== Hub or Cloud Installation =========================*/
|
||||
def installType = "Cloud"
|
||||
//def installType = "Hub"
|
||||
// ===========================================================
|
||||
|
||||
metadata {
|
||||
definition (name: "(${installType}) TP-Link EnergyMonitor Plug",
|
||||
namespace: "davegut",
|
||||
author: "Dave Gutheinz",
|
||||
deviceType: "EnergyMonitor Plug",
|
||||
energyMonitor: "EnergyMonitor",
|
||||
installType: "${installType}") {
|
||||
capability "Switch"
|
||||
capability "refresh"
|
||||
capability "polling"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
capability "Power Meter"
|
||||
command "getPower"
|
||||
capability "Energy Meter"
|
||||
command "getEnergyStats"
|
||||
attribute "monthTotalE", "string"
|
||||
attribute "monthAvgE", "string"
|
||||
attribute "weekTotalE", "string"
|
||||
attribute "weekAvgE", "string"
|
||||
}
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00a0dc",
|
||||
nextState:"waiting"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff",
|
||||
nextState:"waiting"
|
||||
attributeState "waiting", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#15EE10",
|
||||
nextState:"waiting"
|
||||
attributeState "commsError", label:'Comms Error', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#e86d13",
|
||||
nextState:"waiting"
|
||||
}
|
||||
tileAttribute ("deviceError", key: "SECONDARY_CONTROL") {
|
||||
attributeState "deviceError", label: '${currentValue}'
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "capability.refresh", width: 2, height: 1, decoration: "flat") {
|
||||
state "default", label:"Refresh", action:"refresh.refresh"
|
||||
}
|
||||
|
||||
valueTile("currentPower", "device.power", decoration: "flat", height: 1, width: 2) {
|
||||
state "power", label: 'Current Power \n\r ${currentValue} W'
|
||||
}
|
||||
|
||||
valueTile("energyToday", "device.energy", decoration: "flat", height: 1, width: 2) {
|
||||
state "energy", label: 'Usage Today\n\r${currentValue} WattHr'
|
||||
}
|
||||
|
||||
valueTile("monthTotal", "device.monthTotalE", decoration: "flat", height: 1, width: 2) {
|
||||
state "monthTotalE", label: '30 Day Total\n\r ${currentValue} KWH'
|
||||
}
|
||||
|
||||
valueTile("monthAverage", "device.monthAvgE", decoration: "flat", height: 1, width: 2) {
|
||||
state "monthAvgE", label: '30 Day Avg\n\r ${currentValue} KWH'
|
||||
}
|
||||
|
||||
valueTile("weekTotal", "device.weekTotalE", decoration: "flat", height: 1, width: 2) {
|
||||
state "weekTotalE", label: '7 Day Total\n\r ${currentValue} KWH'
|
||||
}
|
||||
|
||||
valueTile("weekAverage", "device.weekAvgE", decoration: "flat", height: 1, width: 2) {
|
||||
state "weekAvgE", label: '7 Day Avg\n\r ${currentValue} KWH'
|
||||
}
|
||||
|
||||
valueTile("4x1Blank", "default", decoration: "flat", height: 1, width: 4) {
|
||||
state "default", label: ''
|
||||
}
|
||||
|
||||
main("switch")
|
||||
details("switch", "refresh" ,"4x1Blank",
|
||||
"currentPower", "weekTotal", "monthTotal",
|
||||
"energyToday", "weekAverage", "monthAverage")
|
||||
}
|
||||
|
||||
def rates = [:]
|
||||
rates << ["5" : "Refresh every 5 minutes"]
|
||||
rates << ["10" : "Refresh every 10 minutes"]
|
||||
rates << ["15" : "Refresh every 15 minutes"]
|
||||
rates << ["30" : "Refresh every 30 minutes"]
|
||||
|
||||
preferences {
|
||||
if (installType == "Hub") {
|
||||
input("deviceIP", "text", title: "Device IP", required: true, displayDuringSetup: true)
|
||||
input("gatewayIP", "text", title: "Gateway IP", required: true, displayDuringSetup: true)
|
||||
}
|
||||
input name: "refreshRate", type: "enum", title: "Refresh Rate", options: rates, description: "Select Refresh Rate", required: false
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Update when installed or setting changed =====
|
||||
def installed() {
|
||||
update()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
runIn(2, update)
|
||||
}
|
||||
|
||||
def update() {
|
||||
state.deviceType = metadata.definition.deviceType
|
||||
state.installType = metadata.definition.installType
|
||||
state.emon = metadata.definition.energyMonitor
|
||||
state.emeterText = "emeter"
|
||||
state.getTimeText = "time"
|
||||
unschedule()
|
||||
switch(refreshRate) {
|
||||
case "5":
|
||||
runEvery5Minutes(refresh)
|
||||
log.info "Refresh Scheduled for every 5 minutes"
|
||||
break
|
||||
case "10":
|
||||
runEvery10Minutes(refresh)
|
||||
log.info "Refresh Scheduled for every 10 minutes"
|
||||
break
|
||||
case "15":
|
||||
runEvery15Minutes(refresh)
|
||||
log.info "Refresh Scheduled for every 15 minutes"
|
||||
break
|
||||
default:
|
||||
runEvery30Minutes(refresh)
|
||||
log.info "Refresh Scheduled for every 30 minutes"
|
||||
}
|
||||
schedule("0 05 0 * * ?", setCurrentDate)
|
||||
schedule("0 10 0 * * ?", getEnergyStats)
|
||||
setCurrentDate()
|
||||
runIn(2, refresh)
|
||||
runIn(7, getEnergyStats)
|
||||
}
|
||||
|
||||
void uninstalled() {
|
||||
if (state.installType == "Cloud") {
|
||||
def alias = device.label
|
||||
log.debug "Removing device ${alias} with DNI = ${device.deviceNetworkId}"
|
||||
parent.removeChildDevice(alias, device.deviceNetworkId)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Basic Plug Control/Status =====
|
||||
def on() {
|
||||
sendCmdtoServer('{"system":{"set_relay_state":{"state": 1}}}', "deviceCommand", "commandResponse")
|
||||
runIn(2, refresh)
|
||||
}
|
||||
|
||||
def off() {
|
||||
sendCmdtoServer('{"system":{"set_relay_state":{"state": 0}}}', "deviceCommand", "commandResponse")
|
||||
runIn(2, refresh)
|
||||
}
|
||||
|
||||
def poll() {
|
||||
sendCmdtoServer('{"system":{"get_sysinfo":{}}}', "deviceCommand", "commandResponse")
|
||||
}
|
||||
|
||||
def refresh(){
|
||||
sendCmdtoServer('{"system":{"get_sysinfo":{}}}', "deviceCommand", "commandResponse")
|
||||
runIn(2, getPower)
|
||||
}
|
||||
|
||||
def commandResponse(cmdResponse){
|
||||
if (cmdResponse.system.set_relay_state == null) {
|
||||
def status = cmdResponse.system.get_sysinfo.relay_state
|
||||
if (status == 1) {
|
||||
status = "on"
|
||||
} else {
|
||||
status = "off"
|
||||
}
|
||||
log.info "${device.name} ${device.label}: Power: ${status}"
|
||||
sendEvent(name: "switch", value: status)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Get Current Energy Data =====
|
||||
def getPower(){
|
||||
sendCmdtoServer("""{"${state.emeterText}":{"get_realtime":{}}}""", "deviceCommand", "energyMeterResponse")
|
||||
runIn(5, getConsumption)
|
||||
}
|
||||
|
||||
def energyMeterResponse(cmdResponse) {
|
||||
def realtime = cmdResponse["emeter"]["get_realtime"]
|
||||
if (realtime.power == null) {
|
||||
state.powerScale = "power_mw"
|
||||
state.energyScale = "energy_wh"
|
||||
} else {
|
||||
state.powerScale = "power"
|
||||
state.energyScale = "energy"
|
||||
}
|
||||
def powerConsumption = realtime."${state.powerScale}"
|
||||
if (state.powerScale == "power_mw") {
|
||||
powerConsumption = Math.round(powerConsumption/10) / 100
|
||||
} else {
|
||||
powerConsumption = Math.round(100*powerConsumption) / 100
|
||||
}
|
||||
sendEvent(name: "power", value: powerConsumption)
|
||||
log.info "$device.name $device.label: Updated CurrentPower to $powerConsumption"
|
||||
}
|
||||
|
||||
// ===== Get Today's Consumption =====
|
||||
def getConsumption(){
|
||||
sendCmdtoServer("""{"${state.emeterText}":{"get_daystat":{"month": ${state.monthToday}, "year": ${state.yearToday}}}}""", "emeterCmd", "useTodayResponse")
|
||||
}
|
||||
|
||||
def useTodayResponse(cmdResponse) {
|
||||
def wattHrToday
|
||||
def wattHrData
|
||||
def dayList = cmdResponse["emeter"]["get_daystat"].day_list
|
||||
for (int i = 0; i < dayList.size(); i++) {
|
||||
wattHrData = dayList[i]
|
||||
if(wattHrData.day == state.dayToday) {
|
||||
wattHrToday = wattHrData."${state.energyScale}"
|
||||
}
|
||||
}
|
||||
if (state.powerScale == "power") {
|
||||
wattHrToday = Math.round(1000*wattHrToday)
|
||||
}
|
||||
sendEvent(name: "energy", value: wattHrToday)
|
||||
log.info "$device.name $device.label: Updated Usage Today to ${wattHrToday}"
|
||||
}
|
||||
|
||||
// ===== Get Weekly and Monthly Stats =====
|
||||
def getEnergyStats() {
|
||||
state.monTotEnergy = 0
|
||||
state.monTotDays = 0
|
||||
state.wkTotEnergy = 0
|
||||
state.wkTotDays = 0
|
||||
sendCmdtoServer("""{"${state.emeterText}":{"get_daystat":{"month": ${state.monthToday}, "year": ${state.yearToday}}}}""", "emeterCmd", "engrStatsResponse")
|
||||
runIn(4, getPrevMonth)
|
||||
}
|
||||
|
||||
def getPrevMonth() {
|
||||
def prevMonth = state.monthStart
|
||||
if (state.monthToday == state.monthStart) {
|
||||
// If all of the data is in this month, do not request previous month.
|
||||
// This will occur when the current month is 31 days.
|
||||
return
|
||||
} else if (state.monthToday - 2 == state.monthStart) {
|
||||
// If the start month is 2 less than current, we must handle
|
||||
// the data to get a third month - January.
|
||||
state.handleFeb = "yes"
|
||||
prevMonth = prevMonth + 1
|
||||
runIn(4, getJan)
|
||||
}
|
||||
// sendCmdtoServer("""{"${state.emeterText}":{"get_daystat":{"month": ${prevMonth}, "year": ${state.yearStart}}}}""", "emeterCmd", "engrStatsResponse")
|
||||
// ===== SIMULATOR COMMANDS ================================================
|
||||
sendCmdtoServer("""{"${state.emeterText}":{"get_daystat":{"month": ${prevMonth}, "year": ${state.yearStart}}}}""", "emeterCmd", "UseJanWatts")
|
||||
// ===== SIMULATOR COMMANDS ================================================
|
||||
}
|
||||
|
||||
def getJan() {
|
||||
// Gets January data on March 1 and 2. Only access if current month = 3
|
||||
// and start month = 1
|
||||
sendCmdtoServer("""{"${state.emeterText}":{"get_daystat":{"month": ${state.monthStart}, "year": ${state.yearStart}}}}""", "emeterCmd", "engrStatsResponse")
|
||||
}
|
||||
|
||||
def engrStatsResponse(cmdResponse) {
|
||||
/*
|
||||
This method parses up to two energy status messages from the device,
|
||||
adding the energy for the previous 30 days and week, ignoring the
|
||||
current day. It then calculates the 30 and 7 day average formatted
|
||||
in kiloWattHours to two decimal places.
|
||||
*/
|
||||
def dayList = cmdResponse[state.emeterText]["get_daystat"].day_list
|
||||
if (!dayList[0]) {
|
||||
log.info "$device.name $device.label: Month has no energy data."
|
||||
return
|
||||
}
|
||||
def monTotEnergy = state.monTotEnergy
|
||||
def wkTotEnergy = state.wkTotEnergy
|
||||
def monTotDays = state.monTotDays
|
||||
def wkTotDays = state.wkTotDays
|
||||
def startDay = state.dayStart
|
||||
def dataMonth = dayList[0].month
|
||||
if (dataMonth == state.monthToday) {
|
||||
for (int i = 0; i < dayList.size(); i++) {
|
||||
def energyData = dayList[i]
|
||||
monTotEnergy += energyData."${state.energyScale}"
|
||||
monTotDays += 1
|
||||
if (state.dayToday < 8 || energyData.day >= state.weekStart) {
|
||||
wkTotEnergy += energyData."${state.energyScale}"
|
||||
wkTotDays += 1
|
||||
}
|
||||
if(energyData.day == state.dayToday) {
|
||||
monTotEnergy -= energyData."${state.energyScale}"
|
||||
wkTotEnergy -= energyData."${state.energyScale}"
|
||||
monTotDays -= 1
|
||||
wkTotDays -= 1
|
||||
}
|
||||
}
|
||||
} else if (state.handleFeb == "yes" && dataMonth == 2) {
|
||||
startDay = 1
|
||||
for (int i = 0; i < dayList.size(); i++) {
|
||||
def energyData = dayList[i]
|
||||
if (energyData.day >= startDay) {
|
||||
monTotEnergy += energyData."${state.energyScale}"
|
||||
monTotDays += 1
|
||||
}
|
||||
if (energyData.day >= state.weekStart && state.dayToday < 8) {
|
||||
wkTotEnergy += energyData."${state.energyScale}"
|
||||
wkTotDays += 1
|
||||
}
|
||||
}
|
||||
} else if (state.handleFeb == "yes" && dataMonth == 1) {
|
||||
for (int i = 0; i < dayList.size(); i++) {
|
||||
def energyData = dayList[i]
|
||||
if (energyData.day >= startDay) {
|
||||
monTotEnergy += energyData."${state.energyScale}"
|
||||
monTotDays += 1
|
||||
}
|
||||
state.handleFeb = ""
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < dayList.size(); i++) {
|
||||
def energyData = dayList[i]
|
||||
if (energyData.day >= startDay) {
|
||||
monTotEnergy += energyData."${state.energyScale}"
|
||||
monTotDays += 1
|
||||
}
|
||||
if (energyData.day >= state.weekStart && state.dayToday < 8) {
|
||||
wkTotEnergy += energyData."${state.energyScale}"
|
||||
wkTotDays += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
state.monTotDays = monTotDays
|
||||
state.monTotEnergy = monTotEnergy
|
||||
state.wkTotEnergy = wkTotEnergy
|
||||
state.wkTotDays = wkTotDays
|
||||
log.info "$device.name $device.label: Update 7 and 30 day energy consumption statistics"
|
||||
if (monTotDays == 0) {
|
||||
// Aviod divide by zero on 1st of month
|
||||
monTotDays = 1
|
||||
wkTotDays = 1
|
||||
}
|
||||
def monAvgEnergy =monTotEnergy/monTotDays
|
||||
def wkAvgEnergy = wkTotEnergy/wkTotDays
|
||||
if (state.powerScale == "power_mw") {
|
||||
monAvgEnergy = Math.round(monAvgEnergy/10)/100
|
||||
wkAvgEnergy = Math.round(wkAvgEnergy/10)/100
|
||||
monTotEnergy = Math.round(monTotEnergy/10)/100
|
||||
wkTotEnergy = Math.round(wkTotEnergy/10)/100
|
||||
} else {
|
||||
monAvgEnergy = Math.round(100*monAvgEnergy)/100
|
||||
wkAvgEnergy = Math.round(100*wkAvgEnergy)/100
|
||||
monTotEnergy = Math.round(100*monTotEnergy)/100
|
||||
wkTotEnergy = Math.round(100*wkTotEnergy)/100
|
||||
}
|
||||
sendEvent(name: "monthTotalE", value: monTotEnergy)
|
||||
sendEvent(name: "monthAvgE", value: monAvgEnergy)
|
||||
sendEvent(name: "weekTotalE", value: wkTotEnergy)
|
||||
sendEvent(name: "weekAvgE", value: wkAvgEnergy)
|
||||
}
|
||||
|
||||
// ===== Obtain Week and Month Data =====
|
||||
def setCurrentDate() {
|
||||
sendCmdtoServer('{"time":{"get_time":null}}', "deviceCommand", "currentDateResponse")
|
||||
}
|
||||
|
||||
def currentDateResponse(cmdResponse) {
|
||||
def currDate = cmdResponse["time"]["get_time"]
|
||||
state.dayToday = currDate.mday.toInteger()
|
||||
state.monthToday = currDate.month.toInteger()
|
||||
state.yearToday = currDate.year.toInteger()
|
||||
def dateToday = Date.parse("yyyy-MM-dd", "${currDate.year}-${currDate.month}-${currDate.mday}")
|
||||
def monStartDate = dateToday - 30
|
||||
def wkStartDate = dateToday - 7
|
||||
state.dayStart = monStartDate[Calendar.DAY_OF_MONTH].toInteger()
|
||||
state.monthStart = monStartDate[Calendar.MONTH].toInteger() + 1
|
||||
state.yearStart = monStartDate[Calendar.YEAR].toInteger()
|
||||
state.weekStart = wkStartDate[Calendar.DAY_OF_MONTH].toInteger()
|
||||
}
|
||||
|
||||
// ----- SEND COMMAND TO CLOUD VIA SM -----
|
||||
private sendCmdtoServer(command, hubCommand, action) {
|
||||
if (state.installType == "Hub") {
|
||||
sendCmdtoHub(command, hubCommand, action)
|
||||
} else {
|
||||
sendCmdtoCloud(command, hubCommand, action)
|
||||
}
|
||||
}
|
||||
|
||||
private sendCmdtoCloud(command, hubCommand, action){
|
||||
def appServerUrl = getDataValue("appServerUrl")
|
||||
def deviceId = getDataValue("deviceId")
|
||||
def cmdResponse = parent.sendDeviceCmd(appServerUrl, deviceId, command)
|
||||
String cmdResp = cmdResponse.toString()
|
||||
if (cmdResp.substring(0,5) == "ERROR"){
|
||||
def errMsg = cmdResp.substring(7,cmdResp.length())
|
||||
log.error "${device.name} ${device.label}: ${errMsg}"
|
||||
sendEvent(name: "switch", value: "commsError", descriptionText: errMsg)
|
||||
sendEvent(name: "deviceError", value: errMsg)
|
||||
action = ""
|
||||
} else {
|
||||
sendEvent(name: "deviceError", value: "OK")
|
||||
}
|
||||
actionDirector(action, cmdResponse)
|
||||
}
|
||||
|
||||
private sendCmdtoHub(command, hubCommand, action){
|
||||
def headers = [:]
|
||||
headers.put("HOST", "$gatewayIP:8082") // Same as on Hub.
|
||||
headers.put("tplink-iot-ip", deviceIP)
|
||||
headers.put("tplink-command", command)
|
||||
headers.put("action", action)
|
||||
headers.put("command", hubCommand)
|
||||
sendHubCommand(new physicalgraph.device.HubAction([
|
||||
headers: headers],
|
||||
device.deviceNetworkId,
|
||||
[callback: hubResponseParse]
|
||||
))
|
||||
}
|
||||
|
||||
def hubResponseParse(response) {
|
||||
def action = response.headers["action"]
|
||||
def cmdResponse = parseJson(response.headers["cmd-response"])
|
||||
if (cmdResponse == "TcpTimeout") {
|
||||
log.error "$device.name $device.label: Communications Error"
|
||||
sendEvent(name: "switch", value: "offline", descriptionText: "ERROR at hubResponseParse TCP Timeout")
|
||||
sendEvent(name: "deviceError", value: "TCP Timeout in Hub")
|
||||
} else {
|
||||
actionDirector(action, cmdResponse)
|
||||
sendEvent(name: "deviceError", value: "OK")
|
||||
}
|
||||
}
|
||||
|
||||
def actionDirector(action, cmdResponse) {
|
||||
switch(action) {
|
||||
case "commandResponse":
|
||||
commandResponse(cmdResponse)
|
||||
break
|
||||
|
||||
case "energyMeterResponse":
|
||||
energyMeterResponse(cmdResponse)
|
||||
break
|
||||
|
||||
case "useTodayResponse":
|
||||
useTodayResponse(cmdResponse)
|
||||
break
|
||||
|
||||
case "currentDateResponse":
|
||||
currentDateResponse(cmdResponse)
|
||||
break
|
||||
|
||||
case "engrStatsResponse":
|
||||
engrStatsResponse(cmdResponse)
|
||||
break
|
||||
|
||||
default:
|
||||
log.debug "at default"
|
||||
}
|
||||
}
|
||||
|
||||
// ----- CHILD / PARENT INTERCHANGE TASKS -----
|
||||
def syncAppServerUrl(newAppServerUrl) {
|
||||
updateDataValue("appServerUrl", newAppServerUrl)
|
||||
log.info "Updated appServerUrl for ${device.name} ${device.label}"
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
TP-Link Plug and Switch Device Handler, 2018, Version 2
|
||||
Copyright 2018 Dave Gutheinz
|
||||
|
||||
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.
|
||||
|
||||
Discalimer: This Service Manager and the associated Device
|
||||
Handlers are in no way sanctioned or supported by TP-Link.
|
||||
All development is based upon open-source data on the
|
||||
TP-Link devices; primarily various users on GitHub.com.
|
||||
|
||||
===== History =============================================
|
||||
2018-01-31 Update to Version 2
|
||||
a. Common file content for all bulb implementations,
|
||||
using separate files by model only.
|
||||
b. User file-internal selection of Energy Monitor
|
||||
function enabling.
|
||||
===== Plug/Switch Type. DO NOT EDIT ====================*/
|
||||
def deviceType = "Plug-Switch" // Plug/Switch
|
||||
// def deviceType = "Dimming Switch" // HS220 Only
|
||||
// ===== Hub or Cloud Installation =========================*/
|
||||
def installType = "Cloud"
|
||||
//def installType = "Hub"
|
||||
// ===========================================================
|
||||
|
||||
metadata {
|
||||
definition (name: "(${installType}) TP-Link ${deviceType}",
|
||||
namespace: "davegut",
|
||||
author: "Dave Gutheinz",
|
||||
deviceType: "${deviceType}",
|
||||
energyMonitor: "Standard",
|
||||
installType: "${installType}") {
|
||||
capability "Switch"
|
||||
capability "refresh"
|
||||
capability "polling"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
if (deviceType == "Dimming Switch") {
|
||||
capability "Switch Level"
|
||||
}
|
||||
}
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00a0dc",
|
||||
nextState:"waiting"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff",
|
||||
nextState:"waiting"
|
||||
attributeState "waiting", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#15EE10",
|
||||
nextState:"waiting"
|
||||
attributeState "commsError", label:'Comms Error', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#e86d13",
|
||||
nextState:"waiting"
|
||||
}
|
||||
if (deviceType == "Dimming Switch") {
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", label: "Brightness: ${currentValue}", action:"switch level.setLevel", range: "(1..100)"
|
||||
}
|
||||
}
|
||||
tileAttribute ("deviceError", key: "SECONDARY_CONTROL") {
|
||||
attributeState "deviceError", label: '${currentValue}'
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "capability.refresh", width: 2, height: 1, decoration: "flat") {
|
||||
state "default", label:"Refresh", action:"refresh.refresh"
|
||||
}
|
||||
|
||||
main("switch")
|
||||
details("switch", "refresh")
|
||||
}
|
||||
|
||||
def rates = [:]
|
||||
rates << ["1" : "Refresh every minutes (Not Recommended)"]
|
||||
rates << ["5" : "Refresh every 5 minutes"]
|
||||
rates << ["10" : "Refresh every 10 minutes"]
|
||||
rates << ["15" : "Refresh every 15 minutes"]
|
||||
rates << ["30" : "Refresh every 30 minutes (Recommended)"]
|
||||
|
||||
preferences {
|
||||
if (installType == "Hub") {
|
||||
input("deviceIP", "text", title: "Device IP", required: true, displayDuringSetup: true)
|
||||
input("gatewayIP", "text", title: "Gateway IP", required: true, displayDuringSetup: true)
|
||||
}
|
||||
input name: "refreshRate", type: "enum", title: "Refresh Rate", options: rates, description: "Select Refresh Rate", required: false
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Update when installed or setting changed =====
|
||||
def installed() {
|
||||
update()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
runIn(2, update)
|
||||
}
|
||||
|
||||
def update() {
|
||||
state.deviceType = metadata.definition.deviceType
|
||||
state.installType = metadata.definition.installType
|
||||
unschedule()
|
||||
switch(refreshRate) {
|
||||
case "1":
|
||||
runEvery1Minute(refresh)
|
||||
log.info "Refresh Scheduled for every minute"
|
||||
break
|
||||
case "5":
|
||||
runEvery5Minutes(refresh)
|
||||
log.info "Refresh Scheduled for every 5 minutes"
|
||||
break
|
||||
case "10":
|
||||
runEvery10Minutes(refresh)
|
||||
log.info "Refresh Scheduled for every 10 minutes"
|
||||
break
|
||||
case "15":
|
||||
runEvery15Minutes(refresh)
|
||||
log.info "Refresh Scheduled for every 15 minutes"
|
||||
break
|
||||
default:
|
||||
runEvery30Minutes(refresh)
|
||||
log.info "Refresh Scheduled for every 30 minutes"
|
||||
}
|
||||
runIn(5, refresh)
|
||||
}
|
||||
|
||||
void uninstalled() {
|
||||
if (state.installType == "Cloud") {
|
||||
def alias = device.label
|
||||
log.debug "Removing device ${alias} with DNI = ${device.deviceNetworkId}"
|
||||
parent.removeChildDevice(alias, device.deviceNetworkId)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Basic Plug Control/Status =====
|
||||
def on() {
|
||||
sendCmdtoServer('{"system":{"set_relay_state":{"state": 1}}}', "deviceCommand", "commandResponse")
|
||||
}
|
||||
|
||||
def off() {
|
||||
sendCmdtoServer('{"system":{"set_relay_state":{"state": 0}}}', "deviceCommand", "commandResponse")
|
||||
}
|
||||
|
||||
def setLevel(percentage) {
|
||||
percentage = percentage as int
|
||||
if (percentage == 0) {
|
||||
percentage = 1
|
||||
}
|
||||
sendCmdtoServer("""{"smartlife.iot.dimmer":{"set_brightness":{"brightness":${percentage}}}}""", "deviceCommand", "commandResponse")
|
||||
}
|
||||
|
||||
def poll() {
|
||||
sendCmdtoServer('{"system":{"get_sysinfo":{}}}', "deviceCommand", "refreshResponse")
|
||||
}
|
||||
|
||||
def refresh(){
|
||||
sendCmdtoServer('{"system":{"get_sysinfo":{}}}', "deviceCommand", "refreshResponse")
|
||||
}
|
||||
|
||||
def commandResponse(cmdResponse) {
|
||||
refresh()
|
||||
}
|
||||
|
||||
def refreshResponse(cmdResponse){
|
||||
def onOff = cmdResponse.system.get_sysinfo.relay_state
|
||||
if (onOff == 1) {
|
||||
onOff = "on"
|
||||
} else {
|
||||
onOff = "off"
|
||||
}
|
||||
sendEvent(name: "switch", value: onOff)
|
||||
def level = "0"
|
||||
if (state.deviceType == "Dimming Switch") {
|
||||
level = cmdResponse.system.get_sysinfo.brightness
|
||||
sendEvent(name: "level", value: level)
|
||||
}
|
||||
log.info "${device.name} ${device.label}: Power: ${onOff} / Dimmer Level: ${level}%"
|
||||
}
|
||||
|
||||
// ----- SEND COMMAND TO CLOUD VIA SM -----
|
||||
private sendCmdtoServer(command, hubCommand, action) {
|
||||
if (state.installType == "Hub") {
|
||||
sendCmdtoHub(command, hubCommand, action)
|
||||
} else {
|
||||
sendCmdtoCloud(command, hubCommand, action)
|
||||
}
|
||||
}
|
||||
|
||||
private sendCmdtoCloud(command, hubCommand, action){
|
||||
def appServerUrl = getDataValue("appServerUrl")
|
||||
def deviceId = getDataValue("deviceId")
|
||||
def cmdResponse = parent.sendDeviceCmd(appServerUrl, deviceId, command)
|
||||
String cmdResp = cmdResponse.toString()
|
||||
if (cmdResp.substring(0,5) == "ERROR"){
|
||||
def errMsg = cmdResp.substring(7,cmdResp.length())
|
||||
log.error "${device.name} ${device.label}: ${errMsg}"
|
||||
sendEvent(name: "switch", value: "commsError", descriptionText: errMsg)
|
||||
sendEvent(name: "deviceError", value: errMsg)
|
||||
action = ""
|
||||
} else {
|
||||
sendEvent(name: "deviceError", value: "OK")
|
||||
}
|
||||
actionDirector(action, cmdResponse)
|
||||
}
|
||||
|
||||
private sendCmdtoHub(command, hubCommand, action){
|
||||
def headers = [:]
|
||||
headers.put("HOST", "$gatewayIP:8082") // Same as on Hub.
|
||||
headers.put("tplink-iot-ip", deviceIP)
|
||||
headers.put("tplink-command", command)
|
||||
headers.put("action", action)
|
||||
headers.put("command", hubCommand)
|
||||
sendHubCommand(new physicalgraph.device.HubAction([
|
||||
headers: headers],
|
||||
device.deviceNetworkId,
|
||||
[callback: hubResponseParse]
|
||||
))
|
||||
}
|
||||
|
||||
def hubResponseParse(response) {
|
||||
def action = response.headers["action"]
|
||||
def cmdResponse = parseJson(response.headers["cmd-response"])
|
||||
if (cmdResponse == "TcpTimeout") {
|
||||
log.error "$device.name $device.label: Communications Error"
|
||||
sendEvent(name: "switch", value: "offline", descriptionText: "ERROR at hubResponseParse TCP Timeout")
|
||||
sendEvent(name: "deviceError", value: "TCP Timeout in Hub")
|
||||
} else {
|
||||
actionDirector(action, cmdResponse)
|
||||
sendEvent(name: "deviceError", value: "OK")
|
||||
}
|
||||
}
|
||||
|
||||
def actionDirector(action, cmdResponse) {
|
||||
switch(action) {
|
||||
case "commandResponse":
|
||||
commandResponse(cmdResponse)
|
||||
break
|
||||
|
||||
case "refreshResponse":
|
||||
refreshResponse(cmdResponse)
|
||||
break
|
||||
|
||||
default:
|
||||
log.debug "at default"
|
||||
}
|
||||
}
|
||||
|
||||
// ----- CHILD / PARENT INTERCHANGE TASKS -----
|
||||
def syncAppServerUrl(newAppServerUrl) {
|
||||
updateDataValue("appServerUrl", newAppServerUrl)
|
||||
log.info "Updated appServerUrl for ${device.name} ${device.label}"
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
TP-Link Connect Service Manager, 2018 Version 2
|
||||
|
||||
Copyright 2018 Dave Gutheinz
|
||||
|
||||
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.
|
||||
|
||||
##### Discalimer: This Service Manager and the associated Device
|
||||
Handlers are in no way sanctioned or supported by TP-Link. All
|
||||
development is based upon open-source data on the TP-Link devices;
|
||||
primarily various users on GitHub.com.
|
||||
|
||||
##### Notes #####
|
||||
1. This Service Manager is designed to install and manage TP-Link
|
||||
bulbs, plugs, and switches using their respective device handlers.
|
||||
2. Please direct comments to the SmartThings community thread
|
||||
'Cloud TP-Link Device SmartThings Integration'.
|
||||
|
||||
##### History #####
|
||||
2018-01-31 Updated for new release of Device Handlers
|
||||
*/
|
||||
|
||||
definition(
|
||||
name: "TP-Link Cloud Connect",
|
||||
namespace: "davegut",
|
||||
author: "Dave Gutheinz",
|
||||
description: "A Service Manager for the TP-Link devices connecting through the TP-Link Cloud",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "http://ecx.images-amazon.com/images/I/51S8gO0bvZL._SL210_QL95_.png",
|
||||
iconX2Url: "http://ecx.images-amazon.com/images/I/51S8gO0bvZL._SL210_QL95_.png",
|
||||
iconX3Url: "http://ecx.images-amazon.com/images/I/51S8gO0bvZL._SL210_QL95_.png",
|
||||
singleInstance: true
|
||||
)
|
||||
|
||||
preferences {
|
||||
page(name: "cloudLogin", title: "TP-Link Cloud Login", nextPage:"", content:"cloudLogin", uninstall: true)
|
||||
page(name: "selectDevices", title: "Select TP-Link Devices", nextPage:"", content:"selectDevices", uninstall: true, install: true)
|
||||
}
|
||||
|
||||
def setInitialStates() {
|
||||
if (!state.TpLinkToken) {state.TpLinkToken = null}
|
||||
if (!state.devices) {state.devices = [:]}
|
||||
if (!state.currentError) {state.currentError = null}
|
||||
if (!state.errorCount) {state.errorCount = 0}
|
||||
}
|
||||
|
||||
// ----- LOGIN PAGE -----
|
||||
def cloudLogin() {
|
||||
setInitialStates()
|
||||
def cloudLoginText = "If possible, open the IDE and select Live Logging. THEN, " +
|
||||
"enter your Username and Password for TP-Link (same as Kasa app) and the "+
|
||||
"action you want to complete. Your current token:\n\r\n\r${state.TpLinkToken}" +
|
||||
"\n\r\n\rAvailable actions:\n\r" +
|
||||
" Initial Install: Obtains token and adds devices.\n\r" +
|
||||
" Add Devices: Only add devices.\n\r" +
|
||||
" Update Token: Updates the token.\n\r"
|
||||
def errorMsg = ""
|
||||
if (state.currentError != null){
|
||||
errorMsg = "Error communicating with cloud:\n\r\n\r${state.currentError}" +
|
||||
"\n\r\n\rPlease resolve the error and try again.\n\r\n\r"
|
||||
}
|
||||
return dynamicPage(
|
||||
name: "cloudLogin",
|
||||
title: "TP-Link Device Service Manager",
|
||||
nextPage: "selectDevices",
|
||||
uninstall: true) {
|
||||
section(errorMsg)
|
||||
section(cloudLoginText) {
|
||||
input(
|
||||
"userName", "string",
|
||||
title:"Your TP-Link Email Address",
|
||||
required:true,
|
||||
displayDuringSetup: true
|
||||
)
|
||||
input(
|
||||
"userPassword", "password",
|
||||
title:"TP-Link account password",
|
||||
required: true,
|
||||
displayDuringSetup: true
|
||||
)
|
||||
input(
|
||||
"updateToken", "enum",
|
||||
title: "What do you want to do?",
|
||||
required: true,
|
||||
multiple: false,
|
||||
options: ["Initial Install", "Add Devices", "Update Token"]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- SELECT DEVICES PAGE -----
|
||||
def selectDevices() {
|
||||
if (updateToken != "Add Devices") {
|
||||
getToken()
|
||||
}
|
||||
if (state.currentError != null || updateToken == "Update Token") {
|
||||
return cloudLogin()
|
||||
}
|
||||
getDevices()
|
||||
def devices = state.devices
|
||||
if (state.currentError != null) {
|
||||
return cloudLogin()
|
||||
}
|
||||
def errorMsg = ""
|
||||
if (devices == [:]) {
|
||||
errorMsg = "There were no devices from TP-Link. This usually means "+
|
||||
"that all devices are in 'Local Control Only'. Correct then " +
|
||||
"rerun.\n\r\n\r"
|
||||
}
|
||||
def newDevices = [:]
|
||||
devices.each {
|
||||
def isChild = getChildDevice(it.value.deviceMac)
|
||||
if (!isChild) {
|
||||
newDevices["${it.value.deviceMac}"] = "${it.value.alias} model ${it.value.deviceModel}"
|
||||
}
|
||||
}
|
||||
if (newDevices == [:]) {
|
||||
errorMsg = "No new devices to add. Are you sure they are in Remote " +
|
||||
"Control Mode?\n\r\n\r"
|
||||
}
|
||||
settings.selectedDevices = null
|
||||
def TPLinkDevicesMsg = "TP-Link Token is ${state.TpLinkToken}\n\r" +
|
||||
"Devices that have not been previously installed and are not in 'Local " +
|
||||
"WiFi control only' will appear below. TAP below to see the list of " +
|
||||
"TP-Link devices available select the ones you want to connect to " +
|
||||
"SmartThings.\n\r\n\rPress DONE when you have selected the devices you " +
|
||||
"wish to add, thenpress DONE again to install the devices. Press < " +
|
||||
"to return to the previous page."
|
||||
return dynamicPage(
|
||||
name: "selectDevices",
|
||||
title: "Select Your TP-Link Devices",
|
||||
install: true,
|
||||
uninstall: true) {
|
||||
section(errorMsg)
|
||||
section(TPLinkDevicesMsg) {
|
||||
input "selectedDevices", "enum",
|
||||
required:false,
|
||||
multiple:true,
|
||||
title: "Select Devices (${newDevices.size() ?: 0} found)",
|
||||
options: newDevices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getDevices() {
|
||||
def currentDevices = getDeviceData()
|
||||
state.devices = [:]
|
||||
def devices = state.devices
|
||||
currentDevices.each {
|
||||
def device = [:]
|
||||
device["deviceMac"] = it.deviceMac
|
||||
device["alias"] = it.alias
|
||||
device["deviceModel"] = it.deviceModel
|
||||
device["deviceId"] = it.deviceId
|
||||
device["appServerUrl"] = it.appServerUrl
|
||||
devices << ["${it.deviceMac}": device]
|
||||
def isChild = getChildDevice(it.deviceMac)
|
||||
if (isChild) {
|
||||
isChild.syncAppServerUrl(it.appServerUrl)
|
||||
}
|
||||
log.info "Device ${it.alias} added to devices array"
|
||||
}
|
||||
}
|
||||
|
||||
def addDevices() {
|
||||
def tpLinkModel = [:]
|
||||
// Plug-Switch Devices (no energy monitor capability)
|
||||
tpLinkModel << ["HS100" : "(Cloud) TP-Link Plug-Switch"] // HS100
|
||||
tpLinkModel << ["HS105" : "(Cloud) TP-Link Plug-Switch"] // HS105
|
||||
tpLinkModel << ["HS200" : "(Cloud) TP-Link Plug-Switch"] // HS200
|
||||
tpLinkModel << ["HS210" : "(Cloud) TP-Link Plug-Switch"] // HS210
|
||||
tpLinkModel << ["KP100" : "(Cloud) TP-Link Plug-Switch"] // KP100
|
||||
// Dimming Plug Devices
|
||||
tpLinkModel << ["HS220" : "(Cloud) TP-Link Dimming Switch"] // HS220
|
||||
// Energy Monitor Plugs
|
||||
tpLinkModel << ["HS110" : "(Cloud) TP-Link EnergyMonitor Plug"] // HS110
|
||||
tpLinkModel << ["HS115" : "(Cloud) TP-Link EnergyMonitor Plug"] // HS110
|
||||
// Soft White Bulbs
|
||||
tpLinkModel << ["KB100" : "(Cloud) TP-Link SoftWhite Bulb"] // KB100
|
||||
tpLinkModel << ["LB100" : "(Cloud) TP-Link SoftWhite Bulb"] // LB100
|
||||
tpLinkModel << ["LB110" : "(Cloud) TP-Link SoftWhite Bulb"] // LB110
|
||||
tpLinkModel << ["LB200" : "(Cloud) TP-Link SoftWhite Bulb"] // LB200
|
||||
// Tunable White Bulbs
|
||||
tpLinkModel << ["LB120" : "(Cloud) TP-Link TunableWhite Bulb"] // LB120
|
||||
// Color Bulbs
|
||||
tpLinkModel << ["KB130" : "(Cloud) TP-Link Color Bulb"] // KB130
|
||||
tpLinkModel << ["LB130" : "(Cloud) TP-Link Color Bulb"] // LB130
|
||||
tpLinkModel << ["LB230" : "(Cloud) TP-Link Color Bulb"] // LB230
|
||||
|
||||
def hub = location.hubs[0]
|
||||
def hubId = hub.id
|
||||
selectedDevices.each { dni ->
|
||||
def isChild = getChildDevice(dni)
|
||||
if (!isChild) {
|
||||
def device = state.devices.find { it.value.deviceMac == dni }
|
||||
def deviceModel = device.value.deviceModel.substring(0,5)
|
||||
addChildDevice(
|
||||
"davegut",
|
||||
tpLinkModel["${deviceModel}"],
|
||||
device.value.deviceMac,
|
||||
hubId, [
|
||||
"label": device.value.alias,
|
||||
"name": device.value.deviceModel,
|
||||
"data": [
|
||||
"deviceId" : device.value.deviceId,
|
||||
"appServerUrl": device.value.appServerUrl,
|
||||
]
|
||||
]
|
||||
)
|
||||
log.info "Installed TP-Link $deviceModel with alias ${device.value.alias}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- GET A NEW TOKEN FROM CLOUD -----
|
||||
def getToken() {
|
||||
def hub = location.hubs[0]
|
||||
def cmdBody = [
|
||||
method: "login",
|
||||
params: [
|
||||
appType: "Kasa_Android",
|
||||
cloudUserName: "${userName}",
|
||||
cloudPassword: "${userPassword}",
|
||||
terminalUUID: "${hub.id}"
|
||||
]
|
||||
]
|
||||
def getTokenParams = [
|
||||
uri: "https://wap.tplinkcloud.com",
|
||||
requestContentType: 'application/json',
|
||||
contentType: 'application/json',
|
||||
headers: ['Accept':'application/json; version=1, */*; q=0.01'],
|
||||
body : new groovy.json.JsonBuilder(cmdBody).toString()
|
||||
]
|
||||
httpPostJson(getTokenParams) {resp ->
|
||||
if (resp.status == 200 && resp.data.error_code == 0) {
|
||||
state.TpLinkToken = resp.data.result.token
|
||||
log.info "TpLinkToken updated to ${state.TpLinkToken}"
|
||||
sendEvent(name: "TokenUpdate", value: "tokenUpdate Successful.")
|
||||
if (state.currentError != null) {
|
||||
state.currentError = null
|
||||
}
|
||||
} else if (resp.status != 200) {
|
||||
state.currentError = resp.statusLine
|
||||
sendEvent(name: "currentError", value: resp.data)
|
||||
log.error "Error in getToken: ${state.currentError}"
|
||||
sendEvent(name: "TokenUpdate", value: state.currentError)
|
||||
} else if (resp.data.error_code != 0) {
|
||||
state.currentError = resp.data
|
||||
sendEvent(name: "currentError", value: resp.data)
|
||||
log.error "Error in getToken: ${state.currentError}"
|
||||
sendEvent(name: "TokenUpdate", value: state.currentError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- GET DEVICE DATA FROM THE CLOUD -----
|
||||
def getDeviceData() {
|
||||
def currentDevices = ""
|
||||
def cmdBody = [method: "getDeviceList"]
|
||||
def getDevicesParams = [
|
||||
uri: "https://wap.tplinkcloud.com?token=${state.TpLinkToken}",
|
||||
requestContentType: 'application/json',
|
||||
contentType: 'application/json',
|
||||
headers: ['Accept':'application/json; version=1, */*; q=0.01'],
|
||||
body : new groovy.json.JsonBuilder(cmdBody).toString()
|
||||
]
|
||||
httpPostJson(getDevicesParams) {resp ->
|
||||
if (resp.status == 200 && resp.data.error_code == 0) {
|
||||
currentDevices = resp.data.result.deviceList
|
||||
if (state.currentError != null) {
|
||||
state.currentError = null
|
||||
}
|
||||
return currentDevices
|
||||
} else if (resp.status != 200) {
|
||||
state.currentError = resp.statusLine
|
||||
sendEvent(name: "currentError", value: resp.data)
|
||||
log.error "Error in getDeviceData: ${state.currentError}"
|
||||
} else if (resp.data.error_code != 0) {
|
||||
state.currentError = resp.data
|
||||
sendEvent(name: "currentError", value: resp.data)
|
||||
log.error "Error in getDeviceData: ${state.currentError}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- SEND DEVICE COMMAND TO CLOUD FOR DH -----
|
||||
def sendDeviceCmd(appServerUrl, deviceId, command) {
|
||||
def cmdResponse = ""
|
||||
def cmdBody = [
|
||||
method: "passthrough",
|
||||
params: [
|
||||
deviceId: deviceId,
|
||||
requestData: "${command}"
|
||||
]
|
||||
]
|
||||
def sendCmdParams = [
|
||||
uri: "${appServerUrl}/?token=${state.TpLinkToken}",
|
||||
requestContentType: 'application/json',
|
||||
contentType: 'application/json',
|
||||
headers: ['Accept':'application/json; version=1, */*; q=0.01'],
|
||||
body : new groovy.json.JsonBuilder(cmdBody).toString()
|
||||
]
|
||||
httpPostJson(sendCmdParams) {resp ->
|
||||
if (resp.status == 200 && resp.data.error_code == 0) {
|
||||
def jsonSlurper = new groovy.json.JsonSlurper()
|
||||
cmdResponse = jsonSlurper.parseText(resp.data.result.responseData)
|
||||
if (state.errorCount != 0) {
|
||||
state.errorCount = 0
|
||||
}
|
||||
if (state.currentError != null) {
|
||||
state.currentError = null
|
||||
sendEvent(name: "currentError", value: null)
|
||||
log.debug "state.errorCount = ${state.errorCount} // state.currentError = ${state.currentError}"
|
||||
}
|
||||
// log.debug "state.errorCount = ${state.errorCount} // state.currentError = ${state.currentError}"
|
||||
} else if (resp.status != 200) {
|
||||
state.currentError = resp.statusLine
|
||||
cmdResponse = "ERROR: ${resp.statusLine}"
|
||||
sendEvent(name: "currentError", value: resp.data)
|
||||
log.error "Error in sendDeviceCmd: ${state.currentError}"
|
||||
} else if (resp.data.error_code != 0) {
|
||||
state.currentError = resp.data
|
||||
cmdResponse = "ERROR: ${resp.data.msg}"
|
||||
sendEvent(name: "currentError", value: resp.data)
|
||||
log.error "Error in sendDeviceCmd: ${state.currentError}"
|
||||
}
|
||||
}
|
||||
return cmdResponse
|
||||
}
|
||||
|
||||
// ----- INSTALL, UPDATE, INITIALIZE -----
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
runEvery5Minutes(checkError)
|
||||
schedule("0 30 2 ? * WED", getToken)
|
||||
if (selectedDevices) {
|
||||
addDevices()
|
||||
}
|
||||
}
|
||||
|
||||
// ----- PERIODIC CLOUD MX TASKS -----
|
||||
def checkError() {
|
||||
if (state.currentError == null || state.currentError == "none") {
|
||||
log.info "TP-Link Connect did not have any set errors."
|
||||
return
|
||||
}
|
||||
def errMsg = state.currentError.msg
|
||||
log.info "Attempting to solve error: ${errMsg}"
|
||||
state.errorCount = state.errorCount +1
|
||||
if (errMsg == "Token expired" && state.errorCount < 6) {
|
||||
sendEvent (name: "ErrHandling", value: "Handle comms error attempt ${state.errorCount}")
|
||||
getDevices()
|
||||
if (state.currentError == null) {
|
||||
log.info "getDevices successful. apiServerUrl updated and token is good."
|
||||
return
|
||||
}
|
||||
log.error "${errMsg} error while attempting getDevices. Will attempt getToken"
|
||||
getToken()
|
||||
if (state.currentError == null) {
|
||||
log.info "getToken successful. Token has been updated."
|
||||
getDevices()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.error "checkError: No auto-correctable errors or exceeded Token request count."
|
||||
}
|
||||
log.error "checkError residual: ${state.currentError}"
|
||||
}
|
||||
|
||||
// ----- CHILD CALLED TASKS -----
|
||||
def removeChildDevice(alias, deviceNetworkId) {
|
||||
try {
|
||||
deleteChildDevice(it.deviceNetworkId)
|
||||
sendEvent(name: "DeviceDelete", value: "${alias} deleted")
|
||||
} catch (Exception e) {
|
||||
sendEvent(name: "DeviceDelete", value: "Failed to delete ${alias}")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user