SleepIQ (Sleep Number)

Anyone else noticing that when the SleepIQ app polls the device (I'm on the default of 15 minutes) it really hammers away at the thing for a solid 60 seconds? Seems excessive

I think the schedule line is a bit off. It currently reads:
schedule("* /${settings.interval} * * * ?", "refreshChildDevices")
But that first * means it's running every second for the minute it runs (see this). If you change that to a number (0-59) I imagine you'll only see this once per bed side.

After applying updated code and installing i was able to get this functioning correctly. I do get errors when creating presense device instances, but it works well. Many use cases for me. Thank you for porting this!!!!

Finally got around to testing this and changing the * to a number didn't seem to solve things.
I tried this:
schedule("3 /${settings.interval} * * * ?", "refreshChildDevices")

Saved it and on it's check it hammered away for the full minute. Guess I'll leave well enough alone for now. Thanks for trying!

That should have worked. Here's what I use:

def initialize() {
// log.trace "initialize()"
refreshChildDevices()
schedule("1 /${settings.interval} * * * ?", "refreshChildDevices")
}

After saving it, you have to remove the old app and add the new app. Did you do that?

Here are what my logs for "SleepIQ Manager" look like:

Polling is configured to occur every 5 minutes, and as you can see, that is exactly what happens.

2 Likes

Jeez, no, I never thought to reinstall my newly changed app. :woozy_face:

Thanks for the tip, I'll give that a shot.

1 Like

A little off topic: Anyone seen drivers for the Sleep IQ Dual Temp bed warmer/cooler?

@aaiyar did that change on the initalize where you when to 1 work? I change mine but when I when to bed it didn't work? I interested if it work for @mikedq also if it did then I must be changing the wrong line.

Works fine.

Did you remove the old app and add the modified app?

I think that I have it now. I went and redid the complete reinstall and copy and paste your change. I must have had a space or some other issue but now it's working thanks.

1 Like

Would anyone care to boil down the code and procedure to set this up into a simple how-to? Im entirely unclear what version of what to use where and with what specific edits.

Also folks should always use the pre-formatted text tags when posting code. Maybe @leeonestop could edit his code posts?

Also, is it possible to control the foot warmer from HE with this integration?

@jon1

I don't know about foot-warmer, but you'll need the app and driver pasted below. Loading the app will create devices that use the driver.

App:

/**
 *  Copyright 2015 Nathan Jacobson <natecj@gmail.com>
 *
 *  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.
 *
 */

definition(
name: "SleepIQ Manager",
namespace: "natecj",
author: "Nathan Jacobson",
description: "Manage sleepers across multiple beds through your SleepIQ account.",
category: "Convenience",
iconUrl: "",
iconX2Url: ""
)

preferences {
page name: "rootPage"
page name: "findDevicePage"
page name: "selectDevicePage"
page name: "createDevicePage"
}

def rootPage() {
log.trace "rootPage()"

def devices = getChildDevices()

dynamicPage(name: "rootPage", install: true, uninstall: true) {
section("Settings") {
input("login", "text", title: "Username", description: "Your SleepIQ username")
input("password", "password", title: "Password", description: "Your SleepIQ password")
input("interval", "number", title: "Refresh Interval", description: "How many minutes between refresh", defaultValue: 15)
}
section("Devices") {
if (devices.size() > 0) {
devices.each { device ->
paragraph title: device.label, "${device.currentBedId} / ${device.currentMode}"
}
}
href "findDevicePage", title: "Create New Device", description: null
}
section {
label title: "Assign a name", required: false
mode title: "Set for specific mode(s)", required: false
}
}
}

def statusText(status) {
"Current Status: " + (status ? "Present" : "Not Present")
}

def findDevicePage() {
// log.trace "findDevicePage()"

def responseData = getBedData()
// log.debug "Response Data: $responseData"

dynamicPage(name: "findDevicePage") {
if (responseData.beds.size() > 0) {
responseData.beds.each { bed ->
section("Bed: ${bed.bedId}") {
def leftStatus = bed.leftSide.isInBed
def rightStatus = bed.rightSide.isInBed
def bothStatus = leftStatus && rightStatus
def eitherStatus = leftStatus || rightStatus
href "selectDevicePage", title: "Both Sides", description: statusText(bothStatus), params: [bedId: bed.bedId, mode: "Both", status: bothStatus]
href "selectDevicePage", title: "Either Side", description: statusText(eitherStatus), params: [bedId: bed.bedId, mode: "Either", status: eitherStatus]
href "selectDevicePage", title: "Left Side", description: statusText(leftStatus), params: [bedId: bed.bedId, mode: "Left", status: leftStatus]
href "selectDevicePage", title: "Right Side", description: statusText(rightStatus), params: [bedId: bed.bedId, mode: "Right", status: rightStatus]
}
}
} else {
section {
paragraph "No Beds Found"
}
}
}
}

def selectDevicePage(params) {
// log.trace "selectDevicePage()"

settings.newDeviceName = null

dynamicPage(name: "selectDevicePage") {
section {
paragraph "Bed ID: ${params.bedId}"
paragraph "Mode: ${params.mode}"
paragraph "Status: ${params.present ? 'Present' : 'Not Present'}"
input "newDeviceName", "text", title: "Device Name", description: "What do you want to call this presence sensor?", defaultValue: ""
}
section {
href "createDevicePage", title: "Create Device", description: null, params: [bedId: params.bedId, mode: params.mode, status: params.status]
}
}
}

def createDevicePage(params) {
// log.trace "createDevicePage()"

def deviceId = "Sleepiq.${params.bedId}.${params.mode}"
def device = addChildDevice("natecj", "SleepIQ Presence Sensor", deviceId, null, [label: settings.newDeviceName])
device.setStatus(params.status)
device.setBedId(params.bedId)
device.setMode(params.mode)
settings.newDeviceName = null

dynamicPage(name: "selectDevicePage") {
section {
paragraph "Name: ${device.name}"
paragraph "Label: ${device.label}"
paragraph "Bed ID: ${device.curentBedId}"
paragraph "Mode: ${device.currentMode}"
paragraph "Presence: ${device.currentPresnce}"
}
section {
href "rootPage", title: "Back to Device List", description: null
}
}
}

def installed() {
// log.trace "installed()"
initialize()
}

def updated() {
// log.trace "updated()"
unsubscribe()
unschedule()
initialize()
}

def initialize() {
// log.trace "initialize()"
refreshChildDevices()
schedule("1 /${settings.interval} * * * ?", "refreshChildDevices")
}

def refreshChildDevices() {
// log.trace "refreshChildDevices()"
getBedData()
}

def getBedData() {
log.trace "getBedData()"

// Make request and wait for completion
state.requestData = null
doStatus()
while(state.requestData == null) {Sleep ( 1000 )}
def requestData = state.requestData
state.requestData = null

// Process data
processBedData(requestData)

// Return data
requestData
}

def processBedData(responseData) {
if (!responseData || responseData.size() == 0) {
return
}
for(def device : getChildDevices()) {
for(def bed : responseData.beds) {
if (device.currentBedId == bed.bedId) {
def statusMap = [:]
statusMap["Both"] = bed.leftSide.isInBed && bed.rightSide.isInBed
statusMap["Either"] = bed.leftSide.isInBed || bed.rightSide.isInBed
statusMap["Left"] = bed.leftSide.isInBed
statusMap["Right"] = bed.rightSide.isInBed
if (statusMap.containsKey(device.currentMode)) {
// log.debug "Setting ${device.label} (${device.currentMode}) to ${statusMap[device.currentMode] ? "Present" : "Not Present"}"
device.setStatus(statusMap[device.currentMode])
}
break
}
}
}
}

private def ApiHost() { "api.sleepiq.sleepnumber.com" }

private def ApiUriBase() { "https://api.sleepiq.sleepnumber.com" }

private def ApiUserAgent() { "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36" }

private def doStatus(alreadyLoggedIn = false) {
// log.trace "doStatus()"

// Login if there isnt an active session
if (!state.session || !state.session?.key) {
if (alreadyLoggedIn) {
// log.error "doStatus() Already attempted login, giving up for now"
} else {
doLogin()
}
return
}

// Make the request
try {
def statusParams = [
uri: ApiUriBase() + '/rest/bed/familyStatus?_k=' + state.session?.key,
headers: [
'Content-Type': 'application/json;charset=UTF-8',
'Host': ApiHost(),
'User-Agent': ApiUserAgent(),
'Cookie': state.session?.cookies,
'DNT': '1',
],
]
httpGet(statusParams) { response ->
if (response.status == 200){
// log.trace "doStatus() Success - Request was successful: ($response.status) $response.data"
state.requestData = response.data
} else {
// log.trace "doStatus() Failure - Request was unsuccessful: ($response.status) $response.data"
state.session = null
state.requestData = [:]
}
}
} catch(Exception e) {
if (alreadyLoggedIn) {
// log.error "doStatus() Error ($e)"
} else {
// log.trace "doStatus() Error ($e)"
doLogin()
}
}
}

private def doLogin() {
log.trace "doLogin()"
state.session = null
state.requestData = [:]
try {
def loginParams = [
uri: ApiUriBase() + '/rest/login',
requestContentType: 'application/json',
contentType: 'application/json',	
headers: [
'Content-Type': 'application/json;charset=UTF-8',
'Host': ApiHost(),
'User-Agent': ApiUserAgent(),
'DNT': '1',
],
body: '{"login":"' + settings.login + '","password":"' + settings.password + '"}='
]
// log.trace "doLogin() put request: $loginParams"
httpPut(loginParams) { response ->
if (response.status==200) {
// log.trace "doLogin() Success - Request was successful: ($response.status) $response.data"
state.session = [:]
state.session.key = response.data.key
state.session.cookies = ''
response.getHeaders('Set-Cookie').each {
state.session.cookies = state.session.cookies + it.value.split(';')[0] + ';'
}
doStatus(true)
} else {
// log.trace "doLogin() Failure - Request was unsuccessful: ($response.status) $response.data"
state.session = null
state.requestData = [:]
}
}
} catch(Exception e) {
// log.error "doLogin() Error ($e)"
state.session = null
state.requestData = [:]
}
} 

Driver:

/**
 *  SleepIQ Presence Sensor
 *
 *  Copyright 2015 Nathan Jacobson
 *
 *  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.
 *
 */
metadata {
  definition (name: "SleepIQ Presence Sensor", namespace: "natecj", author: "Nathan Jacobson") {
    capability "Presence Sensor"
    capability "Switch"
    capability "Polling"

    command "arrived"
    command "departed"
    
    attribute "bedId", "String"
    attribute "mode", "enum", ["Both", "Either", "Left", "Right"]

    command "setStatus", ["string"]
    command "setBedId", ["string"]
    command "setMode", ["string"]
  }

  simulator {
    status "present": "presence: present"
    status "not present": "presence: not present"
	status "on": "switch: on"
    status "off": "switch: not off"
  }

/*
  preferences {
    section("Settings:") {
      input("mode", title: "Mode", "enum", required: false, defaultValue: "Either", options: ["Left", "Right", "Both", "Either"], description: "The side(s) of the bed to monitor")
    }
  }
*/

  tiles {
    standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
      state("not present", label:'not present', icon:"st.presence.tile.not-present", backgroundColor:"#ffffff", action:"arrived")
	  state("present", label:'present', icon:"st.presence.tile.present", backgroundColor:"#53a7c0", action:"departed")
    }
    standardTile("refresh", "device.poll", inactiveLabel: false, decoration: "flat") {
      state "default", action:"polling.poll", icon:"st.secondary.refresh"
    }
    valueTile("bedId", "device.bedId", width: 3, height: 1) {
      state "default", label: '${currentValue}'
    }
    valueTile("mode", "device.mode", width: 1, height: 1) {
      state "default", label: '${currentValue}'
    }
    
    main "presence"
    details(["presence", "refresh", "mode", "bedId"])
  }
}

