SleepIQ (Sleep Number)

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

I just had a few minutes to try it out and it seems to work with a few minor tweaks.

before all headers: lines in the app code (there are 5) put:
requestContentType: 'application/json',
contentType: 'application/json',

And also in the app code, change sleep(100) to Sleep(100)

The rest worked as is. It could probably use some other cleanup (it mentions Smarthings in the user visible text, etc) but nothing that seemed to hamper functionality.

1 Like

@rvrolyk can you tell me what are the headers in the app? I'm not a programmer by any means but trying to learn some. Thanks

@leeonestop

Here you go (you can compare it to the original to see where @rvrolyk's suggested changes are) :

Modified app:

/**
 *  Sleep Number Manager
 *
 *  Copyright 2019 Tim Parsons
 *
 *  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: "Sleep Number Manager",
    namespace: "ClassicTim1",
    author: "Classic_Tim",
    description: "Control your Sleep Number bed vai SmartThings. You can use it to raise, lower, and adjsut the pressure on each side of the bed seperately if it's split, or all together if it's not. If you want to do each side seperately you must use two devices for each side",
    category: "Convenience",
    iconUrl: "https://raw.githubusercontent.com/ClassicTim1/SleepNumberManager/master/icons/logo.jpg",
    iconX2Url: "https://raw.githubusercontent.com/ClassicTim1/SleepNumberManager/master/icons/logo.jpg",
    iconX3Url: "https://raw.githubusercontent.com/ClassicTim1/SleepNumberManager/master/icons/logo.jpg"
)


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")
    }
    section("Devices") {
      if (devices.size() > 0) {
        devices.each { device ->
          paragraph title: device.label, "${device.currentBedId} / ${device.currentSide}"
        }
      }
      href "findDevicePage", title: "Create New Device", description: null
    }
      if(devices.size() > 0){
          section(""){
              paragraph title: "", "To remove a device remove it from the Things tab in SmartThings"
          }
      }
  }
}

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}") {
          href "selectDevicePage", title: "Right Side", description: "Right side of the bed", params: [bedId: bed.bedId, side: "Right"]
          href "selectDevicePage", title: "Left Side", description: "Left side of the bed", params: [bedId: bed.bedId, side: "Left"]
        }
      }
    } 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 "Side: ${params.side}"
      input "newDeviceName", "text", title: "Device Name", description: "Name this device", defaultValue: ""
    }
    section {
      href "createDevicePage", title: "Create Device", description: null, params: [bedId: params.bedId, side: params.side]
    }
  }
}

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

  def deviceId = "sleepiq.${params.bedId}.${params.side}"
  def device = addChildDevice("sleepNumberBed", "Sleep Number Bed", deviceId, null, [label: settings.newDeviceName])
  device.setBedId(params.bedId)
  device.setSide(params.side)
  settings.newDeviceName = null
  rootPage()
  /*-dynamicPage(name: "selectDevicePage") {
    section {
      paragraph "Name: ${device.name}"
      paragraph "Label: ${device.label}"
      paragraph "Bed ID: ${device.curentBedId}"
      paragraph ": ${device.current}"
      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()"
  getBedData()
}

def getBedData() {
  log.trace "getBedData()"
    
  state.requestData = null
  updateFamilyStatus()
  while(state.requestData == null) { Sleep(100) }
  def requestData = state.requestData
  state.requestData = null
  processBedData(requestData)
  return 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 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;
      }
    }
  }
}





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

private def ApiUriBase() { "https://prod-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 updateFamilyStatus(alreadyLoggedIn = false) {
  log.trace "[SleepNumberManager] Updating Family Status"

  if (needsLogin()) {
      login()
  }

  try {
    def statusParams = [
      uri: ApiUriBase() + '/rest/bed/familyStatus?_k=' + state.session?.key,
      requestContentType: 'application/json',
      contentType: 'application/json',
      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) {
        state.requestData = response.data
      } else {
          log.error "[SleepNumberManager] Error updating family status - Request was unsuccessful: ($response.status) $response.data"
        state.session = null
        state.requestData = [:]
      }
    }
  } catch(Exception e) {
      log.error "[SleepNumberManager] Error updating family status -  Error ($e)"
  }
}

private def updateFoundationStatus(String bedId, String currentSide) {
  log.trace "[SleepNumberManager] Updating Foundation Status for: " + currentSide

  if (needsLogin()) {
      login()
  }

  try {
    def statusParams = [
      uri: ApiUriBase() + '/rest/bed/'+bedId+'/foundation/status?_k=' + state.session?.key,
      requestContentType: 'application/json',
      contentType: 'application/json',
      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) {
        return response.data
      } else {
        log.error "[SleepNumberManager] Error updating foundation status - Request was unsuccessful: ($response.status) $response.data"
      }
    }
  } catch(Exception e) {
      log.error "[SleepNumberManager] Error updating foundation status - Error ($e)"
  }
}

private def login() {
  log.trace "[SleepNumberManager] Logging in..."
  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 + '"}='
    ]
    httpPut(loginParams) { response ->
      if (response.status == 200) {
        log.trace "[SleepNumberManager] Login was successful"
        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] + ';'
        }
  		getBedData()
      } else {
        log.trace "[SleepNumberManager] Login failed: ($response.status) $response.data"
        state.session = null
        state.requestData = [:]
      }
    }
  } catch(Exception e) {
    log.error "[SleepNumberManager] Login failed: Error ($e)"
    state.session = null
    state.requestData = [:]
  }
}

private def raiseBed(String bedId, String side){
  log.trace "[SleepNumberManager] Raising bed side "+side+"..."
  put('/rest/bed/'+bedId+'/foundation/preset?_k=', "{'preset':1,'side':"+side+",'speed':0}")
}

private def lowerBed(String bedId, String side){
  log.trace "[SleepNumberManager] Lowering bed side "+side+"..."
  put('/rest/bed/'+bedId+'/foundation/preset?_k=', "{'preset':4,'side':"+side+",'speed':0}")
}

private def setNumber(String bedId, String side, number){
  log.trace "[SleepNumberManager] Setting sleep number side "+side+" to "+number+"..."
  put('/rest/bed/'+bedId+'/sleepNumber?_k=', "{'bed': "+bedId+", 'side': "+side+", 'sleepNumber': "+number+"}")
}

private def put(String uri, String body){
  if(needsLogin()){
  	login()
  }
  uri = uri + state.session?.key
  
  try {
    def statusParams = [
      uri: ApiUriBase() + uri,
        requestContentType: 'application/json',
        contentType: 'application/json',
        headers: [
        'Content-Type': 'application/json;charset=UTF-8',
        'Host': ApiHost(),
        'User-Agent': ApiUserAgent(),
        'Cookie': state.session?.cookies,
        'DNT': '1',
      ],
      body: body,
    ]
    httpPut(statusParams) { response -> 
      if (response.status == 200) {
  		getBedData()
        return true
      } else {
        log.error "[SleepNumberManager] Put Request failed: "+uri+" : "+body+" : ($response.status) $response.data"
        state.session = null
        state.requestData = [:]
        return false
      }
    }
  } catch(Exception e) {
      log.error "[SleepNumberManager] Put Request failed: "+uri+" : "+body+" : Error ($e)"
  }
}

private def needsLogin(){
	if(!state.session || !state.session?.key)
    	return true
        
    try {
        def statusParams = [
          uri: ApiUriBase() + '/rest/bed/familyStatus?_k=' + state.session?.key,
          requestContentType: 'application/json',
          contentType: 'application/json',
          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) {
            state.requestData = response.data
          } else {
            return true;
          }
        }
  } catch(Exception e) {
      return true;
  }
        
    return false
}

Modified driver:

/**
 *  Sleep Number
 *
 *  Copyright 2019 Tim Parsons
 *
 *  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: "Sleep Number Bed", namespace: "sleepNumberBed", author: "Classic_Tim", cstHandler: true) {
		capability "Switch Level"
        capability "Switch"
        capability "Presence Sensor"
        
        attribute "bedId", "String"
        attribute "side", "String"
               
        command "setBedId", ["string"]
        command "setSide", ["string"]
	}


	simulator {
		// TODO: define status and reply messages here
	}

	tiles(scale:2){
        standardTile("switch", "device.switch", width: 4, height: 4, canChangeIcon: false) {
            state "on", label:'RAISED', action:"off", icon:"https://raw.githubusercontent.com/ClassicTim1/SleepNumberManager/master/icons/raisedBed-icn3.png", backgroundColor:"#79b821"
            state "off", label:'FLAT', action:"on", icon:"st.Bedroom.bedroom2", backgroundColor:"#ffffff"
        }
        
        controlTile("levelSliderControl", "device.level", "slider", height: 4, width: 2) {
   			state "level", action:"switch level.setLevel"
		}
		valueTile("Side", "device.side", width: 3, height: 1){
        	state "default", label: '${currentValue} Side'
        }   
		valueTile("Presence", "device.PresenceState", width: 3, height: 1){
        	state "default", label: '${currentValue}'
        } 
   //     valueTile("level", "device.level",height:2, width:2, inactiveLabel: false, decoration: "flat") {
	//		state "level", label: 'Sleep Number: ${currentValue}'
	//	} 
    }
}

def updateData(String state, Integer sleepNumber, boolean present){
	sendEvent(name: "switch", value: state)
	sendEvent(name: "level", value: sleepNumber)
    if(present)
		sendEvent(name: "PresenceState", value: "Present")
    else
		sendEvent(name: "PresenceState", value: "Not Present")
}

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"
	// TODO: handle 'level' attribute

}

def setLevel(level) {
	sendEvent(name: "level", value: level)
    String side = "L"
    if(device.latestState('side').stringValue == "Right"){
    	side = "R"
    }
    parent.setNumber(device.latestState('bedId').stringValue, side, Math.round(level))
}


def setBedId(val){
	sendEvent(name: "bedId", value: val)
}

def setSide(val){
	sendEvent(name: "side", value: val)
}

def on(){
    String side = "L"
    if(device.latestState('side').stringValue == "Right"){
    	side = "R"
    }
	sendEvent(name: "switch", value: "on")
    parent.raiseBed(device.latestState('bedId').stringValue, side)
}

def off(){
    String side = "L"
    if(device.latestState('side').stringValue == "Right"){
    	side = "R"
    }
	sendEvent(name: "switch", value: "off")
    parent.lowerBed(device.latestState('bedId').stringValue, side)
}
3 Likes

Thanks @aaiyar, you beat me to it :smiley:

@leeonestop, you can read more about making HTTP requests here. I don't know why Smartthings documents the requestContentType and contentType parameters but doesn't require them while Hubitat does but basically all it's doing is telling the HTTP library what sort of content is being sent so it can handle encoding as needed.

1 Like

Thanks to you both. I see now where it even has where it says headers. It where I couldn't see the forest because of the trees.

1 Like

Thanks it's working like it does in ST. One thing that notice on both Hubitat and ST is when I turn on or off a side to set the head elevation it toggles the other side of the bed. So if my side is on and hers is off when I turn my side off to go back to flat it turns her side on. It's probably something simple in the logic. It toggles both if I press it again. Almost like it's sending the on off command to the bed instead of the side. The bed is a split top just fyi

1 Like