Compare commits

..

8 Commits

Author SHA1 Message Date
Oso Technologies
5a29c8efd1 MSA-999: Plantlink is a sensor that monitors water moisture in a plant, to do this it take measurements and sends them to myplantlink.com where our servers calculate soil type, plant type, and past records to come up with a moisture value. Our servers also handle notification of users via email, sms, and push notifications to water their plant. This new updated version removes alot of the docking mechanism from the smart app (first time connecting a device and creating links/plants to upload to) and makes it just sends whatever values it has to our server and lets us automatically create what is needed. This fix will improve end users experience by auto correcting issues like a sensor sending up data to a plant that doesn't exist and creating multiple plants for one sensor. 2016-03-25 18:08:49 -05:00
Amol Mundayoor
e5841fb3cb Merge pull request #702 from larsfinander/DVCSMP-1667-Update-Hue-user-creation
DVCSMP-1667 Authentication needs to be updated for Philips Hue
2016-03-25 11:28:19 -07:00
Lars Finander
805b870447 DVCSMP-1667 Authentication needs to be updated for Philips Hue 2016-03-25 10:12:48 -07:00
Vinay Rao
10245315ee Merge pull request #697 from SmartThingsCommunity/staging
Rolling down staging commits to master
2016-03-24 13:22:33 -07:00
Vinay Rao
0b239d4686 Merge pull request #696 from SmartThingsCommunity/revert-668-staging_remove_hue
Revert "Revert "DVCSMP-1615 & DEVC-372""
2016-03-24 13:19:48 -07:00
Vinay Rao
9374290d64 Revert "Revert "DVCSMP-1615 & DEVC-372"" 2016-03-24 13:12:23 -07:00
Vinay Rao
c45129170a Merge pull request #668 from larsfinander/staging_remove_hue
Revert "DVCSMP-1615 & DEVC-372"
2016-03-24 12:56:35 -07:00
Lars Finander
633bef2ac5 Revert "DVCSMP-1615 & DEVC-372"
This reverts commit 6fbef3b297.
(temporary for staging, MArch 25 deploy)
2016-03-22 14:29:32 -07:00
3 changed files with 43 additions and 95 deletions

View File

@@ -41,7 +41,6 @@ metadata {
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 "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 "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
}
@@ -131,7 +130,7 @@ def zwaveEvent(DoorLockOperationReport cmd) {
if (cmd.doorLockMode == 0xFF) {
map.value = "locked"
} else if (cmd.doorLockMode >= 0x40) {
map.value = "unknown" // XXX: Jammed?
map.value = "unknown"
} else if (cmd.doorLockMode & 1) {
map.value = "unlocked with timeout"
} else {
@@ -181,7 +180,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ]
break
case 0xB:
map = [ name: "lock", value: "jammed", descriptionText: "$device.displayName is jammed", displayed: true, eventType: "ALERT" ]
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName is jammed" ]
break
case 0xC:
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
@@ -267,7 +266,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
case 17:
case 23:
case 26:
map = [ name: "lock", value: "jammed", descriptionText: "$device.displayName bolt is jammed", displayed: true, eventType: "ALERT" ]
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName bolt is jammed" ]
break
case 13:
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ]

View File