def installed() {
  log.trace 'installed()'
}

def updated() {
  log.trace 'updated()'
}

def poll() {
  log.trace "poll()"
  parent.refreshChildDevices()
}

def parse(String description) {
  log.trace "parse() - Description: ${description}"
  def results = []
  /*
  def pair = description.split(":")
  results = createEvent(name: pair[0].trim(), value: pair[1].trim())
  //results = createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
  */
  log.debug "parse() - Results: ${results.inspect()}"
  results
}

def arrived() {
  log.trace "arrived()"
  sendEvent(name: "presence", value: "present")
  sendEvent(name: "switch", value: "on")
}

def departed() {
  log.trace "departed()"
  sendEvent(name: "presence", value: "not present")
  sendEvent(name: "switch", value: "off")
}

def on() {
  log.trace "on()"
  arrived()
}

def off() {
  log.trace "off()"
  departed()
}

def setStatus(val) {
  log.trace "setStatus($val)"
  if (val) {
    arrived()
  } else {
    departed()
  }
}

def setBedId(val) {
  log.trace "setBedId($val)"
  sendEvent(name: "bedId", value: val)
}

def setMode(val) {
  log.trace "setMode($val)"
  sendEvent(name: "mode", value: val)
}

Thanks @aaiyar as I was out of town. By the way has your sleep bed sensors been slow example I get in bed and it seems to take a lot longer than usually lately. I know that the sleep number app has been undated and don't know if that causing my slow down or something else? Thanks

