SleepIQ (Sleep Number)

@toy4rick are you pulling up the device and clicking the event button? I'm not seeing the mode or bedid on mine just the switch and presence.

Name Value Unit Description Text Source Type Date
switch off DEVICE 2019-06-08 08:30:00.557 AM EDT
presence not present DEVICE 2019-06-08 08:30:00.557 AM EDT
presence present DEVICE 2019-06-08 12:00:01.281 AM EDT
switch on DEVICE 2019-06-08 12:00:01.281 AM EDT
switch off DEVICE 2019-06-07 07:10:41.333 AM EDT
presence not present DEVICE 2019-06-07 07:10:41.333 AM EDT
switch on DEVICE 2019-06-06 10:50:00.643 PM EDT
presence present DEVICE 2019-06-06 10:50:00.637 PM EDT
switch off DEVICE 2019-06-06 10:45:00.787 PM EDT
presence not present DEVICE 2019-06-06 10:45:00.787 PM EDT

@toy4rick

I wish I could tell you why aren't seeing anything in the event log. Here's what the event log looks like for the side I sleep on:

Here's what the SleepIQ Manager setup screen looks like, after 4 sensors are created (R, L, Either, Both):

56%20AM

Just to confirm - you are using the modifications that @leeonestop posted, correct?

@aaiyar I just set a right and left not both or either? is there a benefit to setting up other beside the right and left???

There is for me. I roll in bed, sometimes starting on one side and ending up on the other. In that situation, I want my wake routine to be based on either side being occupied.

There is also a possibility that both sides will be occupied, and when that happens, I don't want my wake up routine to run.

@leeonestop, yes, using the event button

@aaiyar, I created left and right. why would i need to create both and/or either? I'm not following

By creating Both and Either, won't I know have 6 unique devices for a 2 person bed?

Rick

@aaiyar I understand. Good answer. Thanks

Both or either are useful if you are single.

@toy4rick is the app showing presence in bed and out on both sides if so then I would think that it should show up in the event log?

yes, the SleepIQ app on my phone is recording activity each night

Rick

OK so still nothing from my device or my wifes through HE. I did notice that my "both" and "either" devices did record last nights data so I deleted the other 2 and recreated them, here's to hoping.

Now for my question, what type of response times do you get so that you can apply automation?

For me, I got out of bed at 7:11am this morning per my cell phone and the log entry for "either" was 7:15, don't see how I could use that in anything meaningful

Rick

Depends on how frequently you update. The primary automation that I'm using it for is if I'm in bed for longer that I should be, then I turn on various lights, use Alexa etc to force me out of bed.

If you go into the App for the Sleep IQ you can change the refresh rate it is default at 15 I have mine set at 5 and work ok for me also, I have it set to turn off a ceiling fan when both sides are out of bed.

Just an FYI folks

SleepIQ Manager started running away in the live logs this morning, several hundred entries over a few mins with no signs of stopping. It was also producing some excessive log entries for our bed devices, more of one than the other

I clicked onto the past logs and when I went back to live logging, it had stopped

Rick

Maybe there some code that is causing the issue you are talking about. I will reach out and see if I can get some help. Thanks

I'm getting this error with the app :

Expression [MethodCallExpression] is not allowed: this.sleep(1000) at line number: 158

Is there a modification I should be applying?

I adjusted that line of code see if it works for you now. I put a space in between the sleep and 1000. Thanks

I too am getting that same error as @michael.l.nelson on line 158.

I'm using the adjusted code with the spacing but it won't save the app due to the error.
while(state.requestData == null) { sleep ( 1000 ) }

Anything I should try?

@mikedq @michael.l.nelson

I ran into some formatting issues as well while cutting & pasting the app and driver posted above. I cleaned them up by trial & error. Try cutting and pasting the following for the app. It works on my HE with 2.1.1.120.

/**
 *  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("* /${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 = [:]
}
}

Thanks @aaiyar that did the trick. I should have figured it was just a formatting error. Still learning the ropes here on HE after my transition from ST.

Ditto!