Created
November 28, 2016 02:18
-
-
Save anonymous/8e21898568f959d2078d44afe20db6a0 to your computer and use it in GitHub Desktop.
Scout Alarm Integration for SmartThings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Scout Alarm Integration for SmartThings | |
*/ | |
import groovy.json.* | |
import groovy.time.* | |
definition( | |
name: "Scout Alarm", | |
namespace: "scoutalarm", | |
description: "Scout Alarm Client", | |
category: "Safety & Security", | |
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", | |
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/[email protected]", | |
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/[email protected]" | |
) | |
preferences { | |
page(name: "accountPage", title: "Enter your Scout account information", nextPage: "stModesSelection", uninstall: true) { | |
section { | |
input( | |
name: "email", | |
type: "email", | |
required: true, | |
title: "Email Address" | |
) | |
input( | |
name: "password", | |
type: "password", | |
required: true, | |
title: "Password" | |
) | |
} | |
} | |
page(name: "stModesSelection", title: "Select StartThings modes to run on", nextPage: "scoutLocationPage") { | |
section { | |
input( | |
name: "linkedStModes", | |
type: "enum", | |
title: "SmartThings modes to run on", | |
multiple: true, | |
required: true, | |
options: location.modes.name | |
) | |
} | |
} | |
page(name: "scoutLocationPage") | |
page(name: "preferencesPage", install: true) | |
} | |
def scoutLocationPage() { | |
dynamicPage(name: "scoutLocationPage", nextPage: "preferencesPage") { | |
section("Scout Location") { | |
log.debug("Running scoutLocationPage") | |
def scoutMemberId = getScoutMemberId() | |
def scoutLocations = getScoutLocations(scoutMemberId) | |
input( | |
name: "scoutLocationName", | |
type: "enum", | |
required: true, | |
title: "Scout Location:", | |
options: scoutLocations?.name | |
) | |
} | |
} | |
} | |
def preferencesPage() { | |
dynamicPage(name: "preferencesPage") { | |
section("Preferences") { | |
log.debug("Running preferencesPage") | |
def scoutLocationModes = getScoutLocationModes(getScoutLocationByName(settings.scoutLocationName)?.id) | |
log.debug("Selected Modes ${settings.linkedStModes}") | |
log.debug("Scout Modes ${scoutLocationModes.name}") | |
settings.linkedStModes.each {stMode -> | |
input( | |
name: "onSTMode_${stMode}", | |
type: "enum", | |
required: true, | |
title: "When ST mode ${stMode}, set:", | |
options: scoutLocationModes?.name | |
) | |
} | |
} | |
} | |
} | |
def installed() { | |
log.debug "Installed with settings: ${settings}" | |
initialize() | |
} | |
def updated() { | |
log.debug "Updated with settings: ${settings}" | |
unsubscribe() | |
initialize() | |
} | |
def initialize() { | |
atomicState.scoutMemberId = getScoutMemberId() | |
atomicState.scoutLocationId = getScoutLocationByName(settings.scoutLocationName)?.id | |
subscribe(location, "mode", modeChangeHandler) | |
} | |
def getApiBase() { return "https://api.scoutalarm.com/" } | |
def getDefaultHeaders() { | |
return [ | |
"User-Agent": "okhttp/3.2.0", | |
"Content-Type": "application/json" | |
] | |
} | |
def getDefaultAuthHeaders() { | |
def headers = getDefaultHeaders() | |
headers["Authorization"] = atomicState?.jwt | |
return headers | |
} | |
def getScoutMemberId() { | |
if (!isLoggedIn()) { | |
return login() | |
} | |
return atomicState.scoutMemberId | |
} | |
def getScoutModeForStModeName(stModeName) { | |
log.debug("Getting setting for onSTMode_${stModeName}: ${settings["onSTMode_${stModeName}"]}") | |
return settings["onSTMode_${stModeName}"] | |
} | |
def modeChangeHandler(evt) { | |
log.debug "mode changed to ${evt.value}" | |
if (isSTModeLinked(evt.value)) { | |
def scoutMode = getScoutModeForStModeName(evt.value) | |
if (scoutMode == 'Disarmed') { | |
disarm() | |
} else { | |
arm(scoutMode) | |
} | |
} | |
} | |
def disarm() { | |
log.debug("disarm: Disarming Scout") | |
def armedMode = getArmedMode(atomicState.scoutLocationId) | |
if (armedMode) { | |
def body = new groovy.json.JsonOutput().toJson([ | |
"state": "disarm", | |
]) | |
def params = [ | |
uri: getApiBase(), | |
path: "modes/${armedMode.id}", | |
headers: getDefaultAuthHeaders(), | |
body: body | |
] | |
try { | |
log.debug("disarm: Trying to disarm.") | |
httpPostJson(params) { resp -> | |
if (resp?.status == 200) { | |
log.debug("disarm: Successful") | |
} else { | |
log.error("disarm: 'Unexpected' Response: ${resp?.status}: ${resp?.data}") | |
} | |
} | |
} catch (ex) { | |
log.error("disarm Exception:", ex) | |
} | |
} else { | |
log.debug("arm: Scout is already disarmed") | |
} | |
} | |
def arm(scoutModeName) { | |
log.debug("arm: Arming Scout: ${scoutModeName}") | |
def armedMode = getArmedMode(atomicState.scoutLocationId) | |
def scoutModeToArm = getScoutModeByName(scoutModeName) | |
if (!armedMode) { | |
def body = new groovy.json.JsonOutput().toJson([ | |
"state": "arming", | |
]) | |
def params = [ | |
uri: getApiBase(), | |
path: "modes/${scoutModeToArm.id}", | |
headers: getDefaultAuthHeaders(), | |
body: body | |
] | |
try { | |
log.debug("arm: Trying to arm.") | |
httpPostJson(params) { resp -> | |
if (resp?.status == 200) { | |
log.debug("arm: Successful") | |
} else { | |
log.error("arm: 'Unexpected' Response: ${resp?.status}: ${resp?.data}") | |
} | |
} | |
} catch (ex) { | |
log.error("arm Exception:", ex) | |
} | |
} else { | |
log.debug("arm: Scout is already armed in ${armedMode.name}") | |
} | |
} | |
def login() { | |
def scoutMemberId = null; | |
def body = new groovy.json.JsonOutput().toJson([ | |
"email" : settings.email, | |
"password": settings.password | |
]) | |
def params = [ | |
uri: getApiBase(), | |
path: "auth", | |
headers: getDefaultHeaders(), | |
body: body | |
] | |
try { | |
log.debug("getJWT: Trying to login.") | |
httpPostJson(params) { resp -> | |
if (resp?.status == 200) { | |
log.debug("getJWT: Successful") | |
atomicState.jwt = resp.data.jwt | |
def jsonSlurper = new JsonSlurper() | |
def parsedJwt = resp.data.jwt.split(/\./)[1] | |
parsedJwt = new String(parsedJwt?.decodeBase64(), "UTF-8") | |
parsedJwt = jsonSlurper.parseText(parsedJwt) | |
scoutMemberId = parsedJwt?.id | |
atomicState.jwtExpireTime = parsedJwt?.exp | |
atomicState.scoutMemberId = scoutMemberId | |
} else { | |
log.error("getJWT: 'Unexpected' Response: ${resp?.status}: ${resp?.data}") | |
} | |
} | |
} catch (ex) { | |
log.error("getJWT Exception:", ex) | |
} | |
return scoutMemberId | |
} | |
def isLoggedIn() { | |
if (atomicState?.scoutMemberId == null || atomicState?.jwtExpireTime == null || atomicState?.jwtExpireTime <= (new Date().getTime() / 1000l)) { | |
return false | |
} | |
return true | |
} | |
def isSTModeLinked(stModeName) { | |
return settings.linkedStModes.find { stModeName } | |
} | |
def getScoutLocations(scoutMemberId) { | |
def scoutLocations = null | |
def params = [ | |
uri: getApiBase(), | |
path: "members/${scoutMemberId}/locations", | |
headers: getDefaultAuthHeaders(), | |
] | |
try { | |
httpGet(params) { resp -> | |
if (resp?.status == 200) { | |
log.debug("getLocations: Successful") | |
scoutLocations = resp.data | |
atomicState.scoutLocations = scoutLocations | |
} else { | |
log.warn("getLocations: 'Unexpected' Response: ${resp?.status}: ${resp?.data}") | |
} | |
} | |
} catch (groovyx.net.http.HttpResponseException ex) { | |
if (ex.getStatusCode() == 401) { | |
log.error("getLocations: Authentication failed") | |
} else { | |
log.error("getLocations: Exception ${ex.getStatusCode()}:", ex) | |
} | |
} | |
return scoutLocations | |
} | |
def getArmedMode(scoutLocationId) { | |
def scoutModes = getScoutLocationModes(scoutLocationId) | |
def armedMode = scoutModes.find { scoutMode -> scoutMode.state == 'armed' || scoutMode.state == 'arming' } | |
log.debug("Found armedMode: ${armedMode}") | |
return armedMode; | |
} | |
def getScoutModeByName(scoutModeName) { | |
return atomicState.scoutModes.find { scoutMode -> scoutMode.name == scoutModeName } | |
} | |
def getScoutLocationByName(scoutLocationName) { | |
return atomicState.scoutLocations.find { | |
name: scoutLocationName | |
} | |
} | |
def getScoutLocationModes(scoutLocationId) { | |
if (scoutLocationId == null) { | |
log.warn('getLocationModes: location id was null.') | |
return null | |
} | |
def scoutModes = null | |
def params = [ | |
uri: getApiBase(), | |
path: "locations/${scoutLocationId}/modes", | |
headers: getDefaultAuthHeaders(), | |
] | |
try { | |
httpGet(params) { resp -> | |
if (resp?.status == 200) { | |
log.debug("getLocationModes: Successful") | |
scoutModes = resp.data | |
scoutModes << [name: 'Disarmed'] | |
atomicState.scoutModes = scoutModes | |
} else { | |
log.warn("getLocationModes: 'Unexpected' Response: ${resp?.status}: ${resp?.data}") | |
} | |
} | |
} catch (groovyx.net.http.HttpResponseException ex) { | |
if (ex.getStatusCode() == 401) { | |
log.error("getLocationModes: Authentication failed") | |
} else { | |
log.error("getLocationModes: Exception ${ex.getStatusCode()}:", ex) | |
} | |
} | |
return scoutModes | |
} | |
0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment