[sleepiq] Add functionality to control the bed foundation (#14714)

* Add foundation functionality

Signed-off-by: Mark Hilbush <mark@hilbush.com>
This commit is contained in:
Mark Hilbush 2023-03-28 11:27:31 -04:00 committed by GitHub
parent 1983bc36f1
commit 0a3d9ece3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 2069 additions and 142 deletions

View File

@ -19,6 +19,9 @@ Currently, only dual-chamber beds are supported by this binding.
The SleepIQ cloud thing must be added manually with the username and password used to register with the service.
After that, beds are discovered automatically by querying the service.
If the bed has a foundation, it will be auto-discovered by the binding.
If a foundation is discovered, the channels for controlling presets, position and outlets can be operated.
The thing properties will show whether or not a foundation is installed.
## Binding Configuration
@ -72,7 +75,6 @@ All channels within this group are read-only, except for the sleepNumber and pri
|-----------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------|
| inBed | Switch | The presence of a person or object on the chamber |
| sleepNumber | Number | The Sleep Number setting of the chamber. Set the sleep number of the chamber by sending a command to the sleepNumber channel with a value between 5 and 100. The value must be a multiple of 5 |
| sleepGoalMinutes | Number:Time | The person's sleep goal in minutes |
| pressure | Number | The current pressure inside the chamber |
| privacyMode | Switch | Enable or disable privacy mode |
@ -91,6 +93,11 @@ All channels within this group are read-only, except for the sleepNumber and pri
| monthlySleepIQ | Number | The average Sleep IQ score for the current month |
| monthlyAverageHeartRate | Number | The average heart rate for the current month |
| monthlyAverageRespirationRate | Number | The average respiration rate for the current month |
| foundationPreset | Number | Sets the head and foot position to one of the 6 available presets (1-6) (foundation required) |
| foundationPositionHead | Dimmer | Sets the head position (foundation required) |
| foundationPositionFoot | Dimmer | Sets the foot position (foundation required) |
| nightStandOutlet | Switch | Turn on/off the night stand outlet (foundation required) |
| underBedLight | Switch | Turn on/off the under bed light (foundation required) |
## Items
@ -117,11 +124,15 @@ Number:Time MasterBR_SleepIQ_DailyRestless_Alice "Daily Sleep Restless [%.0f
Number MasterBR_SleepIQ_MonthlySleepIQ_Alice "Monthly Sleep IQ [%d s]" { channel="sleepiq:dualBed:1:master:left#monthlySleepIQ"}
Number MasterBR_SleepIQ_MonthlyHeartRate_Alice "Monthly Heart Rate [%.0f]" { channel="sleepiq:dualBed:1:master:left#monthlyAverageHeartRate"}
Number MasterBR_SleepIQ_MonthlyRespRate_Alice "Monthly Respiration Rate [%.0f]" { channel="sleepiq:dualBed:1:master:left#monthlyAverageRespirationRate"}
Number MasterBR_SleepIQ_FoundationPreset_Alice "Foundation Preset [%d]" { channel="sleepiq:dualBed:1:master:left#foundationPreset"}
Dimmer MasterBR_SleepIQ_FoundationHead_Alice "Head Position [%d]" { channel="sleepiq:dualBed:1:master:left#foundationPositionHead"}
Dimmer MasterBR_SleepIQ_FoundationFoot_Alice "Foot Position [%d]" { channel="sleepiq:dualBed:1:master:left#foundationPositionFoot"}
Switch MasterBR_SleepIQ_FoundationNightStand_Alice "Night Stand [%d]" { channel="sleepiq:dualBed:1:master:left#nightStandOutlet"}
Switch MasterBR_SleepIQ_FoundationNightLight_Alice "Night Light [%d]" { channel="sleepiq:dualBed:1:master:left#nightLightOutlet"}
Switch MasterBR_SleepIQ_InBed_Bob "In Bed [%s]" { channel="sleepiq:dualBed:1:master:right#inBed" }
Number MasterBR_SleepIQ_SleepNumber_Bob "Sleep Number [%s]" { channel="sleepiq:dualBed:1:master:right#sleepNumber" }
Number MasterBR_SleepIQ_SleepGoal_Alice "Sleep Goal [%d min]" { channel="sleepiq:dualBed:1:master:left#sleepGoalMinutes"
Number MasterBR_SleepIQ_SleepGoal_Bob "Sleep Goal [%d min]" { channel="sleepiq:dualBed:1:master:left#sleepGoalMinutes"
Number:Time MasterBR_SleepIQ_Pressure_Bob "Pressure [%s]" { channel="sleepiq:dualBed:1:master:right#pressure" }
Switch MasterBR_SleepIQ_PrivacyMode_Bob "Privacy Mode [%s]" { channel="sleepiq:dualBed:1:master:right#privacyMode" }
String MasterBR_SleepIQ_LastLink_Bob "Last Update [%s]" { channel="sleepiq:dualBed:1:master:right#lastLink" }
@ -139,4 +150,9 @@ Number:Time MasterBR_SleepIQ_DailyRestless_Bob "Daily Sleep Restless [%.0f
Number MasterBR_SleepIQ_MonthlySleepIQ_Bob "Monthly Sleep IQ [%.0f]" { channel="sleepiq:dualBed:1:master:right#monthlySleepIQ"}
Number MasterBR_SleepIQ_MonthlyHeartRate_Bob "Monthly Heart Rate [%.0f]" { channel="sleepiq:dualBed:1:master:right#monthlyAverageHeartRate"}
Number MasterBR_SleepIQ_MonthlyRespRate_Bob "Monthly Respiration Rate [%.0f]" { channel="sleepiq:dualBed:1:master:right#monthlyAverageRespirationRate"}
Number MasterBR_SleepIQ_FoundationPreset_Bob "Foundation Preset [%d]" { channel="sleepiq:dualBed:1:master:right#foundationPreset"}
Dimmer MasterBR_SleepIQ_FoundationHead_Bob "Head Position [%d]" { channel="sleepiq:dualBed:1:master:right#foundationPositionHead"}
Dimmer MasterBR_SleepIQ_FoundationFoot_Bob "Foot Position [%d]" { channel="sleepiq:dualBed:1:master:right#foundationPositionFoot"}
Switch MasterBR_SleepIQ_FoundationNightStand_Bob "Night Stand [%d]" { channel="sleepiq:dualBed:1:master:right#nightStandOutlet"}
Switch MasterBR_SleepIQ_FoundationNightLight_Bob "Night Light [%d]" { channel="sleepiq:dualBed:1:master:right#nightLightOutlet"}
```

View File

@ -97,6 +97,21 @@ public class SleepIQBindingConstants {
public static final String CHANNEL_LEFT_MONTHLY_AVG_RESPIRATION_RATE = "left#monthlyAverageRespirationRate";
public static final String CHANNEL_RIGHT_MONTHLY_AVG_RESPIRATION_RATE = "right#monthlyAverageRespirationRate";
public static final String CHANNEL_LEFT_FOUNDATION_PRESET = "left#foundationPreset";
public static final String CHANNEL_RIGHT_FOUNDATION_PRESET = "right#foundationPreset";
public static final String CHANNEL_LEFT_POSITION_HEAD = "left#foundationPositionHead";
public static final String CHANNEL_RIGHT_POSITION_HEAD = "right#foundationPositionHead";
public static final String CHANNEL_LEFT_POSITION_FOOT = "left#foundationPositionFoot";
public static final String CHANNEL_RIGHT_POSITION_FOOT = "right#foundationPositionFoot";
public static final String CHANNEL_LEFT_NIGHT_STAND_OUTLET = "left#nightStandOutlet";
public static final String CHANNEL_RIGHT_NIGHT_STAND_OUTLET = "right#nightStandOutlet";
public static final String CHANNEL_LEFT_UNDER_BED_LIGHT = "left#underBedLight";
public static final String CHANNEL_RIGHT_UNDER_BED_LIGHT = "right#underBedLight";
// List of non-standard Properties
public static final String PROPERTY_BASE = "base";
public static final String PROPERTY_KIDS_BED = "kidsBed";
@ -105,4 +120,11 @@ public class SleepIQBindingConstants {
public static final String PROPERTY_PURCHASE_DATE = "purchaseDate";
public static final String PROPERTY_SIZE = "size";
public static final String PROPERTY_SKU = "sku";
public static final String PROPERTY_FOUNDATION = "foundation";
public static final String PROPERTY_FOUNDATION_IS_BOARD_AS_SINGLE = "foundationIsBoardAsSingle";
public static final String PROPERTY_FOUNDATION_HAS_MASSAGE_AND_LIGHT = "foundationHasMasssageAndLight";
public static final String PROPERTY_FOUNDATION_HAS_FOOT_CONTROL = "foundationHasFootControl";
public static final String PROPERTY_FOUNDATION_HAS_FOOT_WARMER = "foundationHasFootWarmer";
public static final String PROPERTY_FOUNDATION_HAS_UNDER_BED_LIGHT = "foundationHasUnderBedLight";
public static final String PROPERTY_FOUNDATION_HW_REV = "foundationHwRev";
}

View File

@ -28,8 +28,6 @@ public class Configuration {
private URI baseUri = URI.create("https://api.sleepiq.sleepnumber.com");
private boolean logging = false;
/**
* Get the username on the account.
*
@ -128,39 +126,4 @@ public class Configuration {
setBaseUri(baseUri);
return this;
}
/**
* Get the logging flag.
*
* @return the logging flag
*/
public boolean isLogging() {
return logging;
}
/**
* Set the logging flag. When this is set to <code>true</code>, all requests
* and responses will be logged at the {@link Level#INFO} level. <b>This
* includes usernames and passwords!</b>
*
* @param logging
* the value to set
*/
public void setLogging(boolean logging) {
this.logging = logging;
}
/**
* Set the logging flag. When this is set to <code>true</code>, all requests
* and responses will be logged at the {@link Level#INFO} level. <b>This
* includes usernames and passwords!</b>
*
* @param logging
* the value to set
* @return this configuration instance
*/
public Configuration withLogging(boolean logging) {
setLogging(logging);
return this;
}
}

View File

@ -19,10 +19,17 @@ import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.sleepiq.internal.api.dto.Bed;
import org.openhab.binding.sleepiq.internal.api.dto.FamilyStatusResponse;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
import org.openhab.binding.sleepiq.internal.api.dto.LoginInfo;
import org.openhab.binding.sleepiq.internal.api.dto.PauseModeResponse;
import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import org.openhab.binding.sleepiq.internal.api.enums.SleepDataInterval;
import org.openhab.binding.sleepiq.internal.api.impl.SleepIQImpl;
@ -31,6 +38,7 @@ import org.openhab.binding.sleepiq.internal.api.impl.SleepIQImpl;
* This interface is the main API to access the SleepIQ system.
*
* @author Gregory Moyer - Initial contribution
* @author Mark Hilbush - Added foundation functionality
*/
@NonNullByDefault
public interface SleepIQ {
@ -133,6 +141,68 @@ public interface SleepIQ {
*/
public void setPauseMode(String bedId, boolean command) throws LoginException, SleepIQException;
/**
* Get the foundation features for a bed
*
* @param bedId the unique identifier of the bed
*
* @throws LoginException
* @throws SleepIQException
*/
public FoundationFeaturesResponse getFoundationFeatures(String bedId) throws LoginException, SleepIQException;
/**
* Get the foundation status for a bed
*
* @param bedId the unique identifier of the bed
*
* @throws LoginException
* @throws SleepIQException
*/
public FoundationStatusResponse getFoundationStatus(String bedId) throws LoginException, SleepIQException;
/**
* Set a foundation preset for the side of a bed
*
* @param bedId the unique identifier of the bed
* @param side the chamber of the bed
* @param preset the foundation preset
* @param speed the speed with which the adjustment is made
*
* @throws LoginException
* @throws SleepIQException
*/
public void setFoundationPreset(String bedId, Side side, FoundationPreset preset, FoundationActuatorSpeed speed)
throws LoginException, SleepIQException;
/**
* Set a foundation position for the side of a bed
*
* @param bedId the unique identifier of the bed
* @param side the chamber of the bed
* @param actuator the head or foot of the bed
* @param position the new position of the actuator
* @param speed the speed with which the adjustment is made
*
* @throws LoginException
* @throws SleepIQException
*/
public void setFoundationPosition(String bedId, Side side, FoundationActuator actuator, int position,
FoundationActuatorSpeed speed) throws LoginException, SleepIQException;
/**
* Turn a foundation outlet on or off
*
* @param bedId the unique identifier of the bed
* @param outlet the foundation outlet to control
* @param operation set the outlet on or off
*
* @throws LoginException
* @throws SleepIQException
*/
public void setFoundationOutlet(String bedId, FoundationOutlet outlet, FoundationOutletOperation operation)
throws LoginException, SleepIQException;
/**
* Create a default implementation instance of this interface. Each call to
* this method will create a new object.

View File

@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.dto;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import com.google.gson.annotations.SerializedName;
/**
* The {@link FoundationAdjustmentRequest} is used control the actuator
* on the side of a bed.
*
* @author Mark Hilbush - Initial contribution
*/
public class FoundationAdjustmentRequest {
@SerializedName("side")
private Side side;
@SerializedName("actuator")
private FoundationActuator actuator;
@SerializedName("position")
private int position;
@SerializedName("speed")
private FoundationActuatorSpeed speed;
public Side getSide() {
return side;
}
public void setSide(Side side) {
this.side = side;
}
public FoundationAdjustmentRequest withSide(Side side) {
setSide(side);
return this;
}
public FoundationActuator getFoundationActuator() {
return actuator;
}
public void setFoundationActuator(FoundationActuator actuator) {
this.actuator = actuator;
}
public FoundationAdjustmentRequest withFoundationActuator(FoundationActuator actuator) {
setFoundationActuator(actuator);
return this;
}
public int getFoundationPosition() {
return position;
}
public void setFoundationPosition(int position) {
this.position = position;
}
public FoundationAdjustmentRequest withFoundationPosition(int position) {
setFoundationPosition(position);
return this;
}
public FoundationActuatorSpeed getFoundationActuartorSpeed() {
return speed;
}
public void setFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
this.speed = speed;
}
public FoundationAdjustmentRequest withFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
setFoundationActuatorSpeed(speed);
return this;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("SleepNumberRequest [side=");
builder.append(side);
builder.append(", actuator=");
builder.append(actuator);
builder.append(", position=");
builder.append(position);
builder.append(", speed=");
builder.append(speed);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,120 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link FoundationFeaturesResponse} holds the features of the foundation
* returned from the sleepiq API.
*
* @author Mark Hilbush - Initial contribution
*/
public class FoundationFeaturesResponse {
private static final int BOARD_AS_SINGLE = 0x01;
private static final int MASSAGE_AND_LIGHT = 0x02;
private static final int FOOT_CONTROL = 0x04;
private static final int FOOT_WARMING = 0x08;
private static final int UNDER_BED_LIGHT = 0x10;
@SerializedName("fsBedType")
private int bedType;
@SerializedName("fsBoardFaults")
private int boardFaults;
@SerializedName("fsBoardFeatures")
private int boardFeatures;
@SerializedName("fsBoardHWRevisionCode")
private int boardHWRev;
@SerializedName("fsBoardStatus")
private int boardStatus;
@SerializedName("fsLeftUnderbedLightPWM")
private int leftUnderbedLightPWM;
@SerializedName("fsRightUnderbedLightPWM")
private int rightUnderbedLightPWM;
public int getBedType() {
return bedType;
}
public int getBoardFaults() {
return boardFaults;
}
public int getBoardFeatures() {
return boardFeatures;
}
public boolean isBoardAsSingle() {
return (boardFeatures & BOARD_AS_SINGLE) > 0;
}
public boolean hasMassageAndLight() {
return (boardFeatures & MASSAGE_AND_LIGHT) > 0;
}
public boolean hasFootControl() {
return (boardFeatures & FOOT_CONTROL) > 0;
}
public boolean hasFootWarming() {
return (boardFeatures & FOOT_WARMING) > 0;
}
public boolean hasUnderBedLight() {
return (boardFeatures & UNDER_BED_LIGHT) > 0;
}
public int getBoardHWRev() {
return boardHWRev;
}
public int getBoardStatus() {
return boardStatus;
}
public int getLeftUnderbedLightPWM() {
return leftUnderbedLightPWM;
}
public int getRightUnderbedLightPWM() {
return rightUnderbedLightPWM;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("FoundationFeaturesResponse [");
builder.append("bedType=");
builder.append(bedType);
builder.append(", boardFaults=");
builder.append(boardFaults);
builder.append(", boardFeatures=");
builder.append(boardFeatures);
builder.append(", boardHWRevisionCode=");
builder.append(boardHWRev);
builder.append(", boardStatus=");
builder.append(boardStatus);
builder.append(", leftUnderbedLightPWM=");
builder.append(leftUnderbedLightPWM);
builder.append(", rightUnderbedLightPWM=");
builder.append(rightUnderbedLightPWM);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.dto;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
import com.google.gson.annotations.SerializedName;
/**
* The {@link FoundationOutletRequest} is used to control an outlet on the bed foundation.
*
* @author Mark Hilbush - Initial contribution
*/
public class FoundationOutletRequest {
@SerializedName("outletId")
private FoundationOutlet outlet;
@SerializedName("setting")
private FoundationOutletOperation operation;
public FoundationOutlet getFoundationOutlet() {
return outlet;
}
public void setFoundationOutlet(FoundationOutlet outlet) {
this.outlet = outlet;
}
public FoundationOutletRequest withFoundationOutlet(FoundationOutlet outlet) {
setFoundationOutlet(outlet);
return this;
}
public FoundationOutletOperation getFoundationOutletOperation() {
return operation;
}
public void setFoundationOutletOperation(FoundationOutletOperation operation) {
this.operation = operation;
}
public FoundationOutletRequest withFoundationOutletOperation(FoundationOutletOperation operation) {
setFoundationOutletOperation(operation);
return this;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("SleepNumberRequest [outlet=");
builder.append(outlet);
builder.append(", operation=");
builder.append(operation);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.dto;
/**
* The {@link FoundationPosition} holds the head or foot position of a bed side.
*
* @author Mark Hilbush - Initial contribution
*/
public class FoundationPosition {
private Integer foundationPosition;
public Integer getFoundationPosition() {
return foundationPosition;
}
public void setFoundationPosition(Integer foundationPosition) {
this.foundationPosition = foundationPosition;
}
public FoundationPosition withFoundationPosition(Integer foundationPosition) {
setFoundationPosition(foundationPosition);
return this;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("FoundationPosition [foundationPosition=");
builder.append(foundationPosition);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.dto;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import com.google.gson.annotations.SerializedName;
/**
* The {@link FoundationPositionRequest} is used to set the position of the head or foot
* of a side of a bed..
*
* @author Mark Hilbush - Initial contribution
*/
public class FoundationPositionRequest {
@SerializedName("side")
private Side side;
@SerializedName("position")
private int position;
@SerializedName("actuator")
private FoundationActuator actuator;
@SerializedName("speed")
private FoundationActuatorSpeed speed;
public Side getSide() {
return side;
}
public void setSide(Side side) {
this.side = side;
}
public FoundationPositionRequest withSide(Side side) {
setSide(side);
return this;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public FoundationPositionRequest withPosition(int position) {
setPosition(position);
return this;
}
public FoundationActuator getFoundationActuartor() {
return actuator;
}
public void setFoundationActuator(FoundationActuator actuator) {
this.actuator = actuator;
}
public FoundationPositionRequest withFoundationActuator(FoundationActuator actuator) {
setFoundationActuator(actuator);
return this;
}
public FoundationActuatorSpeed getFoundationActuartorSpeed() {
return speed;
}
public void setFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
this.speed = speed;
}
public FoundationPositionRequest withFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
setFoundationActuatorSpeed(speed);
return this;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("SleepNumberRequest [side=");
builder.append(side);
builder.append(", position=");
builder.append(position);
builder.append(", actuator=");
builder.append(actuator);
builder.append(", speed=");
builder.append(speed);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.dto;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import com.google.gson.annotations.SerializedName;
/**
* The {@link FoundationPresetRequest} is used to set a preset for a bed side.
*
* @author Mark Hilbush - Initial contribution
*/
public class FoundationPresetRequest {
@SerializedName("side")
private Side side;
@SerializedName("preset")
private FoundationPreset preset;
@SerializedName("speed")
private FoundationActuatorSpeed speed;
public Side getSide() {
return side;
}
public void setSide(Side side) {
this.side = side;
}
public FoundationPresetRequest withSide(Side side) {
setSide(side);
return this;
}
public FoundationPreset getFoundationPreset() {
return preset;
}
public void setFoundationPreset(FoundationPreset preset) {
this.preset = preset;
}
public FoundationPresetRequest withFoundationPreset(FoundationPreset preset) {
setFoundationPreset(preset);
return this;
}
public FoundationActuatorSpeed getFoundationActuartorSpeed() {
return speed;
}
public void setFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
this.speed = speed;
}
public FoundationPresetRequest withFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
setFoundationActuatorSpeed(speed);
return this;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("SleepNumberRequest [side=");
builder.append(side);
builder.append(", preset=");
builder.append(preset);
builder.append(", speed=");
builder.append(speed);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,105 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.dto;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import com.google.gson.annotations.SerializedName;
/**
* The {@link FoundationStatusResponse} holds the status of the foundation
* returned from the sleepiq API.
*
* @author Mark Hilbush - Initial contribution
*/
public class FoundationStatusResponse {
@SerializedName("fsType")
private String type;
@SerializedName("fsRightHeadPosition")
private FoundationPosition rightHeadPosition;
@SerializedName("fsRightFootPosition")
private FoundationPosition rightFootPosition;
@SerializedName("fsLeftHeadPosition")
private FoundationPosition leftHeadPosition;
@SerializedName("fsLeftFootPosition")
private FoundationPosition leftFootPosition;
@SerializedName("fsCurrentPositionPresetRight")
private FoundationPreset currentPositionPresetRight;
@SerializedName("fsCurrentPositionPresetLeft")
private FoundationPreset currentPositionPresetLeft;
@SerializedName("fsOutletsOn")
private boolean outletsOn;
public String getType() {
return type;
}
public int getRightHeadPosition() {
return rightHeadPosition.getFoundationPosition().intValue();
}
public int getLeftHeadPosition() {
return leftHeadPosition.getFoundationPosition().intValue();
}
public int getRightFootPosition() {
return rightFootPosition.getFoundationPosition().intValue();
}
public int getLeftFootPosition() {
return leftFootPosition.getFoundationPosition().intValue();
}
public FoundationPreset getCurrentPositionPresetRight() {
return currentPositionPresetRight;
}
public FoundationPreset getCurrentPositionPresetLeft() {
return currentPositionPresetLeft;
}
public boolean getOutletsOn() {
return outletsOn;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("FoundationStatusResponse [");
builder.append("type=");
builder.append(type);
builder.append("rightHeadPosition=");
builder.append(rightHeadPosition);
builder.append(", leftHeadPosition=");
builder.append(leftHeadPosition);
builder.append(", rightFootPosition=");
builder.append(rightFootPosition);
builder.append(", leftFootPosition=");
builder.append(leftFootPosition);
builder.append(", currentPositionPresetRight=");
builder.append(currentPositionPresetRight);
builder.append(", currentPositionPresetLeft=");
builder.append(currentPositionPresetLeft);
builder.append(", outletsOn=");
builder.append(outletsOn);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.enums;
import static org.openhab.binding.sleepiq.internal.SleepIQBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link FoundationActuator} represents actuators at the head and foot of the bed side.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public enum FoundationActuator {
@SerializedName("H")
HEAD("H"),
@SerializedName("F")
FOOT("F");
private final String actuator;
FoundationActuator(final String actuator) {
this.actuator = actuator;
}
public String value() {
return actuator;
}
public static FoundationActuator forValue(String value) {
for (FoundationActuator s : FoundationActuator.values()) {
if (s.actuator.equals(value)) {
return s;
}
}
throw new IllegalArgumentException("Invalid actuator: " + value);
}
public static FoundationActuator convertFromChannelId(String channelId) {
FoundationActuator localActuator;
switch (channelId) {
case CHANNEL_RIGHT_POSITION_HEAD:
case CHANNEL_LEFT_POSITION_HEAD:
localActuator = FoundationActuator.HEAD;
break;
case CHANNEL_RIGHT_POSITION_FOOT:
case CHANNEL_LEFT_POSITION_FOOT:
localActuator = FoundationActuator.FOOT;
break;
default:
throw new IllegalArgumentException("Can't convert channel to actuator: " + channelId);
}
return localActuator;
}
@Override
public String toString() {
return actuator;
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.enums;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link FoundationActuatorSpeed} represents speed with which the actuator operates.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public enum FoundationActuatorSpeed {
FAST(0),
SLOW(1);
private final int speed;
FoundationActuatorSpeed(final int speed) {
this.speed = speed;
}
public int value() {
return speed;
}
public static FoundationActuatorSpeed forValue(int value) {
for (FoundationActuatorSpeed s : FoundationActuatorSpeed.values()) {
if (s.speed == value) {
return s;
}
}
throw new IllegalArgumentException("Invalid speed: " + value);
}
@Override
public String toString() {
return String.valueOf(speed);
}
}

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.enums;
import static org.openhab.binding.sleepiq.internal.SleepIQBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link FoundationOutlet} represents the outlets available on the foundation.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public enum FoundationOutlet {
RIGHT_NIGHT_STAND(1),
LEFT_NIGHT_STAND(2),
RIGHT_UNDER_BED_LIGHT(3),
LEFT_UNDER_BED_LIGHT(4);
private final int outlet;
FoundationOutlet(final int outlet) {
this.outlet = outlet;
}
public int value() {
return outlet;
}
public static FoundationOutlet forValue(int value) {
for (FoundationOutlet s : FoundationOutlet.values()) {
if (s.outlet == value) {
return s;
}
}
throw new IllegalArgumentException("Invalid outlet: " + value);
}
public static FoundationOutlet convertFromChannelId(String channelId) {
FoundationOutlet localOutlet;
switch (channelId) {
case CHANNEL_RIGHT_NIGHT_STAND_OUTLET:
localOutlet = FoundationOutlet.RIGHT_NIGHT_STAND;
break;
case CHANNEL_LEFT_NIGHT_STAND_OUTLET:
localOutlet = FoundationOutlet.LEFT_NIGHT_STAND;
break;
case CHANNEL_RIGHT_UNDER_BED_LIGHT:
localOutlet = FoundationOutlet.RIGHT_UNDER_BED_LIGHT;
break;
case CHANNEL_LEFT_UNDER_BED_LIGHT:
localOutlet = FoundationOutlet.LEFT_UNDER_BED_LIGHT;
break;
default:
throw new IllegalArgumentException("Can't convert channel to outlet: " + channelId);
}
return localOutlet;
}
@Override
public String toString() {
return String.valueOf(outlet);
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.enums;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link FoundationOutletOperation} represents the controls on a foundation outlet.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public enum FoundationOutletOperation {
OFF(0),
ON(1);
private final int outletControl;
FoundationOutletOperation(final int outletControl) {
this.outletControl = outletControl;
}
public int value() {
return outletControl;
}
public static FoundationOutletOperation forValue(int value) {
for (FoundationOutletOperation s : FoundationOutletOperation.values()) {
if (s.outletControl == value) {
return s;
}
}
throw new IllegalArgumentException("Invalid outletControl: " + value);
}
@Override
public String toString() {
return String.valueOf(outletControl);
}
}

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.enums;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link FoundationPreset} represents preset bed positions for a bed side.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public enum FoundationPreset {
NOT_AT_PRESET(0),
FAVORITE(1),
READ(2),
WATCH_TV(3),
FLAT(4),
ZERO_G(5),
SNORE(6);
private final int preset;
FoundationPreset(final int preset) {
this.preset = preset;
}
public int value() {
return preset;
}
public static FoundationPreset forValue(int value) {
for (FoundationPreset s : FoundationPreset.values()) {
if (s.preset == value) {
return s;
}
}
throw new IllegalArgumentException("Invalid preset: " + value);
}
public static FoundationPreset convertFromStatus(String currentPositionPreset) {
FoundationPreset preset;
switch (currentPositionPreset.toLowerCase()) {
case "not at preset":
preset = FoundationPreset.NOT_AT_PRESET;
break;
case "favorite":
preset = FoundationPreset.FAVORITE;
break;
case "read":
preset = FoundationPreset.READ;
break;
case "watch tv":
preset = FoundationPreset.WATCH_TV;
break;
case "flat":
preset = FoundationPreset.FLAT;
break;
case "zero g":
preset = FoundationPreset.ZERO_G;
break;
case "snore":
preset = FoundationPreset.SNORE;
break;
default:
throw new IllegalArgumentException("Unknown preset value: " + currentPositionPreset);
}
return preset;
}
@Override
public String toString() {
return String.valueOf(preset);
}
}

View File

@ -27,8 +27,12 @@ public class Endpoints {
private static final String FAMILY_STATUS = "/rest/bed/familyStatus";
private static final String PAUSE_MODE = "/rest/bed/%s/pauseMode";
private static final String SLEEP_DATA = "/rest/sleepData";
private static final String SET_SLEEP_NUMBER = "/rest/bed/%s/sleepNumber";
private static final String SET_PAUSE_MODE = "/rest/bed/%s/pauseMode";
private static final String SLEEP_NUMBER = "/rest/bed/%s/sleepNumber";
private static final String FOUNDATION_STATUS = "/rest/bed/%s/foundation/status";
private static final String FOUNDATION_FEATURES = "/rest/bed/%s/foundation/system";
private static final String FOUNDATION_POSITION = "/rest/bed/%s/foundation/adjustment/micro";
private static final String FOUNDATION_PRESET = "/rest/bed/%s/foundation/preset";
private static final String FOUNDATION_OUTLET = "/rest/bed/%s/foundation/outlet";
public static String login() {
return LOGIN;
@ -54,12 +58,28 @@ public class Endpoints {
return SLEEP_DATA;
}
public static String setSleepNumber(String bedId) {
return String.format(SET_SLEEP_NUMBER, bedId);
public static String sleepNumber(String bedId) {
return String.format(SLEEP_NUMBER, bedId);
}
public static String setPauseMode(String bedId) {
return String.format(SET_PAUSE_MODE, bedId);
public static String foundationStatus(String bedId) {
return String.format(FOUNDATION_STATUS, bedId);
}
public static String foundationFeatures(String bedId) {
return String.format(FOUNDATION_FEATURES, bedId);
}
public static String foundationPosition(String bedId) {
return String.format(FOUNDATION_POSITION, bedId);
}
public static String foundationPreset(String bedId) {
return String.format(FOUNDATION_PRESET, bedId);
}
public static String foundationOutlet(String bedId) {
return String.format(FOUNDATION_OUTLET, bedId);
}
private Endpoints() {

View File

@ -15,9 +15,19 @@ package org.openhab.binding.sleepiq.internal.api.impl;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationPosition;
import org.openhab.binding.sleepiq.internal.api.dto.SleepNumberRequest;
import org.openhab.binding.sleepiq.internal.api.dto.TimeSince;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationActuatorSpeedTypeAdapter;
import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationOutletOperationTypeAdapter;
import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationOutletTypeAdapter;
import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationPositionTypeAdapter;
import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationPresetTypeAdapter;
import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.SideTypeAdapter;
import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.SleepNumberRequestAdapter;
import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.TimeSinceTypeAdapter;
@ -43,6 +53,11 @@ public class GsonGenerator {
builder.registerTypeAdapter(TimeSince.class, new TimeSinceTypeAdapter());
builder.registerTypeAdapter(SleepNumberRequest.class, new SleepNumberRequestAdapter());
builder.registerTypeAdapter(Side.class, new SideTypeAdapter());
builder.registerTypeAdapter(FoundationPreset.class, new FoundationPresetTypeAdapter());
builder.registerTypeAdapter(FoundationActuatorSpeed.class, new FoundationActuatorSpeedTypeAdapter());
builder.registerTypeAdapter(FoundationOutlet.class, new FoundationOutletTypeAdapter());
builder.registerTypeAdapter(FoundationOutletOperation.class, new FoundationOutletOperationTypeAdapter());
builder.registerTypeAdapter(FoundationPosition.class, new FoundationPositionTypeAdapter());
if (prettyPrint) {
builder.setPrettyPrinting();

View File

@ -36,11 +36,17 @@ import org.openhab.binding.sleepiq.internal.api.Configuration;
import org.openhab.binding.sleepiq.internal.api.LoginException;
import org.openhab.binding.sleepiq.internal.api.ResponseFormatException;
import org.openhab.binding.sleepiq.internal.api.SleepIQ;
import org.openhab.binding.sleepiq.internal.api.SleepIQException;
import org.openhab.binding.sleepiq.internal.api.UnauthorizedException;
import org.openhab.binding.sleepiq.internal.api.dto.Bed;
import org.openhab.binding.sleepiq.internal.api.dto.BedsResponse;
import org.openhab.binding.sleepiq.internal.api.dto.Failure;
import org.openhab.binding.sleepiq.internal.api.dto.FamilyStatusResponse;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationOutletRequest;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationPositionRequest;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationPresetRequest;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
import org.openhab.binding.sleepiq.internal.api.dto.LoginInfo;
import org.openhab.binding.sleepiq.internal.api.dto.LoginRequest;
import org.openhab.binding.sleepiq.internal.api.dto.PauseModeResponse;
@ -48,6 +54,11 @@ import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
import org.openhab.binding.sleepiq.internal.api.dto.SleepNumberRequest;
import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
import org.openhab.binding.sleepiq.internal.api.dto.SleepersResponse;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import org.openhab.binding.sleepiq.internal.api.enums.SleepDataInterval;
import org.slf4j.Logger;
@ -60,6 +71,7 @@ import com.google.gson.JsonSyntaxException;
* The {@link SleepIQImpl} class handles all interactions with the sleepiq service.
*
* @author Gregory Moyer - Initial contribution
* @author Mark Hilbush - Added foundation functionality
*/
@NonNullByDefault
public class SleepIQImpl implements SleepIQ {
@ -225,7 +237,7 @@ public class SleepIQImpl implements SleepIQ {
String body = GSON.toJson(new SleepNumberRequest().withBedId(bedId).withSleepNumber(sleepNumber).withSide(side),
SleepNumberRequest.class);
logger.debug("SleepIQ: setSleepNumber: Request body={}", body);
cloudRequest(Endpoints.setSleepNumber(bedId), null, body);
cloudRequest(Endpoints.sleepNumber(bedId), null, body);
}
@Override
@ -234,7 +246,68 @@ public class SleepIQImpl implements SleepIQ {
logger.debug("SleepIQ: setPauseMode: command={}", pauseMode);
Map<String, String> requestParameters = new HashMap<>();
requestParameters.put("mode", pauseMode ? "on" : "off");
cloudRequest(Endpoints.setPauseMode(bedId), requestParameters, "");
cloudRequest(Endpoints.pauseMode(bedId), requestParameters, "");
}
@Override
public FoundationFeaturesResponse getFoundationFeatures(String bedId)
throws LoginException, ResponseFormatException, CommunicationException {
try {
String contentResponse = cloudRequest(Endpoints.foundationFeatures(bedId));
FoundationFeaturesResponse response = GSON.fromJson(contentResponse, FoundationFeaturesResponse.class);
if (response != null) {
logger.debug("SleepIQ: {}", response);
return response;
} else {
throw new ResponseFormatException("Failed to get a valid 'foundationFeatures' response from cloud");
}
} catch (JsonSyntaxException e) {
throw new ResponseFormatException("Failed to parse 'foundationFeatures' response");
}
}
@Override
public FoundationStatusResponse getFoundationStatus(String bedId) throws LoginException, SleepIQException {
try {
String contentResponse = cloudRequest(Endpoints.foundationStatus(bedId));
FoundationStatusResponse response = GSON.fromJson(contentResponse, FoundationStatusResponse.class);
if (response != null) {
logger.debug("SleepIQ: {}", response);
return response;
} else {
throw new ResponseFormatException("Failed to get a valid 'foundationStatus' response from cloud");
}
} catch (JsonSyntaxException e) {
throw new ResponseFormatException("Failed to parse 'foundationStatus' response");
}
}
@Override
public void setFoundationPreset(String bedId, Side side, FoundationPreset preset, FoundationActuatorSpeed speed)
throws LoginException, SleepIQException {
String body = GSON.toJson(new FoundationPresetRequest().withSide(side).withFoundationPreset(preset)
.withFoundationActuatorSpeed(speed), FoundationPresetRequest.class);
logger.debug("SleepIQ: setFoundationPreset: Request body={}", body);
cloudRequest(Endpoints.foundationPreset(bedId), null, body);
}
@Override
public void setFoundationPosition(String bedId, Side side, FoundationActuator actuator, int position,
FoundationActuatorSpeed speed) throws LoginException, SleepIQException {
String body = GSON.toJson(new FoundationPositionRequest().withSide(side).withPosition(position)
.withFoundationActuator(actuator).withFoundationActuatorSpeed(speed), FoundationPositionRequest.class);
logger.debug("SleepIQ: setFoundationPosition: Request body={}", body);
cloudRequest(Endpoints.foundationPosition(bedId), null, body);
}
@Override
public void setFoundationOutlet(String bedId, FoundationOutlet outlet, FoundationOutletOperation operation)
throws LoginException, SleepIQException {
String body = GSON.toJson(
new FoundationOutletRequest().withFoundationOutlet(outlet).withFoundationOutletOperation(operation),
FoundationOutletRequest.class);
logger.debug("SleepIQ: setFoundationOutlet: Request body={}", body);
cloudRequest(Endpoints.foundationOutlet(bedId), null, body);
}
private String cloudRequest(String endpoint)
@ -249,7 +322,7 @@ public class SleepIQImpl implements SleepIQ {
private String cloudRequest(String endpoint, @Nullable Map<String, String> parameters, @Nullable String body)
throws LoginException, ResponseFormatException, CommunicationException {
logger.debug("SleepIQ: cloudGetRequest: Invoke endpoint={}", endpoint);
logger.debug("SleepIQ: cloudRequest: Invoke endpoint={}", endpoint);
ContentResponse response = (body == null ? doGet(endpoint, parameters) : doPut(endpoint, parameters, body));
if (isUnauthorized(response)) {
logger.debug("SleepIQ: cloudGetRequest: UNAUTHORIZED, reset login");
@ -258,6 +331,7 @@ public class SleepIQImpl implements SleepIQ {
response = (body == null ? doGet(endpoint, parameters) : doPut(endpoint, parameters, body));
}
if (isNotOk(response)) {
logger.debug("SleepIQ.cloudRequest: ResponseFormatException on call to endpoint {}", endpoint);
throw new ResponseFormatException(String.format("Cloud API returned error: status=%d, message=%s",
response.getStatus(), HttpStatus.getCode(response.getStatus()).getMessage()));
}
@ -282,7 +356,7 @@ public class SleepIQImpl implements SleepIQ {
return doRequest(request, parameters);
}
private ContentResponse doRequest(Request request, @Nullable Map<String, String> parameters)
private synchronized ContentResponse doRequest(Request request, @Nullable Map<String, String> parameters)
throws CommunicationException {
try {
if (parameters != null) {
@ -291,12 +365,13 @@ public class SleepIQImpl implements SleepIQ {
}
}
addCookiesToRequest(request);
logger.debug("SleepIQ: doPut: request url={}", request.getURI());
logger.debug("SleepIQ: doRequest: request url={}", request.getURI());
ContentResponse response = request.send();
logger.trace("SleepIQ: doPut: status={} response={}", response.getStatus(), response.getContentAsString());
logger.trace("SleepIQ: doRequest: status={} response={}", response.getStatus(),
response.getContentAsString());
return response;
} catch (InterruptedException | TimeoutException | ExecutionException e) {
logger.debug("SleepIQ: doPut: Exception message={}", e.getMessage(), e);
logger.debug("SleepIQ: doRequest: Exception message={}", e.getMessage(), e);
throw new CommunicationException("Communication error while accessing API: " + e.getMessage());
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
import java.lang.reflect.Type;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* The {@link FoundationActuatorSpeedTypeAdapter} converts the FoundationActuatorSpeed enum
* to the format expected by the sleepiq API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class FoundationActuatorSpeedTypeAdapter implements JsonSerializer<FoundationActuatorSpeed> {
@Override
public JsonElement serialize(FoundationActuatorSpeed speed, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(speed.value());
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
import java.lang.reflect.Type;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* The {@link FoundationOutletOperationTypeAdapter} converts the FoundationOutletOperation enum to the
* format expected by the sleepiq API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class FoundationOutletOperationTypeAdapter implements JsonSerializer<FoundationOutletOperation> {
@Override
public JsonElement serialize(FoundationOutletOperation operation, Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(operation.value());
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
import java.lang.reflect.Type;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* The {@link FoundationOutletTypeAdapter} converts the FoundationOutlet enum to the
* format expected by the sleepiq API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class FoundationOutletTypeAdapter implements JsonSerializer<FoundationOutlet> {
@Override
public JsonElement serialize(FoundationOutlet outlet, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(outlet.value());
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
import java.lang.reflect.Type;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationPosition;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
/**
* The {@link FoundationPositionTypeAdapter} converts the hex string position from the
* foundation status response into an integer value. The position is returned by the API
* as a two character hex string (e.g. "3f"). The position can be between 0 and 100, inclusive.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class FoundationPositionTypeAdapter implements JsonDeserializer<FoundationPosition> {
@Override
public @Nullable FoundationPosition deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException {
try {
return new FoundationPosition().withFoundationPosition(Integer.parseInt(element.getAsString(), 16));
} catch (NumberFormatException e) {
// If we can't parse it, just set to 0
return new FoundationPosition().withFoundationPosition(Integer.valueOf(0));
}
}
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
import java.lang.reflect.Type;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* The {@link FoundationPresetTypeAdapter} converts the FoundationPreset enum to the
* format expected by the sleepiq API.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class FoundationPresetTypeAdapter
implements JsonDeserializer<FoundationPreset>, JsonSerializer<FoundationPreset> {
@Override
public JsonElement serialize(FoundationPreset preset, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(preset.value());
}
@Override
public @Nullable FoundationPreset deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException {
return FoundationPreset.convertFromStatus(element.getAsString());
}
}

View File

@ -36,7 +36,7 @@ public class SideTypeAdapter implements JsonDeserializer<Side>, JsonSerializer<S
@Override
public JsonElement serialize(Side side, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(side.value());
return new JsonPrimitive(side.equals(Side.LEFT) ? "L" : "R");
}
@Override

View File

@ -15,6 +15,7 @@ package org.openhab.binding.sleepiq.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sleepiq.internal.api.dto.BedStatus;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
/**
@ -27,10 +28,23 @@ public interface BedStatusListener {
/**
* This method will be called whenever a new bed status is received by the cloud handler.
*
* @param cloud the cloud service that can be used to gather additional information
* @param status the status returned from the cloud service
* @param status the bed status returned from the cloud service
*/
public void onBedStateChanged(BedStatus status);
/**
* This method will be called whenever a new foundation status is received by the cloud handler.
*
* @param status the foundation status returned from the cloud service
*/
public void onFoundationStateChanged(String bedId, FoundationStatusResponse status);
/**
* Determine if bed has a foundation installed.
*
* @return true if bed has a foundation; otherwise falase
*/
public boolean isFoundationInstalled();
public void onSleeperChanged(@Nullable Sleeper sleeper);
}

View File

@ -32,14 +32,22 @@ import org.openhab.binding.sleepiq.internal.SleepIQBindingConstants;
import org.openhab.binding.sleepiq.internal.SleepIQConfigStatusMessage;
import org.openhab.binding.sleepiq.internal.api.Configuration;
import org.openhab.binding.sleepiq.internal.api.LoginException;
import org.openhab.binding.sleepiq.internal.api.ResponseFormatException;
import org.openhab.binding.sleepiq.internal.api.SleepIQ;
import org.openhab.binding.sleepiq.internal.api.SleepIQException;
import org.openhab.binding.sleepiq.internal.api.UnauthorizedException;
import org.openhab.binding.sleepiq.internal.api.dto.Bed;
import org.openhab.binding.sleepiq.internal.api.dto.BedStatus;
import org.openhab.binding.sleepiq.internal.api.dto.FamilyStatusResponse;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import org.openhab.binding.sleepiq.internal.api.enums.SleepDataInterval;
import org.openhab.binding.sleepiq.internal.config.SleepIQCloudConfiguration;
@ -146,11 +154,15 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
*/
public void registerBedStatusListener(final BedStatusListener listener) {
bedStatusListeners.add(listener);
scheduler.execute(() -> {
/*
* Delay the initial sleeper and status update to give some time for the property update
* to determine if a foundation is installed.
*/
scheduler.schedule(() -> {
refreshSleepers();
refreshBedStatus();
updateListenerManagement();
});
}, 10L, TimeUnit.SECONDS);
}
/**
@ -188,11 +200,8 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
* @param bedId the bed identifier
* @return the identified {@link Bed} or <code>null</code> if no such bed exists
*/
public @Nullable Bed getBed(final @Nullable String bedId) {
public @Nullable Bed getBed(final String bedId) {
logger.debug("CloudHandler: Get bed object for bedId={}", bedId);
if (bedId == null) {
return null;
}
List<Bed> beds = getBeds();
if (beds != null) {
for (Bed bed : beds) {
@ -211,11 +220,8 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
* @param side the side of the bed
* @return the sleeper or null if sleeper not found
*/
public @Nullable Sleeper getSleeper(@Nullable String bedId, Side side) {
public @Nullable Sleeper getSleeper(String bedId, Side side) {
logger.debug("CloudHandler: Get sleeper object for bedId={}, side={}", bedId, side);
if (bedId == null) {
return null;
}
List<Sleeper> localSleepers = sleepers;
if (localSleepers != null) {
for (Sleeper sleeper : localSleepers) {
@ -234,10 +240,7 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
* @param sleepNumber the sleep number multiple of 5 between 5 and 100
* @param side the chamber to set
*/
public void setSleepNumber(@Nullable String bedId, Side side, int sleepNumber) {
if (bedId == null) {
return;
}
public void setSleepNumber(String bedId, Side side, int sleepNumber) {
try {
cloud.setSleepNumber(bedId, side, sleepNumber);
} catch (SleepIQException e) {
@ -251,10 +254,7 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
* @param bedId the bed identifier
* @param mode turn pause mode on or off
*/
public void setPauseMode(@Nullable String bedId, boolean mode) {
if (bedId == null) {
return;
}
public void setPauseMode(String bedId, boolean mode) {
try {
cloud.setPauseMode(bedId, mode);
} catch (SleepIQException e) {
@ -262,6 +262,99 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
}
}
/**
* Get the foundation features of the specified bed
*
* @param bedId the bed identifier
*/
public @Nullable FoundationFeaturesResponse getFoundationFeatures(String bedId) {
try {
return cloud.getFoundationFeatures(bedId);
} catch (ResponseFormatException e) {
logger.debug("CloudHandler: Unable to parse foundation features response for bed={}", bedId);
} catch (SleepIQException e) {
logger.debug("CloudHandler: Exception getting foundation features, bed={}", bedId, e);
}
return null;
}
/**
* Get the foundation status of the specified bed
*
* @param bedId the bed identifier
*/
public @Nullable FoundationStatusResponse getFoundationStatus(String bedId) {
try {
return cloud.getFoundationStatus(bedId);
} catch (ResponseFormatException e) {
logger.debug("CloudHandler: Unable to parse foundation status response for bed={}", bedId);
} catch (SleepIQException e) {
logger.debug("CloudHandler: Exception getting foundation status, bed={}: {}", bedId, e.getMessage());
}
return null;
}
/**
* Set a foundation adjustment preset.
*
* @param bedId the bed identifier
* @param side the side of the bed
* @param preset the preset to be applied
* @param speed the speed with which to make the adjustment
*/
public void setFoundationPreset(String bedId, Side side, FoundationPreset preset, FoundationActuatorSpeed speed) {
try {
cloud.setFoundationPreset(bedId, side, preset, speed);
} catch (ResponseFormatException e) {
logger.debug("CloudHandler: ResponseFormatException setting foundation preset for bed={}: {}", bedId,
e.getMessage());
} catch (SleepIQException e) {
logger.debug("CloudHandler: Exception setting the foundation preset for bed={}", bedId, e);
}
return;
}
/**
* Set a foundation position on head or foot of bed side.
*
* @param bedId the bed identifier
* @param side the side of the bed
* @param actuator the head or foot of the bed
* @param position the new position of the actuator
* @param speed the speed with which to make the adjustment
*/
public void setFoundationPosition(String bedId, Side side, FoundationActuator actuator, int position,
FoundationActuatorSpeed speed) {
try {
cloud.setFoundationPosition(bedId, side, actuator, position, speed);
} catch (ResponseFormatException e) {
logger.debug("CloudHandler: ResponseFormatException setting foundation position for bed={}: {}", bedId,
e.getMessage());
} catch (SleepIQException e) {
logger.debug("CloudHandler: Exception setting the foundation position for bed={}", bedId, e);
}
return;
}
/**
* Operate an outlet on the foundation.
*
* @param bedId the bed identifier
* @param outlet the outlet to operate
* @param operation the operation (On or Off) performed on the outlet
*/
public void setFoundationOutlet(String bedId, FoundationOutlet outlet, FoundationOutletOperation operation) {
try {
cloud.setFoundationOutlet(bedId, outlet, operation);
} catch (ResponseFormatException e) {
logger.debug("CloudHandler: ResponseFormatException setting the foundation outlet for bed={}: {}", bedId,
e.getMessage());
} catch (SleepIQException e) {
logger.debug("CloudHandler: Exception setting the foundation outlet for bed={}", bedId, e);
}
return;
}
/**
* Update the given properties with attributes of the given bed. If no properties are given, a new map will be
* created.
@ -289,22 +382,61 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
return properties;
}
/**
* Update the given foundation properties with features of the given bed foundation.
*
* @param bed the source of data
* @param features the foundation features to update (this may be <code>null</code>)
* @return the given map (or a new map if no map was given) with updated/set properties from the supplied bed
*/
public Map<String, String> updateFeatures(final String bedId, final @Nullable FoundationFeaturesResponse features,
Map<String, String> properties) {
if (features != null) {
logger.debug("CloudHandler: Updating foundation properties for bed={}", bedId);
properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION, "Installed");
properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HW_REV,
String.valueOf(features.getBoardHWRev()));
properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_IS_BOARD_AS_SINGLE,
features.isBoardAsSingle() ? "yes" : "no");
properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HAS_MASSAGE_AND_LIGHT,
features.hasMassageAndLight() ? "yes" : "no");
properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HAS_FOOT_CONTROL,
features.hasFootControl() ? "yes" : "no");
properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HAS_FOOT_WARMER,
features.hasFootWarming() ? "yes" : "no");
properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HAS_UNDER_BED_LIGHT,
features.hasUnderBedLight() ? "yes" : "no");
} else {
logger.debug("CloudHandler: Foundation not installed on bed={}", bedId);
properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION, "Not installed");
}
return properties;
}
/**
* Retrieve the latest status on all beds and update all registered listeners
* with bed status, sleepers and sleep data.
* with bed status, foundation status, sleepers and sleep data.
*/
private void refreshBedStatus() {
logger.debug("CloudHandler: Refreshing BED STATUS, updating chanels with status, sleepers, and sleep data");
try {
FamilyStatusResponse familyStatus = cloud.getFamilyStatus();
if (familyStatus != null && familyStatus.getBeds() != null) {
if (familyStatus.getBeds() != null) {
updateStatus(ThingStatus.ONLINE);
for (BedStatus bedStatus : familyStatus.getBeds()) {
logger.debug("CloudHandler: Informing listeners with bed status for bedId={}",
bedStatus.getBedId());
String bedId = bedStatus.getBedId();
logger.debug("CloudHandler: Informing listeners with bed status for bedId={}", bedId);
bedStatusListeners.stream().forEach(l -> l.onBedStateChanged(bedStatus));
}
// Get foundation status only if bed has a foundation
bedStatusListeners.stream().filter(l -> l.isFoundationInstalled()).forEach(l -> {
try {
l.onFoundationStateChanged(bedId, cloud.getFoundationStatus(bedStatus.getBedId()));
} catch (SleepIQException e) {
logger.debug("CloudHandler: Exception getting foundation status for bedId={}", bedId);
}
});
}
List<Sleeper> localSleepers = sleepers;
if (localSleepers != null) {
for (Sleeper sleeper : localSleepers) {
@ -358,7 +490,7 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
private void createCloudConnection() throws LoginException {
SleepIQCloudConfiguration bindingConfig = getConfigAs(SleepIQCloudConfiguration.class);
Configuration cloudConfig = new Configuration().withUsername(bindingConfig.username)
.withPassword(bindingConfig.password).withLogging(logger.isTraceEnabled());
.withPassword(bindingConfig.password);
logger.debug("CloudHandler: Authenticating at the SleepIQ cloud service");
cloud = SleepIQ.create(cloudConfig, httpClient);
cloud.login();

View File

@ -23,8 +23,15 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sleepiq.internal.api.dto.Bed;
import org.openhab.binding.sleepiq.internal.api.dto.BedSideStatus;
import org.openhab.binding.sleepiq.internal.api.dto.BedStatus;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import org.openhab.binding.sleepiq.internal.config.SleepIQBedConfiguration;
import org.openhab.core.library.types.DecimalType;
@ -60,13 +67,15 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
private final Logger logger = LoggerFactory.getLogger(SleepIQDualBedHandler.class);
private volatile @Nullable String bedId;
private volatile String bedId = "";
private @Nullable Sleeper sleeperLeft;
private @Nullable Sleeper sleeperRight;
private @Nullable BedStatus previousStatus;
private @Nullable FoundationFeaturesResponse foundationFeatures;
public SleepIQDualBedHandler(final Thing thing) {
super(thing);
}
@ -84,12 +93,15 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Incorrect bridge thing found");
return;
}
bedId = getConfigAs(SleepIQBedConfiguration.class).bedId;
if (bedId == null) {
String localBedId = getConfigAs(SleepIQBedConfiguration.class).bedId;
if (localBedId == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Bed id not found in configuration");
return;
}
bedId = localBedId;
// Assume the bed has a foundation until we determine otherwise
setFoundationFeatures(new FoundationFeaturesResponse());
logger.debug("BedHandler: Registering SleepIQ bed status listener for bedId={}", bedId);
SleepIQCloudHandler cloudHandler = (SleepIQCloudHandler) handler;
@ -111,7 +123,7 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
if (cloudHandler != null) {
cloudHandler.unregisterBedStatusListener(this);
}
bedId = null;
bedId = "";
}
@Override
@ -128,12 +140,14 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
// Channels will be refreshed automatically by cloud handler
return;
}
String channelId = channelUID.getId();
String groupId = channelUID.getGroupId();
switch (channelUID.getId()) {
switch (channelId) {
case CHANNEL_LEFT_SLEEP_NUMBER:
case CHANNEL_RIGHT_SLEEP_NUMBER:
if (command instanceof DecimalType) {
Side side = Side.convertFromGroup(channelUID.getGroupId());
Side side = Side.convertFromGroup(groupId);
logger.debug("BedHandler: Set sleepnumber to {} for bedId={}, side={}", command, bedId, side);
SleepIQCloudHandler cloudHandler = getCloudHandler();
if (cloudHandler != null) {
@ -144,7 +158,7 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
case CHANNEL_LEFT_PRIVACY_MODE:
case CHANNEL_RIGHT_PRIVACY_MODE:
if (command instanceof OnOffType) {
Side side = Side.convertFromGroup(channelUID.getGroupId());
Side side = Side.convertFromGroup(groupId);
logger.debug("BedHandler: Set sleepnumber to {} for bedId={}, side={}", command, bedId, side);
SleepIQCloudHandler cloudHandler = getCloudHandler();
if (cloudHandler != null) {
@ -152,6 +166,60 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
}
}
break;
case CHANNEL_LEFT_FOUNDATION_PRESET:
case CHANNEL_RIGHT_FOUNDATION_PRESET:
logger.debug("Received command {} on channel {} to set preset", command, channelUID);
if (isFoundationInstalled() && command instanceof DecimalType) {
try {
Side side = Side.convertFromGroup(groupId);
FoundationPreset preset = FoundationPreset.forValue(((DecimalType) command).intValue());
logger.debug("BedHandler: Set foundation preset to {} for bedId={}, side={}", command, bedId,
side);
SleepIQCloudHandler cloudHandler = getCloudHandler();
if (cloudHandler != null) {
cloudHandler.setFoundationPreset(bedId, side, preset, FoundationActuatorSpeed.SLOW);
}
} catch (IllegalArgumentException e) {
logger.info("BedHandler: Foundation preset invalid: {} must be 1-6", command);
}
}
break;
case CHANNEL_LEFT_NIGHT_STAND_OUTLET:
case CHANNEL_RIGHT_NIGHT_STAND_OUTLET:
case CHANNEL_LEFT_UNDER_BED_LIGHT:
case CHANNEL_RIGHT_UNDER_BED_LIGHT:
logger.debug("Received command {} on channel {} to control outlet", command, channelUID);
if (isFoundationInstalled() && command instanceof OnOffType) {
try {
logger.debug("BedHandler: Set foundation outlet channel {} to {} for bedId={}", channelId,
command, bedId);
FoundationOutlet outlet = FoundationOutlet.convertFromChannelId(channelId);
SleepIQCloudHandler cloudHandler = getCloudHandler();
if (cloudHandler != null) {
FoundationOutletOperation operation = command == OnOffType.ON ? FoundationOutletOperation.ON
: FoundationOutletOperation.OFF;
cloudHandler.setFoundationOutlet(bedId, outlet, operation);
}
} catch (IllegalArgumentException e) {
logger.info("BedHandler: Can't convert channel {} to foundation outlet", channelId);
}
}
break;
case CHANNEL_LEFT_POSITION_HEAD:
case CHANNEL_RIGHT_POSITION_HEAD:
logger.debug("Received command {} on channel {} to set position", command, channelUID);
if (groupId != null && isFoundationInstalled() && command instanceof DecimalType) {
setFoundationPosition(groupId, channelId, command);
}
case CHANNEL_LEFT_POSITION_FOOT:
case CHANNEL_RIGHT_POSITION_FOOT:
logger.debug("Received command {} on channel {} to set position", command, channelUID);
if (groupId != null && isFoundationInstalled() && isFoundationFootAdjustable()
&& command instanceof DecimalType) {
setFoundationPosition(groupId, channelId, command);
}
break;
}
}
@ -179,6 +247,7 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
return;
}
logger.debug("BedHandler: Updating bed status channels for bed {}", bedId);
BedStatus localPreviousStatus = previousStatus;
BedSideStatus left = status.getLeftSide();
updateState(CHANNEL_LEFT_IN_BED, left.isInBed() ? OnOffType.ON : OnOffType.OFF);
@ -187,8 +256,8 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
updateState(CHANNEL_LEFT_LAST_LINK, new StringType(left.getLastLink().toString()));
updateState(CHANNEL_LEFT_ALERT_ID, new DecimalType(left.getAlertId()));
updateState(CHANNEL_LEFT_ALERT_DETAILED_MESSAGE, new StringType(left.getAlertDetailedMessage()));
if (previousStatus != null) {
updateSleepDataChannels(previousStatus.getLeftSide(), left, sleeperLeft);
if (localPreviousStatus != null) {
updateSleepDataChannels(localPreviousStatus.getLeftSide(), left, sleeperLeft);
}
BedSideStatus right = status.getRightSide();
@ -198,13 +267,57 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
updateState(CHANNEL_RIGHT_LAST_LINK, new StringType(right.getLastLink().toString()));
updateState(CHANNEL_RIGHT_ALERT_ID, new DecimalType(right.getAlertId()));
updateState(CHANNEL_RIGHT_ALERT_DETAILED_MESSAGE, new StringType(right.getAlertDetailedMessage()));
if (previousStatus != null) {
updateSleepDataChannels(previousStatus.getRightSide(), right, sleeperRight);
if (localPreviousStatus != null) {
updateSleepDataChannels(localPreviousStatus.getRightSide(), right, sleeperRight);
}
previousStatus = status;
}
@Override
public void onFoundationStateChanged(String bedId, final @Nullable FoundationStatusResponse status) {
if (status == null || !bedId.equals(this.bedId)) {
return;
}
logger.debug("BedHandler: Updating foundation status channels for bed {}", bedId);
updateState(CHANNEL_LEFT_POSITION_HEAD, new DecimalType(status.getLeftHeadPosition()));
updateState(CHANNEL_LEFT_POSITION_FOOT, new DecimalType(status.getLeftFootPosition()));
updateState(CHANNEL_RIGHT_POSITION_HEAD, new DecimalType(status.getRightHeadPosition()));
updateState(CHANNEL_RIGHT_POSITION_FOOT, new DecimalType(status.getRightFootPosition()));
updateState(CHANNEL_LEFT_FOUNDATION_PRESET, new DecimalType(status.getCurrentPositionPresetLeft().value()));
updateState(CHANNEL_RIGHT_FOUNDATION_PRESET, new DecimalType(status.getCurrentPositionPresetRight().value()));
}
@Override
public boolean isFoundationInstalled() {
return foundationFeatures != null;
}
private void setFoundationFeatures(@Nullable FoundationFeaturesResponse features) {
foundationFeatures = features;
}
private boolean isFoundationFootAdjustable() {
FoundationFeaturesResponse localFoundationFeatures = foundationFeatures;
return localFoundationFeatures != null ? localFoundationFeatures.hasFootControl() : false;
}
private void setFoundationPosition(String groupId, String channelId, Command command) {
try {
logger.debug("BedHandler: Set foundation position channel {} to {} for bedId={}", channelId, command,
bedId);
Side side = Side.convertFromGroup(groupId);
FoundationActuator actuator = FoundationActuator.convertFromChannelId(channelId);
int position = ((DecimalType) command).intValue();
SleepIQCloudHandler cloudHandler = getCloudHandler();
if (cloudHandler != null) {
cloudHandler.setFoundationPosition(bedId, side, actuator, position, FoundationActuatorSpeed.SLOW);
}
} catch (IllegalArgumentException e) {
logger.info("BedHandler: Can't convert channel {} to foundation position", channelId);
}
}
private void updateSleepDataChannels(BedSideStatus previousSideStatus, BedSideStatus currentSideStatus,
@Nullable Sleeper sleeper) {
if (sleeper == null) {
@ -331,6 +444,13 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
return;
}
updateProperties(cloudHandler.updateProperties(bed, editProperties()));
logger.debug("BedHandler: Checking if foundation is installed for bedId={}", bedId);
if (isFoundationInstalled()) {
FoundationFeaturesResponse foundationFeaturesResponse = cloudHandler.getFoundationFeatures(bedId);
updateProperties(cloudHandler.updateFeatures(bedId, foundationFeaturesResponse, editProperties()));
setFoundationFeatures(foundationFeaturesResponse);
}
}
}

View File

@ -33,6 +33,18 @@ channel-type.sleepiq.alertIdType.label = Alert ID
channel-type.sleepiq.alertIdType.description = Identifier for an alert condition with the chamber
channel-type.sleepiq.firstNameType.label = First Name
channel-type.sleepiq.firstNameType.description = The first name of the sleeper
channel-type.sleepiq.foundationPositionFootType.label = Foot Position
channel-type.sleepiq.foundationPositionFootType.description = The position of the adjustable foot
channel-type.sleepiq.foundationPositionHeadType.label = Head Position
channel-type.sleepiq.foundationPositionHeadType.description = The position of the adjustable head
channel-type.sleepiq.foundationPresetType.label = Preset
channel-type.sleepiq.foundationPresetType.description = Bed position preset
channel-type.sleepiq.foundationPresetType.state.option.1 = Favorite
channel-type.sleepiq.foundationPresetType.state.option.2 = Read
channel-type.sleepiq.foundationPresetType.state.option.3 = Watch TV
channel-type.sleepiq.foundationPresetType.state.option.4 = Flat
channel-type.sleepiq.foundationPresetType.state.option.5 = Zero G
channel-type.sleepiq.foundationPresetType.state.option.6 = Snore
channel-type.sleepiq.inBedType.label = In Bed
channel-type.sleepiq.inBedType.description = The presence of a person or object on the chamber
channel-type.sleepiq.lastLinkType.label = Last Link
@ -43,6 +55,8 @@ channel-type.sleepiq.monthlyAverageRespirationRateType.label = Monthly Avg Respi
channel-type.sleepiq.monthlyAverageRespirationRateType.description = The average respiration rate for the past month
channel-type.sleepiq.monthlySleepIQType.label = Monthly Sleep IQ
channel-type.sleepiq.monthlySleepIQType.description = The overall Sleep Quotient for the past month
channel-type.sleepiq.nightStandOutletType.label = Night Stand
channel-type.sleepiq.nightStandOutletType.description = The night stand outlet
channel-type.sleepiq.pressureType.label = Pressure
channel-type.sleepiq.pressureType.description = The current pressure inside the chamber
channel-type.sleepiq.privacyMode.label = Privacy Mode
@ -69,6 +83,13 @@ channel-type.sleepiq.todaySleepRestfulSecondsType.label = Today's Sleep Restful
channel-type.sleepiq.todaySleepRestfulSecondsType.description = The total duration of restful sleep for today in seconds
channel-type.sleepiq.todaySleepRestlessSecondsType.label = Today's Sleep Restless Duration
channel-type.sleepiq.todaySleepRestlessSecondsType.description = The total duration of restless sleep for today in seconds
channel-type.sleepiq.underBedLightType.label = Under Bed Light
channel-type.sleepiq.underBedLightType.description = The under bed lighting
# channel types
channel-type.sleepiq.nightLightOutletType.label = Night Light
channel-type.sleepiq.nightLightOutletType.description = The night light outlet
# configuration messages

View File

@ -69,7 +69,7 @@
</channel-groups>
<properties>
<property name="thingTypeVersion">1</property>
<property name="thingTypeVersion">2</property>
</properties>
<config-description>
@ -105,6 +105,11 @@
<channel id="monthlySleepIQ" typeId="monthlySleepIQType"/>
<channel id="monthlyAverageHeartRate" typeId="monthlyAverageHeartRateType"/>
<channel id="monthlyAverageRespirationRate" typeId="monthlyAverageRespirationRateType"/>
<channel id="foundationPreset" typeId="foundationPresetType"/>
<channel id="foundationPositionHead" typeId="foundationPositionHeadType"/>
<channel id="foundationPositionFoot" typeId="foundationPositionFootType"/>
<channel id="nightStandOutlet" typeId="nightStandOutletType"/>
<channel id="underBedLight" typeId="underBedLightType"/>
</channels>
</channel-group-type>
@ -235,5 +240,44 @@
<description>The average respiration rate for the past month</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="foundationPresetType">
<item-type>Number</item-type>
<label>Preset</label>
<description>Bed position preset</description>
<state readOnly="false" min="1" max="6" step="1" pattern="%d">
<options>
<option value="1">Favorite</option>
<option value="2">Read</option>
<option value="3">Watch TV</option>
<option value="4">Flat</option>
<option value="5">Zero G</option>
<option value="6">Snore</option>
</options>
</state>
</channel-type>
<channel-type id="foundationPositionHeadType">
<item-type>Number</item-type>
<label>Head Position</label>
<description>The position of the adjustable head</description>
<state readOnly="false" min="0" max="100" step="1" pattern="%d"/>
</channel-type>
<channel-type id="foundationPositionFootType">
<item-type>Number</item-type>
<label>Foot Position</label>
<description>The position of the adjustable foot</description>
<state readOnly="false" min="0" max="100" step="1" pattern="%d"/>
</channel-type>
<channel-type id="nightStandOutletType">
<item-type>Switch</item-type>
<label>Night Stand</label>
<description>The night stand outlet</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="underBedLightType">
<item-type>Switch</item-type>
<label>Under Bed Light</label>
<description>The under bed lighting</description>
<state readOnly="false"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -20,6 +20,24 @@
</add-channel>
</instruction-set>
<instruction-set targetVersion="2">
<add-channel id="foundationPreset" groupIds="left,right">
<type>sleepiq:foundationPresetType</type>
</add-channel>
<add-channel id="foundationPositionHead" groupIds="left,right">
<type>sleepiq:foundationPositionHeadType</type>
</add-channel>
<add-channel id="foundationPositionFoot" groupIds="left,right">
<type>sleepiq:foundationPositionFootType</type>
</add-channel>
<add-channel id="nightStandOutlet" groupIds="left,right">
<type>sleepiq:nightStandOutletType</type>
</add-channel>
<add-channel id="underBedLight" groupIds="left,right">
<type>sleepiq:underBedLightType</type>
</add-channel>
</instruction-set>
</thing-type>
</update:update-descriptions>

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.api.model;
import static org.junit.jupiter.api.Assertions.*;
import java.io.FileReader;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.sleepiq.api.test.AbstractTest;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import org.openhab.binding.sleepiq.internal.api.impl.GsonGenerator;
import com.google.gson.Gson;
/**
* The {@link FoundationStatusResponseTest} tests deserialization of a foundation status
* response object.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class FoundationStatusResponseTest extends AbstractTest {
private static Gson gson = GsonGenerator.create(true);
@Test
public void testDeserializeAllFields() throws Exception {
try (FileReader reader = new FileReader(getTestDataFile("foundation-status-response.json"))) {
FoundationStatusResponse status = gson.fromJson(reader, FoundationStatusResponse.class);
assertNotNull(status);
assertEquals(0x0f, status.getLeftHeadPosition());
assertEquals(0x21, status.getLeftFootPosition());
assertEquals(0x0d, status.getRightHeadPosition());
assertEquals(0x04, status.getRightFootPosition());
assertEquals(FoundationPreset.SNORE, status.getCurrentPositionPresetLeft());
assertEquals(FoundationPreset.READ, status.getCurrentPositionPresetRight());
}
}
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.api.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.sleepiq.api.test.AbstractTest;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationOutletRequest;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
import org.openhab.binding.sleepiq.internal.api.impl.GsonGenerator;
import com.google.gson.Gson;
/**
* The {@link SetFoundationOutletTest} tests serialization of the foundation outlet request.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class SetFoundationOutletTest extends AbstractTest {
private static Gson gson = GsonGenerator.create(true);
@Test
public void testSerializeAllFields() throws Exception {
FoundationOutletRequest preset = new FoundationOutletRequest()
.withFoundationOutlet(FoundationOutlet.RIGHT_UNDER_BED_LIGHT)
.withFoundationOutletOperation(FoundationOutletOperation.ON);
assertEquals(readJson("set-foundation-outlet.json"), gson.toJson(preset));
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.api.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.sleepiq.api.test.AbstractTest;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationPositionRequest;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import org.openhab.binding.sleepiq.internal.api.impl.GsonGenerator;
import com.google.gson.Gson;
/**
* The {@link SetFoundationPositionTest} tests serialization of the foundation position request.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class SetFoundationPositionTest extends AbstractTest {
private static Gson gson = GsonGenerator.create(true);
@Test
public void testSerializeAllFields() throws Exception {
FoundationPositionRequest preset = new FoundationPositionRequest().withSide(Side.RIGHT).withPosition(75)
.withFoundationActuator(FoundationActuator.HEAD)
.withFoundationActuatorSpeed(FoundationActuatorSpeed.FAST);
assertEquals(readJson("set-foundation-position.json"), gson.toJson(preset));
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.sleepiq.api.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.sleepiq.api.test.AbstractTest;
import org.openhab.binding.sleepiq.internal.api.dto.FoundationPresetRequest;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
import org.openhab.binding.sleepiq.internal.api.enums.Side;
import org.openhab.binding.sleepiq.internal.api.impl.GsonGenerator;
import com.google.gson.Gson;
/**
* The {@link SetFoundationPresetTest} tests serialization of the foundation preset request.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class SetFoundationPresetTest extends AbstractTest {
private static Gson gson = GsonGenerator.create(true);
@Test
public void testSerializeAllFields() throws Exception {
FoundationPresetRequest preset = new FoundationPresetRequest().withSide(Side.RIGHT)
.withFoundationPreset(FoundationPreset.WATCH_TV)
.withFoundationActuatorSpeed(FoundationActuatorSpeed.FAST);
assertEquals(readJson("set-foundation-preset.json"), gson.toJson(preset));
}
}

View File

@ -34,17 +34,6 @@ import com.google.gson.Gson;
public class SleeperTest extends AbstractTest {
private static Gson gson = GsonGenerator.create(true);
@Test
public void testSerializeAllFields() throws Exception {
Sleeper sleeper = new Sleeper().withAccountId("-5555555555555555555").withAccountOwner(true).withActive(true)
.withAvatar("").withBedId("-9999999999999999999").withBirthMonth(6).withBirthYear("1970")
.withChild(false).withDuration("").withEmail("alice@domain.com").withEmailValidated(true)
.withFirstName("Alice").withHeight(64).withLastLogin("2017-02-17 20:19:36 CST").withLicenseVersion(6L)
.withMale(false).withSide(Side.RIGHT).withSleeperId("-1111111111111111111").withSleepGoal(450)
.withTimezone("US/Pacific").withUsername("alice@domain.com").withWeight(110).withZipCode("90210");
assertEquals(readJson("sleeper.json"), gson.toJson(sleeper));
}
@Test
public void testSerializeLastLoginNull() throws Exception {
Sleeper sleeper = new Sleeper().withLastLogin("null");

View File

@ -0,0 +1,30 @@
{
"fsType": "Single",
"fsNeedsHoming": false,
"fsIsMoving": false,
"fsStatusSummary": "48",
"fsConfigured": true,
"fsCurrentPositionPreset": "66",
"fsTimerPositionPreset": "00",
"fsCurrentPositionPresetLeft": "Snore",
"fsLeftHeadPosition": "0f",
"fsLeftFootPosition": "21",
"fsLeftHeadActuatorMotorStatus": "08",
"fsLeftFootActuatorMotorStatus": "00",
"fsTimerPositionPresetLeft": "No timer running, thus no preset to active",
"fsLeftPositionTimerMSB": "00",
"fsLeftPositionTimerLSB": "00",
"fsCurrentPositionPresetRight": "Read",
"fsRightHeadPosition": "0d",
"fsRightFootPosition": "04",
"fsRightHeadActuatorMotorStatus": "08",
"fsRightFootActuatorMotorStatus": "00",
"fsTimerPositionPresetRight": "No timer running, thus no preset to active",
"fsRightPositionTimerMSB": "00",
"fsRightPositionTimerLSB": "00",
"fsOutletsOn": true,
"fsTimedOutletsOn": false
}

View File

@ -0,0 +1,6 @@
{
"side": "R",
"position": 75,
"actuator": "H",
"speed": 0
}

View File

@ -0,0 +1,5 @@
{
"side": "R",
"preset": 3,
"speed": 0
}