This seems to work pretty good and I like the idea of being able to use the bed for presence, but sometimes (not all the time) the logs are going crazy. Like the screenshot below, this was all within a few seconds. Should I be worried about it doing so much in so little time? I've got the refresh rate set at 15 which is the default, and from what I can tell from the code is in minutes.

Like I said, it doesn't do it all the time so I am wondering if it only goes crazy during and after presence or if there is a bug?

If it working good I wouldn't worry to much about it. I been using mine for about a year and it works good for me. I control lights and ceiling fan with the presence. I reach out to the original author from Smartthings and he help to get it fix to run on Hubitat.

1 Like

FYI this was, without a doubt, bogging down my hub. The hub admin UI has been feeling slower and slower lately. After realizing there isn't much I need, at the moment, to automate based on bed presence, I decided to remove the SleepIQ devices, app, and driver. It made a noticeable difference. If I come across the need for bed presence automations, I will look back into it. Hopefully by then it will be optimized a bit more. :slight_smile:

1 Like

I am trying to add some functionality that was present in the ST integration into the Hubitat port. Specifically, the sleep number pressure setting and am having some trouble with the code.

This is the habitat app code:

def processBedData(responseData) {
if (!responseData || responseData.size() == 0) {
return
}
for(def device : getChildDevices()) {
for(def bed : responseData.beds) {
if (device.currentBedId == bed.bedId) {
def bedSide = bed.leftSide
if(device.currentSide == "Right")
bedSide = bed.rightSide
def foundationStatus = updateFoundationStatus(device.currentBedId, device.currentSide)
String onOff = "off"
if(foundationStatus.fsCurrentPositionPresetRight != null && ((device.currentSide == "Right" && foundationStatus.fsCurrentPositionPresetRight != "Flat") || (device.currentSide == "Left" && foundationStatus.fsCurrentPositionPresetLeft != "Flat"))){
onOff = "on"
}
device.updateData(onOff, bedSide.sleepNumber, bedSide.isInBed)
break;
}
}
}
}

