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
|-----------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------|
| 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 |
| 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
Here is a sample item configuration:
```java
-Switch MasterBR_SleepIQ_InBed_Alice "In Bed [%s]" { channel="sleepiq:dualBed:1:master:left#inBed" }
-Number MasterBR_SleepIQ_SleepNumber_Alice "Sleep Number [%s]" { channel="sleepiq:dualBed:1:master:left#sleepNumber" }
-Number:Time MasterBR_SleepIQ_SleepGoal_Alice "Sleep Goal [%d min]" { channel="sleepiq:dualBed:1:master:left#sleepGoalMinutes"
-Number MasterBR_SleepIQ_Pressure_Alice "Pressure [%s]" { channel="sleepiq:dualBed:1:master:left#pressure" }
-Switch MasterBR_SleepIQ_PrivacyMode_Alice "Privacy Mode [%s]" { channel="sleepiq:dualBed:1:master:left#privacyMode" }
-String MasterBR_SleepIQ_LastLink_Alice "Last Update [%s]" { channel="sleepiq:dualBed:1:master:left#lastLink" }
-Number MasterBR_SleepIQ_AlertId_Alice "Alert ID [%s]" { channel="sleepiq:dualBed:1:master:left#alertId" }
-String MasterBR_SleepIQ_AlertMessage_Alice "Alert Message [%s]" { channel="sleepiq:dualBed:1:master:left#alertDetailedMessage" }
-Number MasterBR_SleepIQ_DailySleepIQ_Alice "Daily Sleep IQ [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepIQ" }
-Number MasterBR_SleepIQ_DailyHeartRate_Alice "Daily Heart Rate [%.0f]" { channel="sleepiq:dualBed:1:master:left#todayAverageHeartRate" }
-Number MasterBR_SleepIQ_DailyRespRate_Alice "Daily Respiration Rate [%.0f]" { channel="sleepiq:dualBed:1:master:left#todayAverageRespirationRate"}
-String MasterBR_SleepIQ_DailyMessage_Alice "Daily Message [%s]" { channel="sleepiq:dualBed:1:master:left#todayMessage"}
-Number:Time MasterBR_SleepIQ_DailyDuration_Alice "Daily Sleep Duration [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepDurationSeconds"}
-Number:Time MasterBR_SleepIQ_DailyInBed_Alice "Daily Sleep In Bed [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepInBedSeconds"}
-Number:Time MasterBR_SleepIQ_DailyOutOfBed_Alice "Daily Sleep Out Of Bed [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepOutOfBedSeconds"}
-Number:Time MasterBR_SleepIQ_DailyRestful_Alice "Daily Sleep Restful [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepRestfulSeconds"}
-Number:Time MasterBR_SleepIQ_DailyRestless_Alice "Daily Sleep Restless [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepRestlessSeconds"}
-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"}
-
-
-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: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" }
-Number MasterBR_SleepIQ_AlertId_Bob "Alert ID [%s]" { channel="sleepiq:dualBed:1:master:right#alertId" }
-String MasterBR_SleepIQ_AlertMessage_Bob "Alert Message [%s]" { channel="sleepiq:dualBed:1:master:right#alertDetailedMessage" }
-Number MasterBR_SleepIQ_DailySleepIQ_Bob "Daily Sleep IQ [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepIQ" }
-Number MasterBR_SleepIQ_DailyHeartRate_Bob "Daily Heart Rate [%.0f]" { channel="sleepiq:dualBed:1:master:right#todayAverageHeartRate" }
-Number MasterBR_SleepIQ_DailyRespRate_Bob "Daily Respiration Rate [%.0f]" { channel="sleepiq:dualBed:1:master:right#todayAverageRespirationRate"}
-String MasterBR_SleepIQ_DailyMessage_Bob "Daily Message [%s]" { channel="sleepiq:dualBed:1:master:right#todayMessage"}
-Number:Time MasterBR_SleepIQ_DailyDuration_Bob "Daily Sleep Duration [%d s]" { channel="sleepiq:dualBed:1:master:right#todaySleepDurationSeconds"}
-Number:Time MasterBR_SleepIQ_DailyInBed_Bob "Daily Sleep In Bed [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepInBedSeconds"}
-Number:Time MasterBR_SleepIQ_DailyOutOfBed_Bob "Daily Sleep Out Of Bed [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepOutOfBedSeconds"}
-Number:Time MasterBR_SleepIQ_DailyRestful_Bob "Daily Sleep Restful [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepRestfulSeconds"}
-Number:Time MasterBR_SleepIQ_DailyRestless_Bob "Daily Sleep Restless [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepRestlessSeconds"}
-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"}
+Switch MasterBR_SleepIQ_InBed_Alice "In Bed [%s]" { channel="sleepiq:dualBed:1:master:left#inBed" }
+Number MasterBR_SleepIQ_SleepNumber_Alice "Sleep Number [%s]" { channel="sleepiq:dualBed:1:master:left#sleepNumber" }
+Number:Time MasterBR_SleepIQ_SleepGoal_Alice "Sleep Goal [%d min]" { channel="sleepiq:dualBed:1:master:left#sleepGoalMinutes"
+Number MasterBR_SleepIQ_Pressure_Alice "Pressure [%s]" { channel="sleepiq:dualBed:1:master:left#pressure" }
+Switch MasterBR_SleepIQ_PrivacyMode_Alice "Privacy Mode [%s]" { channel="sleepiq:dualBed:1:master:left#privacyMode" }
+String MasterBR_SleepIQ_LastLink_Alice "Last Update [%s]" { channel="sleepiq:dualBed:1:master:left#lastLink" }
+Number MasterBR_SleepIQ_AlertId_Alice "Alert ID [%s]" { channel="sleepiq:dualBed:1:master:left#alertId" }
+String MasterBR_SleepIQ_AlertMessage_Alice "Alert Message [%s]" { channel="sleepiq:dualBed:1:master:left#alertDetailedMessage" }
+Number MasterBR_SleepIQ_DailySleepIQ_Alice "Daily Sleep IQ [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepIQ" }
+Number MasterBR_SleepIQ_DailyHeartRate_Alice "Daily Heart Rate [%.0f]" { channel="sleepiq:dualBed:1:master:left#todayAverageHeartRate" }
+Number MasterBR_SleepIQ_DailyRespRate_Alice "Daily Respiration Rate [%.0f]" { channel="sleepiq:dualBed:1:master:left#todayAverageRespirationRate"}
+String MasterBR_SleepIQ_DailyMessage_Alice "Daily Message [%s]" { channel="sleepiq:dualBed:1:master:left#todayMessage"}
+Number:Time MasterBR_SleepIQ_DailyDuration_Alice "Daily Sleep Duration [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepDurationSeconds"}
+Number:Time MasterBR_SleepIQ_DailyInBed_Alice "Daily Sleep In Bed [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepInBedSeconds"}
+Number:Time MasterBR_SleepIQ_DailyOutOfBed_Alice "Daily Sleep Out Of Bed [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepOutOfBedSeconds"}
+Number:Time MasterBR_SleepIQ_DailyRestful_Alice "Daily Sleep Restful [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepRestfulSeconds"}
+Number:Time MasterBR_SleepIQ_DailyRestless_Alice "Daily Sleep Restless [%.0f]" { channel="sleepiq:dualBed:1:master:left#todaySleepRestlessSeconds"}
+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_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" }
+Number MasterBR_SleepIQ_AlertId_Bob "Alert ID [%s]" { channel="sleepiq:dualBed:1:master:right#alertId" }
+String MasterBR_SleepIQ_AlertMessage_Bob "Alert Message [%s]" { channel="sleepiq:dualBed:1:master:right#alertDetailedMessage" }
+Number MasterBR_SleepIQ_DailySleepIQ_Bob "Daily Sleep IQ [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepIQ" }
+Number MasterBR_SleepIQ_DailyHeartRate_Bob "Daily Heart Rate [%.0f]" { channel="sleepiq:dualBed:1:master:right#todayAverageHeartRate" }
+Number MasterBR_SleepIQ_DailyRespRate_Bob "Daily Respiration Rate [%.0f]" { channel="sleepiq:dualBed:1:master:right#todayAverageRespirationRate"}
+String MasterBR_SleepIQ_DailyMessage_Bob "Daily Message [%s]" { channel="sleepiq:dualBed:1:master:right#todayMessage"}
+Number:Time MasterBR_SleepIQ_DailyDuration_Bob "Daily Sleep Duration [%d s]" { channel="sleepiq:dualBed:1:master:right#todaySleepDurationSeconds"}
+Number:Time MasterBR_SleepIQ_DailyInBed_Bob "Daily Sleep In Bed [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepInBedSeconds"}
+Number:Time MasterBR_SleepIQ_DailyOutOfBed_Bob "Daily Sleep Out Of Bed [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepOutOfBedSeconds"}
+Number:Time MasterBR_SleepIQ_DailyRestful_Bob "Daily Sleep Restful [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepRestfulSeconds"}
+Number:Time MasterBR_SleepIQ_DailyRestless_Bob "Daily Sleep Restless [%.0f]" { channel="sleepiq:dualBed:1:master:right#todaySleepRestlessSeconds"}
+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"}
```
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";
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";
}
private URI baseUri = URI.create("https://api.sleepiq.sleepnumber.com");
- private boolean logging = false;
-
/**
* Get the username on the account.
*
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;
- }
}
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;
* 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 {
*/
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.
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
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;
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() {
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;
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();
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;
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;
* 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 {
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
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)
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");
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()));
}
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) {
}
}
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());
}
}
--- /dev/null
+/**
+ * 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());
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
@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
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;
/**
/**
* 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);
}
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;
*/
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);
}
/**
* @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) {
* @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) {
* @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) {
* @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) {
}
}
+ /**
+ * 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.
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) {
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();
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;
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);
}
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;
if (cloudHandler != null) {
cloudHandler.unregisterBedStatusListener(this);
}
- bedId = null;
+ bedId = "";
}
@Override
// 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) {
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) {
}
}
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;
}
}
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);
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();
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) {
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);
+ }
}
}
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
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
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
</channel-groups>
<properties>
- <property name="thingTypeVersion">1</property>
+ <property name="thingTypeVersion">2</property>
</properties>
<config-description>
<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>
<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>
</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>
--- /dev/null
+/**
+ * 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());
+ }
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
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");
--- /dev/null
+{
+ "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
+}
\ No newline at end of file
--- /dev/null
+{
+ "outletId": 3,
+ "setting": 1
+}
\ No newline at end of file
--- /dev/null
+{
+ "side": "R",
+ "position": 75,
+ "actuator": "H",
+ "speed": 0
+}
\ No newline at end of file
--- /dev/null
+{
+ "side": "R",
+ "preset": 3,
+ "speed": 0
+}
\ No newline at end of file