SimpliSafe Alarm Integration (cloud to cloud)

Am I the only one with Simplisafe integration stopped working today, the logs show connection reset when trying to login? Are we losing Simplisafe integration after Blink as well. While I do realize this was always a risk, it sucks that all companies are trying to close their systems.

Looks like I am having issues too. Here is what I see in the Live Logging:

11:57:04 AM: error java.net.SocketException: Connection reset
11:57:04 AM: info Executing 'loginā€™
11:57:04 AM: debug Need to login
11:57:04 AM: debug No state.auth
11:57:04 AM: info Executing 'apiā€™
11:57:04 AM: info Executing 'statusā€™
11:57:04 AM: info Executing ā€˜pollā€™

8:55:10 AM: error java.net.SocketException: Connection reset
8:55:10 AM: info Executing 'loginā€™
8:55:10 AM: debug Need to login
8:55:10 AM: debug No state.auth
8:55:10 AM: info Executing 'apiā€™
8:55:10 AM: info Executing ā€˜awayā€™

I tried deleting everything and reinstalling. hrmph

Manā€¦this is a real bummer, I had things setup and working perfectly.

I am also seeing this as well. My guess is that something has changed now
that they switched to their new online control panel.

This was first reported for people with the new v3 system but now it
appears to be more wide spread.

I have a v3 unit on the way so I can take a look at the changes and update
the device handler.

2 Likes

Really great to hear that you are still supporting this device handler! Just wanted to say thanks

2 Likes

My system was still working as of this morningā€¦ iā€™ll be tragic to loose this feature. Literally a criteria when i bought into both ST and SS.

My alarm set correctly around 6:30 AM PST.

This hasnā€™t updated since last night for me.

Joel_Day, can you tell me where you got the from. I can get to that point, but donā€™t know what that is.

I did some web tracing when I logged into the new SS portal. Thatā€™s where I found the API endpoints.

Do you have a specific question?

Hey @Joel_Day,
Iā€™m trying to work on a fix for this, but Iā€™m not able to use the grant_type you discovered. Is it still working for you?

 {
    "error": "unsupported_grant_type",
    "error_description": "Unsupported grant type: undefined"
}

Like I posted earlier, after upgrading to SS3, I couldnā€™t use the device handler for the newer SS3 device. It looks like we need to use the new API as Joel_Day figured out. I took a stab at updating the device handler and with some of the code from tobycth3ā€™s device handler and I got it working with my SS3. See the code below. I
needed only the arm/disarm functionality so I have updated the code to do only that part.

This is my first attempt at custom device handlers and Iā€™m not a good programmer but I can tell you that this is working for me :slight_smile:

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() {
	runEvery5Minutes(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")
    {
        httpPost([ uri: getAPIUrl("alarmOff"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8" ])
    }
    else if (alState == "home")
    {
        httpPost([ uri: getAPIUrl("alarmHome"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8" ])
    }
    else if (alState == "away")
    {
        
        httpPost([ uri: getAPIUrl("alarmAway"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8" ])
    }
    else
    {
        log.info "Invalid state requested."
    }
}

def poll() {
    log.info "Executing polling..."
    if (now() > state.tokenExpiry)
    {
    	apiLogin()
    }    
	httpGet ([uri: getAPIUrl("refresh"), headers: state.respAuthHeader, contentType: "application/json; charset=utf-8"]) { response ->
        sendEvent(name: "alarm", value: response.data.state)
    }
    //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="	]
    httpPost([ uri: getAPIUrl("initAuth"), headers: authHeader, contentType: "application/json; charset=utf-8", body: authBody ]) { response ->
        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)
   	{
        getUserId()
   	}
    if (!state.subscriptionId)
    {
        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
    }
}

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)
    }
}

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/ss3/subscriptions/$state.subscriptionId/state/off"
    }
    else if (urlType == "alarmHome" )
    {
    	return "https://api.simplisafe.com/v1/ss3/subscriptions/$state.subscriptionId/state/home"
    }
    else if (urlType == "alarmAway" )
    {
    	return "https://api.simplisafe.com/v1/ss3/subscriptions/$state.subscriptionId/state/away"
    }
    else if (urlType == "refresh")
    {
    	return "https://api.simplisafe.com/v1/ss3/subscriptions/$state.subscriptionId/state"
    }
    else
    {
    	log.info "Invalid URL type"
    }
}

For some reason I didnā€™t paste what I was asking. What I meant was where did the SUBSCRIPTION-ID come from.

I can get all the way to the following point, and donā€™t know what to use for that value.

$settings = Invoke-WebRequest -Uri "https://api.simplisafe.com/v1/subscriptions/<SUBSCRIPTION-ID>/settings?cached=true&settingsType=sensors" -WebSession $ssLogin -Headers $headers

I was able to do it earlier and could do the grant_type password. Here is what mine looked like.

{
  "grant_type": "password",
  "username": "YourEmail@email.com",
  "password": "YourPassword123"
}

You can get the userId with this request.
$authCheck = Invoke-WebRequest -Uri ā€œhttps://api.simplisafe.com/v1/api/authCheckā€ -WebSession $ssLogin -Headers $headers

use that userId in this URL and do a get to see the subscription id.

ā€œhttps://api.simplisafe.com/v1/users//subscriptions?activeOnly=falseā€

"https://api.simplisafe.com/v1/users/<UserId>/subscriptions?activeOnly=false"

@RCP
Thanks for putting this together. I attempted to use it on my v2 SimpliSafe but it kept throwing errors when requesting a token. Iā€™m starting to wonder if we are going to need separate handlers for v2/v3.

Iā€™m going to try and spend a little time on this tonight to see if I can come up with a fix for v2 users.

1 Like

I was able to do it through a separate client app doing post and get with my SS2. I will try this version later.