And this is the ST app code:

def processBedData(responseData) {
if (!responseData || responseData.size() == 0) {
return
}
for(def device : getChildDevices()) {
for(def bed : responseData.beds) {
if (device.currentBedId == bed.bedId) {
def bedSide = bed.leftSide
if(device.currentSide == "Right")
bedSide = bed.rightSide
def foundationStatus = updateFoundationStatus(device.currentBedId, device.currentSide)
String onOff = "off"
if(foundationStatus.fsCurrentPositionPresetRight != null && ((device.currentSide == "Right" && foundationStatus.fsCurrentPositionPresetRight != "Flat") || (device.currentSide == "Left" && foundationStatus.fsCurrentPositionPresetLeft != "Flat"))){
onOff = "on"
}
device.updateData(onOff, bedSide.sleepNumber, bedSide.isInBed)
break;
}
}
}
}

The smartthings code lets you set the tilt of the head to whatever is configured as favorite, set the pressure for each side, and gives you the presence. Whereas the Hubitat port only provides presence.

Nevermind, all the work that would need to be done in the driver broke my brain... I'm just going to hold off for now.

From what I can tell, you're looking at this ST app/driver? The one in this thread is different and was only ever for presence... I've been using that one for a while now without any issues and aside from the polling time being slower than a real presence sensor, it works well for me.

The one (I think) you're looking at indicated the focus was not presence and it has no refresh scheduled. If you want platform/bed controls, maybe you just want to port the new app and use both (if you're already using the one for presence)? I didn't look to see how hard it would be to merge the two concepts together but probably just adding a refresh scheduled into the one by Classic_Tim is enough if you already have that app working; add this to the initialize function:

schedule("0 /15 * * * ?", "getBedData")

I haven't tried this as I haven't tried getting that app to work.... Setting the foundation angle does sound interesting so I may give it a try one of these days but not sure if/when I'd be able to get to it.

I tried porting over Tim's but I'm not familiar enough with coding to get it done and even less so with all the code formatting requirements for Hubitat. Tim's looks like it's based off Hubitat's and he added the extra functions for the ST community. I was able to get it to import the driver and app with a few tweaks that I took from the presence sensor one. They are definitely the same code with changes to make it work with ST. If you look at both side by side it's obvious. I'll leave it to others with more coding skill. I was able to get what I needed working by using a virtual dimmer in Hubitat and SharpTools.io in between ST that uses Tim's code.

.Here's a link to Tim's code: https://community.smartthings.com/t/release-sleep-number-sleepiq-smartthings-integration/169038

1 Like