SimpliSafe Alarm Integration (cloud to cloud)

Thank you all so much for working on it!! All of use who use this integration really appreciate it!!!

So this version definitely doesn’t work with SS2. It breaks down with the following calls: alarmOff, alarmHome, alarmOff, and refresh. Looks like the url for those have SS3 in them. I am trying to trace the calls for my system, and they look different. Will post back when I have something.

1 Like

Ok, I can set my SS2 to Off, Home or Away. I had to change the following:

else if (urlType == "alarmOff" )
{
	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/off"
    return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=off"
}
else if (urlType == "alarmHome" )
{
	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/home"
    return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=home"
}
else if (urlType == "alarmAway" )
{
	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/away"
    return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=away"
}

Still working on trying to get the current state.

1 Like

I used your code and it works for one time only because the status won’t update after I disarm it, I can’t execute arming again. Thanks for figuring this part out though.

So it looks like with the online portal change, a re-write is necessary. I’ve also found that depending on your version (v1-2 or v3) that the URLs are different. While I won’t have time to fix everything this weekend (wife’s birthday and we’re going out of town) I will commit some time to it Monday to try and restore functionally. Sorry for the delay, know how much everyone depends on this intergration.

-Toby

6 Likes

THANK YOU, Toby!

I am getting closer to something basic working, but I am getting a lot of timeouts and errors.

Thank you guys for the effort in getting this resolved! Looking forward to having alarm functionality restored.

Can you modify it so that it does not have the pending period checking for status and just assumes the command worked?
This way, the arming/disarming functions can be force triggered. Thanks.

I am pasting here what I have thus far. This in no way is a guaranteed working solution. I have seen it lose connection and not want to connect again, so no promises it will work. I have a lot of extra logging commands in there.

preferences {
	input(name: "username", type: "text", title: "Username", required: "true", description: "SimpliSafe Username")
	input(name: "password", type: "password", title: "Password", required: "true", description: "SimpliSafe Password")
}

metadata {	
	definition (name: "SimpliSafe 3", namespace: "SimpliSafe3", author: "Multiple Authors") {
		capability "Alarm"
		command "home"
		command "away"
		command "off"
		command "update_state"
	}

tiles(scale: 2) {
multiAttributeTile(name:"alarm", type: "generic", width: 6, height: 4){
    tileAttribute ("device.alarm", key: "PRIMARY_CONTROL") {
        attributeState "off", label:'${name}', icon: "st.security.alarm.off", backgroundColor: "#594531"
        attributeState "home", label:'${name}', icon: "st.Home.home4", backgroundColor: "#00BEAC"
        attributeState "away", label:'${name}', icon: "st.security.alarm.on", backgroundColor: "#008CC1"
			attributeState "pending off", label:'${name}', icon: "st.security.alarm.off", backgroundColor: "#ffffff"
			attributeState "pending away", label:'${name}', icon: "st.Home.home4", backgroundColor: "#ffffff"
			attributeState "pending home", label:'${name}', icon: "st.security.alarm.on", backgroundColor: "#ffffff"
			attributeState "failed set", label:'error', icon: "st.secondary.refresh", backgroundColor: "#d44556"
    }
		
		tileAttribute("device.events", key: "SECONDARY_CONTROL", wordWrap: true) {
			attributeState("default", label:'${currentValue}')
		}
}	
	
standardTile("off", "device.alarm", width: 2, height: 2, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
    state ("off", label:"off", action:"off", icon: "st.security.alarm.off", backgroundColor: "#594531", nextState: "pending")
    state ("away", label:"off", action:"off", icon: "st.security.alarm.off", backgroundColor: "#505050", nextState: "pending")
    state ("home", label:"off", action:"off", icon: "st.security.alarm.off", backgroundColor: "#505050", nextState: "pending")
    state ("pending", label:"pending", icon: "st.security.alarm.off", backgroundColor: "#ffffff")
	}
	
standardTile("away", "device.alarm", width: 2, height: 2, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
    state ("off", label:"away", action:"away", icon: "st.security.alarm.on", backgroundColor: "#505050", nextState: "pending") 
		state ("away", label:"away", action:"away", icon: "st.security.alarm.on", backgroundColor: "#008CC1", nextState: "pending")
    state ("home", label:"away", action:"away", icon: "st.security.alarm.on", backgroundColor: "#505050", nextState: "pending")
		state ("pending", label:"pending", icon: "st.security.alarm.on", backgroundColor: "#ffffff")
	}
	
standardTile("home", "device.alarm", width: 2, height: 2, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
    state ("off", label:"home", action:"home", icon: "st.Home.home4", backgroundColor: "#505050", nextState: "pending")
    state ("away", label:"home", action:"home", icon: "st.Home.home4", backgroundColor: "#505050", nextState: "pending")
		state ("home", label:"home", action:"home", icon: "st.Home.home4", backgroundColor: "#00BEAC", nextState: "pending")
		state ("pending", label:"pending", icon: "st.Home.home4", backgroundColor: "#ffffff")
	}
	standardTile("refresh", "device.alarm", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
		state "default", action:"update_state", icon:"st.secondary.refresh"
	}

		main(["alarm"])
		details(["alarm","off", "away", "home", "refresh"])
	}
}