@@ -87,13 +87,10 @@ def authPage(){
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
@@ -106,72 +103,24 @@ def uninstalled() {
}
def initialize() {
atomicState.attached_sensors = [:]
unsubscribe()
if (plantlinksensors){
subscribe(plantlinksensors, "moisture_status", moistureHandler)
subscribe(plantlinksensors, "battery_status", batteryHandler)
plantlinksensors.each{ sensor_device ->
sensor_device.setStatusIcon("Waiting on First Measurement")
subscribe(sensor_device, "moisture_status", moistureHandler)
subscribe(sensor_device, "battery_status", batteryHandler)
sensor_device.setInstallSmartApp("connectedToSmartApp")
}
}
}
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 updatePlantNameIfNeeded(plant, expected_plant_name){
def plant_put_params = [
uri : appSettings.https_plantLinkServer,
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType : "application/json"
]
if (plant.name != expected_plant_name) {
log.debug "updating plant for - ${expected_plant_name}"
log.debug "renaming plant ${plant.key} - ${expected_plant_name}"
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
def plant_put_body_map = [
name: expected_plant_name
@@ -185,32 +134,39 @@ def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
}
def moistureHandler(event){
def expected_plant_name = "SmartThings - ${event.displayName}"
def device_serial = getDeviceSerialFromEvent(event)
log.debug "moistureHandler - ${event.value}"
if (!atomicState.attached_sensors.containsKey(device_serial)){
dock_sensor(device_serial, expected_plant_name)
def expected_plant_name = "${event.displayName} (ST)"
def device_serial = getDeviceSerialFromEvent(event)
def device_battery = atomicState["battery${device_serial}"]
if ( device_battery == null){
log.error "Missing Battery Voltage - next cycle should have it"
}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 = [
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
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: appendedEventWithBatteryInfo
]
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 plant = measurement.plant
log.debug plant
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
updatePlantNameIfNeeded(plant, expected_plant_name)
plantlinksensors.each{ sensor_device ->
if (sensor_device.id == event.deviceId){
sensor_device.setStatusIcon(plant.status)
if (plant.last_measurements && plant.last_measurements[0].moisture){
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
}
if (plant.last_measurements && plant.last_measurements[0].battery){
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
}
@@ -224,21 +180,7 @@ def moistureHandler(event){
def batteryHandler(event){
def expected_plant_name = "SmartThings - ${event.displayName}"
def device_serial = getDeviceSerialFromEvent(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')
}
}
atomicState["battery${device_serial}"] = getDeviceBatteryFromEvent(event)
}
def getDeviceSerialFromEvent(event){
@@ -247,6 +189,12 @@ def getDeviceSerialFromEvent(event){
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(){
atomicState.oauthInitState = UUID.randomUUID().toString()
def oauthParams = [
@@ -263,12 +211,13 @@ def buildRedirectUrl(){
}
def swapToken(){
log.debug "PlantLink Connector - OAuth Token"
def code = params.code
def oauthState = params.state
def stcid = appSettings.client_id
def postParams = [
method: 'POST',
uri: "https://oso-tech.appspot.com",
uri: appSettings.https_plantLinkServer,
path: "/api/v1/oauth-token",
query: [grant_type:'authorization_code', code:params.code, client_id:stcid,
client_secret:appSettings.client_secret, redirect_uri: buildRedirectUrl()],
@@ -321,10 +270,11 @@ def swapToken(){
}
private refreshAuthToken() {
log.debug "PlantLink Connector - Refresh OAuth"
def stcid = appSettings.client_id
def refreshParams = [
method: 'POST',
uri: "https://hardware-dot-oso-tech.appspot.com",
uri: appSettings.https_plantLinkServer,
path: "/api/v1/oauth-token",
query: [grant_type:'refresh_token', code:"${atomicState.refreshToken}", client_id:stcid,
client_secret:appSettings.client_secret],
@@ -333,7 +283,6 @@ private refreshAuthToken() {
def jsonMap
httpPost(refreshParams) { resp ->
if(resp.status == 200){
log.debug "OAuth Token refreshed"
jsonMap = resp.data
if (resp.data) {
atomicState.refreshToken = resp?.data?.refresh_token
@@ -346,12 +295,12 @@ private refreshAuthToken() {
}
data.action = ""
}else{
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
log.debug "PlantLink Server - ${resp.status} : ${resp.status.code}"
}
}
}
catch(Exception e){
log.debug "caught exception refreshing auth token: " + e
log.debug "PlantLink Connector - OAuth Refresh Failed: " + e
}
}
@@ -364,7 +313,7 @@ def parse_api_response(resp, message) {
refreshAuthToken()
return false
} else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.", true)
debugEvent("Plantlink Error: ${resp.status} - ${resp.status.code}", true)
return false
}
}

View File

@@ -161,7 +161,7 @@ private sendDeveloperReq() {
headers: [
HOST: host
],
body: [devicetype: "$token-0", username: "$token-0"]], "${selectedHue}"))
body: [devicetype: "$token-0"]], "${selectedHue}"))
}
private discoverHueBulbs() {