/**
* Copyright 2023 Bloodtick
*
* 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.
*
*/
public static String version() {return "1.3.1"}
metadata
{
definition(name: "Replica SmartThings Hub", namespace: "replica", author: "bloodtick", importUrl:"https://raw.githubusercontent.com/bloodtick/Hubitat/main/hubiThingsReplica/devices/replicaSmartThingsHub.groovy")
{
capability "Actuator"
capability "Configuration"
capability "Refresh"
attribute "mode", "string"
attribute "modes", "JSON_OBJECT"
attribute "latitude", "string"
attribute "longitude", "string"
attribute "countryCode", "string"
attribute "locationName", "string"
attribute "temperatureScale", "string"
attribute "timeZoneId", "string"
attribute "replica", "string"
attribute "oauthStatus", "enum", ["unknown", "authorized", "warning", "failure", "pending"]
attribute "healthStatus", "enum", ["offline", "online"]
command "deleteLocationMode", [[name: "modeName*", type: "STRING", description: "Delete mode name"]]
command "createLocationMode", [[name: "modeName*", type: "STRING", description: "Create mode name"]]
command "setLocationMode", [[name: "modeName*", type: "STRING", description: "Set mode string"]]
}
preferences {
input(name:"deviceModeHubitatFollows", type: "bool", title: "Hubitat Hub follows SmartThings Mode Updates:", defaultValue: false)
input(name:"deviceModeSmartThingsFollows", type: "bool", title: "SmartThings Hub follows Hubitat Mode Updates:", defaultValue: false)
input(name:"deviceInfoDisable", type: "bool", title: "Disable Info logging:", defaultValue: false)
input(name:"deviceDebugEnable", type: "bool", title: "Enable Debug logging:", defaultValue: false)
}
}
def installed() {
initialize()
setOauthStatusValue('unknown')
}
def updated() {
initialize()
}
def initialize() {
updateDataValue("triggers", groovy.json.JsonOutput.toJson(getReplicaTriggers()))
updateDataValue("commands", groovy.json.JsonOutput.toJson(getReplicaCommands()))
runEvery1Hour(refresh)
runIn(15, refresh) // replica needs to load 'description' information before we can startup
}
def configure() {
logInfo "${device.displayName} configured default rules"
initialize()
updateDataValue("rules", getReplicaRules())
sendCommand("configure")
}
// Methods documented here will show up in the Replica Command Configuration. These should be mostly setter in nature.
static Map getReplicaCommands() {
return ([ "setReplicaValue":[[name:"replica*",type:"STRING"]], "setModeValue":[[name:"mode*",type:"STRING"]], "setOauthStatusValue":[[name:"oauthStatus*",type:"ENUM"]], "setHealthStatusValue":[[name:"healthStatus*",type:"ENUM"]]])
}
def setModeValue(value) {
String mode = state?.modes?.find{ it?.id==value }?.label
setModeAttribute(mode)
}
def setReplicaValue(value) {
sendEvent(name: "replica", value: value, descriptionText: "${device.displayName} replica set to $value")
}
def setOauthStatusValue(value) {
sendEvent(name: "oauthStatus", value: value, descriptionText: "${device.displayName} oauthStatus set to $value")
}
def setHealthStatusValue(value) {
sendEvent(name: "healthStatus", value: value, descriptionText: "${device.displayName} healthStatus set to $value")
}
// Methods documented here will show up in the Replica Trigger Configuration. These should be all of the native capability commands
static Map getReplicaTriggers() {
return ([ "refresh":[]])
}
private def sendCommand(String name, def value=null, String unit=null, data=[:]) {
data.version=version()
parent?.deviceTriggerHandler(device, [name:name, value:value, unit:unit, data:data, now:now()])
}
void refresh() {
//sendCommand("refresh")
setReplicaValue( getParent()?.getLabel() )
getLocationInfo()
getLocationModes()
getLocationMode()
}
static String getReplicaRules() {
return """{"version":1,"components":[{"trigger":{"type":"attribute","properties":{"value":{"title":"HealthState","type":"string"}},"additionalProperties":false,"required":["value"],"capability":"healthCheck","attribute":"healthStatus","label":"attribute: healthStatus.*"},"command":{"name":"setHealthStatusValue","label":"command: setHealthStatusValue(healthStatus*)","type":"command","parameters":[{"name":"healthStatus*","type":"ENUM"}]},"type":"smartTrigger","mute":true}]}"""
}
import groovy.transform.CompileStatic
import groovy.transform.Field
@Field static final Integer iHttpSuccess=200
@Field static final Integer iHttpError=400
@Field static final String sURI="https://api.smartthings.com"
private String getAuthToken() {
return parent?.getAuthToken()
}
private String getLocationId() {
String locationId = null
try {
String description = getDataValue("description")
if(description) {
Map descriptionJson = new groovy.json.JsonSlurper().parseText(description)
locationId = descriptionJson?.locationId
}
} catch (e) {
logWarn "${device.displayName} getLocationId error: $e"
}
return locationId
}
def setModeAttribute(mode) {
sendEvent(name: "mode", value: mode, descriptionText: "${device.displayName} mode is $mode")
if(deviceModeHubitatFollows) getParent()?.setLocationMode(mode)
}
def setModesAttributes(modesMap) {
state.modes = modesMap?.items?.sort{ (it?.label?:it?.name) }.collect{ [id:it?.id, label:(it?.label?:it?.name)] }
List modes = modesMap?.items?.collect{ it?.label }.sort()
if(modes?.size()) sendEvent(name: "modes", value: modes, descriptionText: "${device.displayName} modes are $modes")
}
def setLocationAttributes(locationMap) {
sendEvent(name: "latitude", value: locationMap.latitude, unit: "°", descriptionText: "${device.displayName} latitude is $locationMap.latitude°")
sendEvent(name: "longitude", value: locationMap.longitude, unit: "°", descriptionText: "${device.displayName} latitude is $locationMap.longitude°")
sendEvent(name: "countryCode", value: locationMap.countryCode, descriptionText: "${device.displayName} country code is $locationMap.countryCode")
sendEvent(name: "locationName", value: locationMap.name, descriptionText: "${device.displayName} location name is $locationMap.name")
sendEvent(name: "temperatureScale", value: locationMap.temperatureScale, unit: "°", descriptionText: "${device.displayName} temperature scale is $locationMap.temperatureScale°")
sendEvent(name: "timeZoneId", value: locationMap.timeZoneId, descriptionText: "${device.displayName} time zone ID is $locationMap.timeZoneId")
}
Map setLocationMode(String modeName, event=false) {
logDebug "${device.displayName} executing 'setLocationMode($modeName)'"
Map response = [statusCode:iHttpError]
getLocationModes()
String modeId = state?.modes?.find{ it?.label?.toLowerCase()==modeName?.toLowerCase() }?.id
if(!modeId || (event && !deviceModeSmartThingsFollows)) return response
Map params = [
uri: sURI,
path: "/locations/${getLocationId()}/modes/current",
body: groovy.json.JsonOutput.toJson([modeId:modeId]),
contentType: "application/json",
requestContentType: "application/json",
headers: [ Authorization: "Bearer ${getAuthToken()}" ]
]
try {
httpPut(params) { resp ->
logDebug "response data: ${resp.data}"
response.data = resp.data
response.statusCode = resp.status
logInfo "${device.displayName} set SmartThings mode to '${resp.data.label}'"
}
} catch (e) {
logWarn "${device.displayName} has setLocationMode('$modeName' : '$modeId') error: $e"
}
return response
}
Map getLocationMode() {
logDebug "${device.displayName} executing 'getLocationMode()'"
Map response = [statusCode:iHttpError]
Map params = [
uri: sURI,
path: "/locations/${getLocationId()}/modes/current",
headers: [ Authorization: "Bearer ${getAuthToken()}" ]
]
try {
httpGet(params) { resp ->
logDebug "response data: ${resp.data}"
response.data = resp.data
response.statusCode = resp.status
setModeAttribute(resp.data?.label)
}
} catch (e) {
logWarn "${device.displayName} has getLocationMode() error: $e"
}
return response
}
Map createLocationMode(String modeName) {
logDebug "${device.displayName} executing 'createLocationMode()'"
Map response = [statusCode:iHttpError]
Map params = [
uri: sURI,
body: groovy.json.JsonOutput.toJson([label:modeName,name:modeName]),
path: "/locations/${getLocationId()}/modes",
contentType: "application/json",
requestContentType: "application/json",
headers: [ Authorization: "Bearer ${getAuthToken()}" ]
]
try {
httpPost(params) { resp ->
logDebug "response data: ${resp.data}"
response.data = resp.data
response.statusCode = resp.status
logInfo "${device.displayName} created SmartThings mode '${resp.data.label}'"
getLocationModes()
}
} catch (e) {
logWarn "${device.displayName} has createLocationMode() error: $e"
}
return response
}
Map deleteLocationMode(String modeName) {
logDebug "${device.displayName} executing 'deleteLocationMode()'"
Map response = [statusCode:iHttpError]
String modeId = state?.modes?.find{ it?.label?.toLowerCase()==modeName?.toLowerCase() }?.id
if(!modeId) return response
Map params = [
uri: sURI,
path: "/locations/${getLocationId()}/modes/$modeId",
headers: [ Authorization: "Bearer ${getAuthToken()}" ]
]
try {
httpDelete(params) { resp ->
logDebug "response data: ${resp.data}"
response.data = resp.data
response.statusCode = resp.status
logInfo "${device.displayName} deleted SmartThings mode '$modeName'"
getLocationModes()
}
} catch (e) {
logWarn "${device.displayName} has deleteLocationMode() error: $e"
}
return response
}
Map getLocationModes() {
logDebug "${device.displayName} executing 'getLocationModes()'"
Map response = [statusCode:iHttpError]
Map params = [
uri: sURI,
path: "/locations/${getLocationId()}/modes",
headers: [ Authorization: "Bearer ${getAuthToken()}" ]
]
try {
httpGet(params) { resp ->
logDebug "response data: ${resp.data}"
response.data = resp.data
response.statusCode = resp.status
setModesAttributes(resp.data)
}
} catch (e) {
logWarn "${device.displayName} has getLocationModes() error: $e"
}
return response
}
Map getLocationInfo() {
logDebug "${device.displayName} executing 'getLocationInfo()'"
Map response = [statusCode:iHttpError]
Map params = [
uri: sURI,
path: "/locations/${getLocationId()}",
headers: [ Authorization: "Bearer ${getAuthToken()}" ]
]
try {
httpGet(params) { resp ->
logDebug "response data: ${resp.data}"
response.data = resp.data
response.statusCode = resp.status
setLocationAttributes(resp.data)
}
} catch (e) {
logWarn "${device.displayName} has getLocationInfo() error: $e"
}
return response
}
private logInfo(msg) { if(settings?.deviceInfoDisable != true) { log.info "${msg}" } }
private logDebug(msg) { if(settings?.deviceDebugEnable == true) { log.debug "${msg}" } }
private logTrace(msg) { if(settings?.deviceTraceEnable == true) { log.trace "${msg}" } }
private logWarn(msg) { log.warn "${msg}" }
private logError(msg) { log.error "${msg}" }