mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-19 21:03:46 +00:00
Compare commits
2 Commits
MSA-999-5
...
DVCSMP-399
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d39f5b1b6d | ||
|
|
4ec43463d8 |
@@ -41,6 +41,7 @@ metadata {
|
|||||||
attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
|
attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
|
||||||
attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
|
attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
|
||||||
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
|
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
|
||||||
|
attributeState "jammed", label:"jammed", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
|
||||||
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
|
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
|
||||||
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
|
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
@@ -130,7 +131,7 @@ def zwaveEvent(DoorLockOperationReport cmd) {
|
|||||||
if (cmd.doorLockMode == 0xFF) {
|
if (cmd.doorLockMode == 0xFF) {
|
||||||
map.value = "locked"
|
map.value = "locked"
|
||||||
} else if (cmd.doorLockMode >= 0x40) {
|
} else if (cmd.doorLockMode >= 0x40) {
|
||||||
map.value = "unknown"
|
map.value = "unknown" // XXX: Jammed?
|
||||||
} else if (cmd.doorLockMode & 1) {
|
} else if (cmd.doorLockMode & 1) {
|
||||||
map.value = "unlocked with timeout"
|
map.value = "unlocked with timeout"
|
||||||
} else {
|
} else {
|
||||||
@@ -180,7 +181,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
|||||||
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ]
|
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ]
|
||||||
break
|
break
|
||||||
case 0xB:
|
case 0xB:
|
||||||
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName is jammed" ]
|
map = [ name: "lock", value: "jammed", descriptionText: "$device.displayName is jammed", displayed: true, eventType: "ALERT" ]
|
||||||
break
|
break
|
||||||
case 0xC:
|
case 0xC:
|
||||||
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
||||||
@@ -266,7 +267,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
|||||||
case 17:
|
case 17:
|
||||||
case 23:
|
case 23:
|
||||||
case 26:
|
case 26:
|
||||||
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName bolt is jammed" ]
|
map = [ name: "lock", value: "jammed", descriptionText: "$device.displayName bolt is jammed", displayed: true, eventType: "ALERT" ]
|
||||||
break
|
break
|
||||||
case 13:
|
case 13:
|
||||||
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ]
|
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ]
|
||||||
|
|||||||
@@ -87,10 +87,13 @@ def authPage(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
|
log.debug "Updated with settings: ${settings}"
|
||||||
|
unsubscribe()
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,24 +106,72 @@ def uninstalled() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
unsubscribe()
|
atomicState.attached_sensors = [:]
|
||||||
if (plantlinksensors){
|
if (plantlinksensors){
|
||||||
|
subscribe(plantlinksensors, "moisture_status", moistureHandler)
|
||||||
|
subscribe(plantlinksensors, "battery_status", batteryHandler)
|
||||||
plantlinksensors.each{ sensor_device ->
|
plantlinksensors.each{ sensor_device ->
|
||||||
subscribe(sensor_device, "moisture_status", moistureHandler)
|
sensor_device.setStatusIcon("Waiting on First Measurement")
|
||||||
subscribe(sensor_device, "battery_status", batteryHandler)
|
|
||||||
sensor_device.setInstallSmartApp("connectedToSmartApp")
|
sensor_device.setInstallSmartApp("connectedToSmartApp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updatePlantNameIfNeeded(plant, expected_plant_name){
|
def dock_sensor(device_serial, expected_plant_name) {
|
||||||
|
def docking_body_json_builder = new JsonBuilder([version: '1c', smartthings_device_id: device_serial])
|
||||||
|
def docking_params = [
|
||||||
|
uri : appSettings.https_plantLinkServer,
|
||||||
|
path : "/api/v1/smartthings/links",
|
||||||
|
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||||
|
contentType: "application/json",
|
||||||
|
body: docking_body_json_builder.toString()
|
||||||
|
]
|
||||||
|
def plant_post_body_map = [
|
||||||
|
plant_type_key: 999999,
|
||||||
|
soil_type_key : 1000004
|
||||||
|
]
|
||||||
|
def plant_post_params = [
|
||||||
|
uri : appSettings.https_plantLinkServer,
|
||||||
|
path : "/api/v1/plants",
|
||||||
|
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||||
|
contentType: "application/json",
|
||||||
|
]
|
||||||
|
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
|
||||||
|
httpPost(docking_params) { docking_response ->
|
||||||
|
if (parse_api_response(docking_response, "Docking a link")) {
|
||||||
|
if (docking_response.data.plants.size() == 0) {
|
||||||
|
log.debug "creating plant for - ${expected_plant_name}"
|
||||||
|
plant_post_body_map["name"] = expected_plant_name
|
||||||
|
plant_post_body_map['links_key'] = [docking_response.data.key]
|
||||||
|
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
|
||||||
|
plant_post_params["body"] = plant_post_body_json_builder.toString()
|
||||||
|
httpPost(plant_post_params) { plant_post_response ->
|
||||||
|
if(parse_api_response(plant_post_response, 'creating plant')){
|
||||||
|
def attached_map = atomicState.attached_sensors
|
||||||
|
attached_map[device_serial] = plant_post_response.data
|
||||||
|
atomicState.attached_sensors = attached_map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
def plant = docking_response.data.plants[0]
|
||||||
|
def attached_map = atomicState.attached_sensors
|
||||||
|
attached_map[device_serial] = plant
|
||||||
|
atomicState.attached_sensors = attached_map
|
||||||
|
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
|
||||||
def plant_put_params = [
|
def plant_put_params = [
|
||||||
uri : appSettings.https_plantLinkServer,
|
uri : appSettings.https_plantLinkServer,
|
||||||
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||||
contentType : "application/json"
|
contentType : "application/json"
|
||||||
]
|
]
|
||||||
if (plant.name != expected_plant_name) {
|
if (plant.name != expected_plant_name) {
|
||||||
log.debug "renaming plant ${plant.key} - ${expected_plant_name}"
|
log.debug "updating plant for - ${expected_plant_name}"
|
||||||
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
|
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
|
||||||
def plant_put_body_map = [
|
def plant_put_body_map = [
|
||||||
name: expected_plant_name
|
name: expected_plant_name
|
||||||
@@ -134,39 +185,32 @@ def updatePlantNameIfNeeded(plant, expected_plant_name){
|
|||||||
}
|
}
|
||||||
|
|
||||||
def moistureHandler(event){
|
def moistureHandler(event){
|
||||||
log.debug "moistureHandler - ${event.value}"
|
def expected_plant_name = "SmartThings - ${event.displayName}"
|
||||||
|
|
||||||
def expected_plant_name = "${event.displayName} (ST)"
|
|
||||||
def device_serial = getDeviceSerialFromEvent(event)
|
def device_serial = getDeviceSerialFromEvent(event)
|
||||||
def device_battery = atomicState["battery${device_serial}"]
|
|
||||||
if ( device_battery == null){
|
if (!atomicState.attached_sensors.containsKey(device_serial)){
|
||||||
log.error "Missing Battery Voltage - next cycle should have it"
|
dock_sensor(device_serial, expected_plant_name)
|
||||||
}else{
|
}else{
|
||||||
// {"type":"link","signal":"0x00","zigbeedeviceid":"0022A3000003D75A","created":1458843686,"moisture":"0x1987"}
|
|
||||||
def appendedEventWithBatteryInfo = event.value.replace('}',",\"battery\":\"${device_battery}\"}")
|
|
||||||
log.debug "payload - ${appendedEventWithBatteryInfo}"
|
|
||||||
def measurement_post_params = [
|
def measurement_post_params = [
|
||||||
uri: appSettings.https_plantLinkServer,
|
uri: appSettings.https_plantLinkServer,
|
||||||
path: "/api/v1/smartthings/links/${device_serial}/measurements",
|
path: "/api/v1/smartthings/links/${device_serial}/measurements",
|
||||||
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
body: appendedEventWithBatteryInfo
|
body: event.value
|
||||||
]
|
]
|
||||||
|
|
||||||
httpPost(measurement_post_params) { measurement_post_response ->
|
httpPost(measurement_post_params) { measurement_post_response ->
|
||||||
if (parse_api_response(measurement_post_response, 'creating moisture measurement') && measurement_post_response.data.size() >0){
|
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
|
||||||
|
measurement_post_response.data.size() >0){
|
||||||
def measurement = measurement_post_response.data[0]
|
def measurement = measurement_post_response.data[0]
|
||||||
def plant = measurement.plant
|
def plant = measurement.plant
|
||||||
|
log.debug plant
|
||||||
updatePlantNameIfNeeded(plant, expected_plant_name)
|
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||||
|
|
||||||
plantlinksensors.each{ sensor_device ->
|
plantlinksensors.each{ sensor_device ->
|
||||||
if (sensor_device.id == event.deviceId){
|
if (sensor_device.id == event.deviceId){
|
||||||
sensor_device.setStatusIcon(plant.status)
|
sensor_device.setStatusIcon(plant.status)
|
||||||
if (plant.last_measurements && plant.last_measurements[0].moisture){
|
if (plant.last_measurements && plant.last_measurements[0].moisture){
|
||||||
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
|
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plant.last_measurements && plant.last_measurements[0].battery){
|
if (plant.last_measurements && plant.last_measurements[0].battery){
|
||||||
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
|
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
|
||||||
}
|
}
|
||||||
@@ -180,7 +224,21 @@ def moistureHandler(event){
|
|||||||
def batteryHandler(event){
|
def batteryHandler(event){
|
||||||
def expected_plant_name = "SmartThings - ${event.displayName}"
|
def expected_plant_name = "SmartThings - ${event.displayName}"
|
||||||
def device_serial = getDeviceSerialFromEvent(event)
|
def device_serial = getDeviceSerialFromEvent(event)
|
||||||
atomicState["battery${device_serial}"] = getDeviceBatteryFromEvent(event)
|
|
||||||
|
if (!atomicState.attached_sensors.containsKey(device_serial)){
|
||||||
|
dock_sensor(device_serial, expected_plant_name)
|
||||||
|
}else{
|
||||||
|
def measurement_post_params = [
|
||||||
|
uri: appSettings.https_plantLinkServer,
|
||||||
|
path: "/api/v1/smartthings/links/${device_serial}/measurements",
|
||||||
|
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||||
|
contentType: "application/json",
|
||||||
|
body: event.value
|
||||||
|
]
|
||||||
|
httpPost(measurement_post_params) { measurement_post_response ->
|
||||||
|
parse_api_response(measurement_post_response, 'creating battery measurement')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getDeviceSerialFromEvent(event){
|
def getDeviceSerialFromEvent(event){
|
||||||
@@ -189,12 +247,6 @@ def getDeviceSerialFromEvent(event){
|
|||||||
return match_result[0][1]
|
return match_result[0][1]
|
||||||
}
|
}
|
||||||
|
|
||||||
def getDeviceBatteryFromEvent(event){
|
|
||||||
def pattern = /.*"battery"\s*:\s*"(\w+)".*/
|
|
||||||
def match_result = (event.value =~ pattern)
|
|
||||||
return match_result[0][1]
|
|
||||||
}
|
|
||||||
|
|
||||||
def oauthInitUrl(){
|
def oauthInitUrl(){
|
||||||
atomicState.oauthInitState = UUID.randomUUID().toString()
|
atomicState.oauthInitState = UUID.randomUUID().toString()
|
||||||
def oauthParams = [
|
def oauthParams = [
|
||||||
@@ -211,13 +263,12 @@ def buildRedirectUrl(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
def swapToken(){
|
def swapToken(){
|
||||||
log.debug "PlantLink Connector - OAuth Token"
|
|
||||||
def code = params.code
|
def code = params.code
|
||||||
def oauthState = params.state
|
def oauthState = params.state
|
||||||
def stcid = appSettings.client_id
|
def stcid = appSettings.client_id
|
||||||
def postParams = [
|
def postParams = [
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
uri: appSettings.https_plantLinkServer,
|
uri: "https://oso-tech.appspot.com",
|
||||||
path: "/api/v1/oauth-token",
|
path: "/api/v1/oauth-token",
|
||||||
query: [grant_type:'authorization_code', code:params.code, client_id:stcid,
|
query: [grant_type:'authorization_code', code:params.code, client_id:stcid,
|
||||||
client_secret:appSettings.client_secret, redirect_uri: buildRedirectUrl()],
|
client_secret:appSettings.client_secret, redirect_uri: buildRedirectUrl()],
|
||||||
@@ -270,11 +321,10 @@ def swapToken(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
private refreshAuthToken() {
|
private refreshAuthToken() {
|
||||||
log.debug "PlantLink Connector - Refresh OAuth"
|
|
||||||
def stcid = appSettings.client_id
|
def stcid = appSettings.client_id
|
||||||
def refreshParams = [
|
def refreshParams = [
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
uri: appSettings.https_plantLinkServer,
|
uri: "https://hardware-dot-oso-tech.appspot.com",
|
||||||
path: "/api/v1/oauth-token",
|
path: "/api/v1/oauth-token",
|
||||||
query: [grant_type:'refresh_token', code:"${atomicState.refreshToken}", client_id:stcid,
|
query: [grant_type:'refresh_token', code:"${atomicState.refreshToken}", client_id:stcid,
|
||||||
client_secret:appSettings.client_secret],
|
client_secret:appSettings.client_secret],
|
||||||
@@ -283,6 +333,7 @@ private refreshAuthToken() {
|
|||||||
def jsonMap
|
def jsonMap
|
||||||
httpPost(refreshParams) { resp ->
|
httpPost(refreshParams) { resp ->
|
||||||
if(resp.status == 200){
|
if(resp.status == 200){
|
||||||
|
log.debug "OAuth Token refreshed"
|
||||||
jsonMap = resp.data
|
jsonMap = resp.data
|
||||||
if (resp.data) {
|
if (resp.data) {
|
||||||
atomicState.refreshToken = resp?.data?.refresh_token
|
atomicState.refreshToken = resp?.data?.refresh_token
|
||||||
@@ -295,12 +346,12 @@ private refreshAuthToken() {
|
|||||||
}
|
}
|
||||||
data.action = ""
|
data.action = ""
|
||||||
}else{
|
}else{
|
||||||
log.debug "PlantLink Server - ${resp.status} : ${resp.status.code}"
|
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e){
|
catch(Exception e){
|
||||||
log.debug "PlantLink Connector - OAuth Refresh Failed: " + e
|
log.debug "caught exception refreshing auth token: " + e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +364,7 @@ def parse_api_response(resp, message) {
|
|||||||
refreshAuthToken()
|
refreshAuthToken()
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
debugEvent("Plantlink Error: ${resp.status} - ${resp.status.code}", true)
|
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.", true)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ private sendDeveloperReq() {
|
|||||||
headers: [
|
headers: [
|
||||||
HOST: host
|
HOST: host
|
||||||
],
|
],
|
||||||
body: [devicetype: "$token-0"]], "${selectedHue}"))
|
body: [devicetype: "$token-0", username: "$token-0"]], "${selectedHue}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private discoverHueBulbs() {
|
private discoverHueBulbs() {
|
||||||
|
|||||||
Reference in New Issue
Block a user