def installed() {
  init()
}

def updated() {
  unschedule()
  init()
}
  
def init() {
	//runEvery1Minutes(poll)
}

// handle commands
def off() {
	log.info "Setting SimpliSafe mode to 'Off'"
	setState ('off')
poll()	
}

def home() { 
	log.info "Setting SimpliSafe mode to 'Home'"
	setState ('home')
poll()	
}

def away() {
	log.info "Setting SimpliSafe mode to 'Away'"
	setState ('away')
poll()
}

def update_state() {
	log.info "Refreshing SimpliSafe state..."
	poll()
}

def setState (alState){
//if (now() > state.tokenExpiry)
//{
	apiLogin()
//}   
if (alState == "off")
{
	try {
    	httpPost([ uri: getAPIUrl("alarmOff"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8" ])
    } catch (e) {
    	log.debug "Alarm SET to OFF Error: $e"
    }
}
else if (alState == "home")
{
	log.info "66666"
    try {
    	httpPost([ uri: getAPIUrl("alarmHome"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8" ])
    } catch (e) {
    	log.debug "Alarm SET to HOME Error: $e"
    }
    log.info "77777"
}
else if (alState == "away")
{
	try {
    	httpPost([ uri: getAPIUrl("alarmAway"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8" ])
    } catch (e) {
    	log.debug "Alarm SET to AWAY Error: $e"
    }
}
else
{
    log.info "Invalid state requested."
}
}

def poll() {
log.info "Executing polling..."
//if (now() > state.tokenExpiry)
//{
	apiLogin()
//}    
log.info "55555"
	httpGet ([uri: getAPIUrl("refresh"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
    sendEvent(name: "alarm", value: response.data.subscription.location.system.alarmState)
    log.info "Alarm State1: $response.data.subscription.location.system.alarmState"
}
//log.info "Alarm State2: $response"
//apiLogout()
}

def apiLogin() {
	log.info "Getting authrorization token..."
//get token
def authBody = [ "grant_type":"password",
                "username":settings.username,
                "password": settings.password ]
def authHeader = [ "Authorization":"Basic NGRmNTU2MjctNDZiMi00ZTJjLTg2NmItMTUyMWIzOTVkZWQyLjEtMC0wLldlYkFwcC5zaW1wbGlzYWZlLmNvbTo="	]
log.info "11111"
httpPost([ uri: getAPIUrl("initAuth"), headers: authHeader, contentType: "application/json; charset=utf-8", body: authBody ]) { response ->
	log.info "22222"
    state.token = response.data.access_token
    state.tokenType = response.data.token_type
    state.respAuthHeader = ["Authorization":state.tokenType + " " + state.token]
    state.tokenExpiry = now() + 3600000
}

//if (!state.uid)
   	//{
	log.info "33333"
    getUserId()
   	//}
//if (!state.subscriptionId)
//{
	log.info "44444"
    getSubscriptionId()
//}
}

def getUserId() {
	//check auth and get uid
httpGet ([uri: getAPIUrl("authCheck"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
    state.uid = response.data.userId
}
log.info "User ID: $state.uid"
}

def getSubscriptionId() {
	//get subscription id
httpGet ([uri: getAPIUrl("subId"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
	String tsid = response.data.subscriptions.location.sid
		state.subscriptionId = tsid.substring(1, tsid.length() - 1)
}
log.info "Subscription ID: $state.subscriptionId"
}

def checkAuth()
{
	//check auth and return status
httpGet ([uri: getAPIUrl("authCheck"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
    return response.status
}
}

def apiLogout() {
httpDelete([ uri: getAPIUrl("initAuth"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8" ]) { response ->
    if (response.status == 200) {
        state.subscriptionId = null
        log.info "Logged out from API."
    }
}
}

def getTime()
{
	def tDate = new Date()
return tDate.getTime()
}

def getAPIUrl(urlType) {
	if (urlType == "initAuth")
    {
    	return "https://api.simplisafe.com/v1/api/token"
    }
    else if (urlType == "authCheck")
    {
    	return "https://api.simplisafe.com/v1/api/authCheck"
    }
    else if (urlType == "subId" )
    {
    	return "https://api.simplisafe.com/v1/users/$state.uid/subscriptions?activeOnly=false"
    }
    else if (urlType == "alarmOff" )
    {
    	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/off"
        return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=off"
    }
    else if (urlType == "alarmHome" )
    {
    	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/home"
        return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=home"
    }
    else if (urlType == "alarmAway" )
    {
    	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/away"
        return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=away"
    }
    else if (urlType == "refresh")
    {
    	return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/"
    }
    else
    {
    	log.info "Invalid URL type"
    }
}

I am going to add notes as I learn things that way I have them collected all in one place :grin:

All of the below applies to SS2.

  • When tracing the login on the new Simplisafe Website, for each call they do (token, authCheck, subscriptions, events, etc
). They always do HTTP OPTIONS before doing a POST or GET
  • Each time a page is refreshed or new page called, they call authCheck. I am guessing they are checking to see if that passes or not before throwing the user back to the login page if it fails.
  • With SS2 there are 2 ways to read the STATE data (off, home, away) that I have found.
  1. The first way is to call https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state. The problem with this method is that it actually goes out and polls your system (cellular) and has a tendency to timeout in smartthings (exceeds 10 second limit).
  2. The second way is call https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/. This gives you a ton of information regarding the system and is super quick. Then you can drill down into the data by doing: response.data.subscription.location.system.alarmState. This will give you the last state of the alarm that was sent to Simplisafe. I think this will be the preferred method.
  • When calling the token as specified in my code I posted (with grant_type, password, and username) you get back access_token, refresh_token, expires_in, and token_type. Not sure what refresh_token is. I looked at how the website is doing the token, and they add another json value to the list above that is called device_id. It is set to a long string variable, but for our case if we just put in there “WebApp” we no longer get a refresh_token, which matches the website. This could be the cause of my disconnection issue if I tried to use a standalone test app to connect to simplisafe and through smartthings at the same time, one of them loses access. Still testing this, but this looks promising.

I am thinking that the app needs to always call a CheckAuth before doing anything. If that fails, a new login is necessary. if it passes, then continue to do normal calls. Need to add a lot of exception handling (try/catch) to be robust.

I have family in town this weeked, so probably can’t do much with it until they are gone.

3 Likes

I got timeouts on the v3 device today. It turns out the timeout occurs only when the device is on the cellular connection. I had some issue with my 2GHz wifi that the v3 device was using and it failed over to cellular. I was getting timeouts on the simplisafe app/web too while changing the alarm state or refreshing device state. It would refresh once but if I try again it would timeout. I don’t see timeouts after I restored the wifi.

Anyone else getting timeouts on the simplisafe app/web when changing state or refreshing the device or system status?

I have a v2 system and I gave this a try. The tile is very thin, but the functionality seems to be restored!!! It worked and synced between the SimpliSafe and SHM. This is really great work and very appreciated!!

I’ve done a bit more testing and it seems that if I trigger the SimpliSafe alarm with my app/keypad, then the proper detail is highlighted in the device of the SmartThings app. If I allow the SMH to manage things, then the “off” indicator" is the only one highlighted.

I am still working and testing on cleaning things up. Don’t have to much time this weekend with family being here this weekend.

No worries. I was just posting feedback as it occurs so there’s some kind of user testing log. It figures there would be this kind of key change on a weekend where you guys are gone :slight_smile:

It’s definitely going to take some time to do the rewrite. I’m new to this so I’m just plugging along trying to reverse-engineer what you guys have thought out.

NOTE: The below code is for SS2 only.

Here is what I have thus far. I have been using this since yesterday, and it has been working well.It is setup to poll the alarm everything 5 minutes. It will keep an open connection to your alarm, and only relogin if the time has expired. I did run into some issues with using the old code as a base in that the global values for State somehow were sticking around. I had to go in and delete the Device (not this code which is the Device Handler) and re-add the device again (which meant logging in again). If you run into issues like that, please follow the instructions at the following link starting at step 11 and going through step 15. Unfortunately I don’t know why this is happening as I don’t have a good enough background and knowledge of how SmartThings handles some things.

Here is the code (it still has a lot of work that needs to be done, but this seems to be stable). I used Toby’s code as a base. I have some unused functions that are incomplete.

/**
 *  SimpliSafe integration for SmartThings
 *
 *  Copyright 2015 Felix Gorodishter
 *	Modifications by Toby Harris - 6/13/2017
 *
 *  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.
 *
 */

preferences {
	input(name: "username", type: "text", title: "Username", required: "true", description: "SimpliSafe Username")
	input(name: "password", type: "password", title: "Password", required: "true", description: "SimpliSafe Password")
}

metadata {	
	definition (name: "SimpliSafe2", namespace: "tobycth3", author: "Toby Harris") {
		capability "Alarm"
		command "home"
		command "away"
		command "off"
		command "update_state"
	}

tiles(scale: 2) {
    multiAttributeTile(name:"alarm", type: "generic", width: 6, height: 4){
        tileAttribute ("device.alarm", key: "PRIMARY_CONTROL") {
            attributeState "off", label:'${name}', icon: "st.security.alarm.off", backgroundColor: "#594531"
            attributeState "home", label:'${name}', icon: "st.Home.home4", backgroundColor: "#00BEAC"
            attributeState "away", label:'${name}', icon: "st.security.alarm.on", backgroundColor: "#008CC1"
			attributeState "pending off", label:'${name}', icon: "st.security.alarm.off", backgroundColor: "#ffffff"
			attributeState "pending away", label:'${name}', icon: "st.Home.home4", backgroundColor: "#ffffff"
			attributeState "pending home", label:'${name}', icon: "st.security.alarm.on", backgroundColor: "#ffffff"
			attributeState "failed set", label:'error', icon: "st.secondary.refresh", backgroundColor: "#d44556"
        }
		
		tileAttribute("device.events", key: "SECONDARY_CONTROL", wordWrap: true) {
			attributeState("default", label:'${currentValue}')
		}
    }	
	
    standardTile("off", "device.alarm", width: 2, height: 2, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
        state ("off", label:"off", action:"off", icon: "st.security.alarm.off", backgroundColor: "#594531", nextState: "pending")
        state ("away", label:"off", action:"off", icon: "st.security.alarm.off", backgroundColor: "#505050", nextState: "pending")
        state ("home", label:"off", action:"off", icon: "st.security.alarm.off", backgroundColor: "#505050", nextState: "pending")
        state ("pending", label:"pending", icon: "st.security.alarm.off", backgroundColor: "#ffffff")
	}
	
    standardTile("away", "device.alarm", width: 2, height: 2, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
        state ("off", label:"away", action:"away", icon: "st.security.alarm.on", backgroundColor: "#505050", nextState: "pending") 
		state ("away", label:"away", action:"away", icon: "st.security.alarm.on", backgroundColor: "#008CC1", nextState: "pending")
        state ("home", label:"away", action:"away", icon: "st.security.alarm.on", backgroundColor: "#505050", nextState: "pending")
		state ("pending", label:"pending", icon: "st.security.alarm.on", backgroundColor: "#ffffff")
	}
	
    standardTile("home", "device.alarm", width: 2, height: 2, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
        state ("off", label:"home", action:"home", icon: "st.Home.home4", backgroundColor: "#505050", nextState: "pending")
        state ("away", label:"home", action:"home", icon: "st.Home.home4", backgroundColor: "#505050", nextState: "pending")
		state ("home", label:"home", action:"home", icon: "st.Home.home4", backgroundColor: "#00BEAC", nextState: "pending")
		state ("pending", label:"pending", icon: "st.Home.home4", backgroundColor: "#ffffff")
	}
	standardTile("refresh", "device.alarm", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
		state "default", action:"update_state", icon:"st.secondary.refresh"
	}

		main(["alarm"])
		details(["alarm","off", "away", "home", "refresh"])
	}
}

def installed() {
  init()
}

def updated() {
  unschedule()
  init()
}
  
def init() {
	log.info "Setting up Schedule (every 5 minutes)..."
	runEvery5Minutes(poll)
}

// handle commands
def off() {
	log.info "Setting SimpliSafe mode to 'Off'"
	setState ('off')
}

def home() { 
	log.info "Setting SimpliSafe mode to 'Home'"
	setState ('home')
}

def away() {
	log.info "Setting SimpliSafe mode to 'Away'"
	setState ('away')
}

def update_state() {
	log.info "Refreshing SimpliSafe state..."
	poll()
}

def setState (alState){
	//Check Auth first
	checkAuth()
    def timeout = false;
    
    if (alState == "off")
    {
    	try {
        	httpPost([ uri: getAPIUrl("alarmOff"), headers: state.auth.respAuthHeader, contentType: "application/json; charset=utf-8" ])
        } catch (e) {
        	timeout = true;
        	log.debug "Alarm SET to OFF Error: $e"
        }
    }
    else if (alState == "home")
    {
    	try {
        	httpPost([ uri: getAPIUrl("alarmHome"), headers: state.auth.respAuthHeader, contentType: "application/json; charset=utf-8" ])
        } catch (e) {
        	timeout = true;
        	log.debug "Alarm SET to HOME Error: $e"
        }
    }
    else if (alState == "away")
    {
    	try {
        	httpPost([ uri: getAPIUrl("alarmAway"), headers: state.auth.respAuthHeader, contentType: "application/json; charset=utf-8" ])
        } catch (e) {
        	timeout = true;
        	log.debug "Alarm SET to AWAY Error: $e"
        }
    }
    else
    {
        log.info "Invalid state requested."
    }
    
    //If not a timeout, we can poll immediately, otherwise wait 10 seconds
    if (!timeout) {
    	poll()
    } else {
    	//There was a timeout, so we can't poll right away. Wait 10 seconds and try polling.
    	runIn(10, poll)
    }
}

def poll() {
	//Check Auth first
	checkAuth()

    log.info "Executing polling..."
   
	httpGet ([uri: getAPIUrl("refresh"), headers: state.auth.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
        sendEvent(name: "alarm", value: response.data.subscription.location.system.alarmState)
        log.info "Alarm State1: $response.data.subscription.location.system.alarmState"
    }
    //log.info "Alarm State2: $response"
    //apiLogout()
}

def apiLogin() {
	//Login to the system
    log.info "Executing Login..."
   
   	//Define the login Auth Body and Header Information
    def authBody = [ "grant_type":"password",
    				"device_id":"WebApp",
                    "username":settings.username,
                    "password": settings.password ]                    
    def authHeader = [ "Authorization":"Basic NGRmNTU2MjctNDZiMi00ZTJjLTg2NmItMTUyMWIzOTVkZWQyLjEtMC0wLldlYkFwcC5zaW1wbGlzYWZlLmNvbTo="	]
    
    try {
        httpPost([ uri: getAPIUrl("initAuth"), headers: authHeader, contentType: "application/json; charset=utf-8", body: authBody ]) { response ->
        	state.auth = response.data
            state.auth.respAuthHeader = ["Authorization":state.auth.token_type + " " + state.auth.access_token]
            state.auth.tokenExpiry = now() + 3600000
        }
 	} catch (e) {
    	//state.token = 
    }
    
    //Check for valid UID, and if not get it
    if (!state.uid)
   	{
    	getUserId()
   	}
    
    //Check for valid Subscription ID, and if not get it
    //Might be able to expand this to multiple systems
    if (!state.subscriptionId)
    {
    	getSubscriptionId()
    }
}

def getUserId() {
	//check auth and get uid    
    httpGet ([uri: getAPIUrl("authCheck"), headers: state.auth.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
        state.uid = response.data.userId
    }
    log.info "User ID: $state.uid"
}

def getSubscriptionId() {
	//get subscription id
    httpGet ([uri: getAPIUrl("subId"), headers: state.auth.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
    	String tsid = response.data.subscriptions.location.sid
		state.subscriptionId = tsid.substring(1, tsid.length() - 1)
    }
    log.info "Subscription ID: $state.subscriptionId"
}

def checkAuth()
{
	log.info "Checking to see if time has expired...."
        
    //If no State Auth, or now Token Expiry, or time has expired, need to relogin
    //log.info "Expiry time: $state.auth.tokenExpiry"
    if (!state.auth || !state.auth.tokenExpiry || now() > state.auth.tokenExpiry) {    
    	log.info"Token Time has expired, excecuting re-login..."
        apiLogin()
    }
    
	//Check Auth
    httpGet ([uri: getAPIUrl("authCheck"), headers: state.auth.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
        return response.status        
    }
}

def apiLogout() {
    httpDelete([ uri: getAPIUrl("initAuth"), headers: state.auth.respAuthHeader, contentType: "application/json; charset=utf-8" ]) { response ->
        if (response.status == 200) {
            state.subscriptionId = null
            log.info "Logged out from API."
        }
    }
}

def getTime()
{
	def tDate = new Date()
    return tDate.getTime()
}

def getAPIUrl(urlType) {
	if (urlType == "initAuth")
    {
    	return "https://api.simplisafe.com/v1/api/token"
    }
    else if (urlType == "authCheck")
    {
    	return "https://api.simplisafe.com/v1/api/authCheck"
    }
    else if (urlType == "subId" )
    {
    	return "https://api.simplisafe.com/v1/users/$state.uid/subscriptions?activeOnly=false"
    }
    else if (urlType == "alarmOff" )
    {
    	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/off"
        return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=off"
    }
    else if (urlType == "alarmHome" )
    {
    	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/home"
        return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=home"
    }
    else if (urlType == "alarmAway" )
    {
    	//return "https://api.simplisafe.com/v1/ss2/subscriptions/$state.subscriptionId/state/away"
        return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/state?state=away"
    }
    else if (urlType == "refresh")
    {
    	return "https://api.simplisafe.com/v1/subscriptions/$state.subscriptionId/"
    }
    else
    {
    	log.info "Invalid URL type"
    }
}

Toby,

When you get back to working on this let me know if you would like my assistance in working on the SS2 side of things (i don’t have a SS3).

Thanks for all your work on this. I installed the SS3 system yesterday so if you need some testing let me know. I had the SS2 system and thought things were working correctly, but since I only had it about 60 days, SS let me send it back and I bought the SS3 (mainly for the keypads that light up). Anyway, would love to see this updated for SS3 and will be glad to beta test. Thanks.

While you’re working on an update, can you try to add the panic button as a switch to be usable with Alexa?