]> git.basschouten.com Git - openhab-addons.git/commitdiff
[sleepiq] Add functionality to control the bed foundation (#14714)
authorMark Hilbush <mark@hilbush.com>
Tue, 28 Mar 2023 15:27:31 +0000 (11:27 -0400)
committerGitHub <noreply@github.com>
Tue, 28 Mar 2023 15:27:31 +0000 (17:27 +0200)
* Add foundation functionality

Signed-off-by: Mark Hilbush <mark@hilbush.com>
40 files changed:
bundles/org.openhab.binding.sleepiq/README.md
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/SleepIQBindingConstants.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/Configuration.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/SleepIQ.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationAdjustmentRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationFeaturesResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationOutletRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPosition.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPositionRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPresetRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationStatusResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationActuator.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationActuatorSpeed.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationOutlet.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationOutletOperation.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationPreset.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/Endpoints.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/GsonGenerator.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/SleepIQImpl.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationActuatorSpeedTypeAdapter.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationOutletOperationTypeAdapter.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationOutletTypeAdapter.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationPositionTypeAdapter.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationPresetTypeAdapter.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/SideTypeAdapter.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/handler/BedStatusListener.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/handler/SleepIQCloudHandler.java
bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/handler/SleepIQDualBedHandler.java
bundles/org.openhab.binding.sleepiq/src/main/resources/OH-INF/i18n/sleepiq.properties
bundles/org.openhab.binding.sleepiq/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.sleepiq/src/main/resources/OH-INF/update/instructions.xml
bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/FoundationStatusResponseTest.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationOutletTest.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationPositionTest.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationPresetTest.java [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SleeperTest.java
bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/foundation-status-response.json [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-outlet.json [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-position.json [new file with mode: 0644]
bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-preset.json [new file with mode: 0644]

index c01f408be61d48261a3febbe7db3466c76b95c1e..be1302c9fd3f89dd525a682173ac79c98b9fefb3 100644 (file)
@@ -19,6 +19,9 @@ Currently, only dual-chamber beds are supported by this binding.
 
 The SleepIQ cloud thing must be added manually with the username and password used to register with the service.
 After that, beds are discovered automatically by querying the service.
+If the bed has a foundation, it will be auto-discovered by the binding.
+If a foundation is discovered, the channels for controlling presets, position and outlets can be operated.
+The thing properties will show whether or not a foundation is installed.
 
 ## Binding Configuration
 
@@ -72,7 +75,6 @@ All channels within this group are read-only, except for the sleepNumber and pri
 |-----------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------|
 | inBed                             | Switch         | The presence of a person or object on the chamber  |
 | sleepNumber                       | Number         | The Sleep Number setting of the chamber. Set the sleep number of the chamber by sending a command to the sleepNumber channel with a value between 5 and 100. The value must be a multiple of 5  |
-
 | sleepGoalMinutes                  | Number:Time    | The person's sleep goal in minutes |
 | pressure                          | Number         | The current pressure inside the chamber |
 | privacyMode                       | Switch         | Enable or disable privacy mode |
@@ -91,52 +93,66 @@ All channels within this group are read-only, except for the sleepNumber and pri
 | monthlySleepIQ                    | Number         | The average Sleep IQ score for the current month |
 | monthlyAverageHeartRate           | Number         | The average heart rate for the current month |
 | monthlyAverageRespirationRate     | Number         | The average respiration rate for the current month |
+| foundationPreset                  | Number         | Sets the head and foot position to one of the 6 available presets (1-6) (foundation required) |
+| foundationPositionHead            | Dimmer         | Sets the head position (foundation required) |
+| foundationPositionFoot            | Dimmer         | Sets the foot position (foundation required) |
+| nightStandOutlet                  | Switch         | Turn on/off the night stand outlet (foundation required) |
+| underBedLight                     | Switch         | Turn on/off the under bed light (foundation required) |
 
 ## Items
 
 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"}
 ```
index dc1191c97cef31eb830ec3ea9acc078687b911d0..844d8bd9d42aa1285355399edce32b7417215e3b 100644 (file)
@@ -97,6 +97,21 @@ public class SleepIQBindingConstants {
     public static final String CHANNEL_LEFT_MONTHLY_AVG_RESPIRATION_RATE = "left#monthlyAverageRespirationRate";
     public static final String CHANNEL_RIGHT_MONTHLY_AVG_RESPIRATION_RATE = "right#monthlyAverageRespirationRate";
 
+    public static final String CHANNEL_LEFT_FOUNDATION_PRESET = "left#foundationPreset";
+    public static final String CHANNEL_RIGHT_FOUNDATION_PRESET = "right#foundationPreset";
+
+    public static final String CHANNEL_LEFT_POSITION_HEAD = "left#foundationPositionHead";
+    public static final String CHANNEL_RIGHT_POSITION_HEAD = "right#foundationPositionHead";
+
+    public static final String CHANNEL_LEFT_POSITION_FOOT = "left#foundationPositionFoot";
+    public static final String CHANNEL_RIGHT_POSITION_FOOT = "right#foundationPositionFoot";
+
+    public static final String CHANNEL_LEFT_NIGHT_STAND_OUTLET = "left#nightStandOutlet";
+    public static final String CHANNEL_RIGHT_NIGHT_STAND_OUTLET = "right#nightStandOutlet";
+
+    public static final String CHANNEL_LEFT_UNDER_BED_LIGHT = "left#underBedLight";
+    public static final String CHANNEL_RIGHT_UNDER_BED_LIGHT = "right#underBedLight";
+
     // List of non-standard Properties
     public static final String PROPERTY_BASE = "base";
     public static final String PROPERTY_KIDS_BED = "kidsBed";
@@ -105,4 +120,11 @@ public class SleepIQBindingConstants {
     public static final String PROPERTY_PURCHASE_DATE = "purchaseDate";
     public static final String PROPERTY_SIZE = "size";
     public static final String PROPERTY_SKU = "sku";
+    public static final String PROPERTY_FOUNDATION = "foundation";
+    public static final String PROPERTY_FOUNDATION_IS_BOARD_AS_SINGLE = "foundationIsBoardAsSingle";
+    public static final String PROPERTY_FOUNDATION_HAS_MASSAGE_AND_LIGHT = "foundationHasMasssageAndLight";
+    public static final String PROPERTY_FOUNDATION_HAS_FOOT_CONTROL = "foundationHasFootControl";
+    public static final String PROPERTY_FOUNDATION_HAS_FOOT_WARMER = "foundationHasFootWarmer";
+    public static final String PROPERTY_FOUNDATION_HAS_UNDER_BED_LIGHT = "foundationHasUnderBedLight";
+    public static final String PROPERTY_FOUNDATION_HW_REV = "foundationHwRev";
 }
index 861b4a11b7a4bdb79fba92596b6cfeed88808f29..629c40571e13af4ebf8ed6e6e52d938f137672b1 100644 (file)
@@ -28,8 +28,6 @@ public class Configuration {
 
     private URI baseUri = URI.create("https://api.sleepiq.sleepnumber.com");
 
-    private boolean logging = false;
-
     /**
      * Get the username on the account.
      *
@@ -128,39 +126,4 @@ public class Configuration {
         setBaseUri(baseUri);
         return this;
     }
-
-    /**
-     * Get the logging flag.
-     *
-     * @return the logging flag
-     */
-    public boolean isLogging() {
-        return logging;
-    }
-
-    /**
-     * Set the logging flag. When this is set to <code>true</code>, all requests
-     * and responses will be logged at the {@link Level#INFO} level. <b>This
-     * includes usernames and passwords!</b>
-     *
-     * @param logging
-     *            the value to set
-     */
-    public void setLogging(boolean logging) {
-        this.logging = logging;
-    }
-
-    /**
-     * Set the logging flag. When this is set to <code>true</code>, all requests
-     * and responses will be logged at the {@link Level#INFO} level. <b>This
-     * includes usernames and passwords!</b>
-     *
-     * @param logging
-     *            the value to set
-     * @return this configuration instance
-     */
-    public Configuration withLogging(boolean logging) {
-        setLogging(logging);
-        return this;
-    }
 }
index ca6a9bf74d8744977896e556006f4e11f4d6fbda..8778e685b6091f977133c7b3c80433f18d051596 100644 (file)
@@ -19,10 +19,17 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.jetty.client.HttpClient;
 import org.openhab.binding.sleepiq.internal.api.dto.Bed;
 import org.openhab.binding.sleepiq.internal.api.dto.FamilyStatusResponse;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.LoginInfo;
 import org.openhab.binding.sleepiq.internal.api.dto.PauseModeResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
 import org.openhab.binding.sleepiq.internal.api.enums.Side;
 import org.openhab.binding.sleepiq.internal.api.enums.SleepDataInterval;
 import org.openhab.binding.sleepiq.internal.api.impl.SleepIQImpl;
@@ -31,6 +38,7 @@ import org.openhab.binding.sleepiq.internal.api.impl.SleepIQImpl;
  * This interface is the main API to access the SleepIQ system.
  *
  * @author Gregory Moyer - Initial contribution
+ * @author Mark Hilbush - Added foundation functionality
  */
 @NonNullByDefault
 public interface SleepIQ {
@@ -133,6 +141,68 @@ public interface SleepIQ {
      */
     public void setPauseMode(String bedId, boolean command) throws LoginException, SleepIQException;
 
+    /**
+     * Get the foundation features for a bed
+     *
+     * @param bedId the unique identifier of the bed
+     *
+     * @throws LoginException
+     * @throws SleepIQException
+     */
+    public FoundationFeaturesResponse getFoundationFeatures(String bedId) throws LoginException, SleepIQException;
+
+    /**
+     * Get the foundation status for a bed
+     *
+     * @param bedId the unique identifier of the bed
+     *
+     * @throws LoginException
+     * @throws SleepIQException
+     */
+    public FoundationStatusResponse getFoundationStatus(String bedId) throws LoginException, SleepIQException;
+
+    /**
+     * Set a foundation preset for the side of a bed
+     *
+     * @param bedId the unique identifier of the bed
+     * @param side the chamber of the bed
+     * @param preset the foundation preset
+     * @param speed the speed with which the adjustment is made
+     *
+     * @throws LoginException
+     * @throws SleepIQException
+     */
+    public void setFoundationPreset(String bedId, Side side, FoundationPreset preset, FoundationActuatorSpeed speed)
+            throws LoginException, SleepIQException;
+
+    /**
+     * Set a foundation position for the side of a bed
+     *
+     * @param bedId the unique identifier of the bed
+     * @param side the chamber of the bed
+     * @param actuator the head or foot of the bed
+     * @param position the new position of the actuator
+     * @param speed the speed with which the adjustment is made
+     *
+     * @throws LoginException
+     * @throws SleepIQException
+     */
+    public void setFoundationPosition(String bedId, Side side, FoundationActuator actuator, int position,
+            FoundationActuatorSpeed speed) throws LoginException, SleepIQException;
+
+    /**
+     * Turn a foundation outlet on or off
+     *
+     * @param bedId the unique identifier of the bed
+     * @param outlet the foundation outlet to control
+     * @param operation set the outlet on or off
+     *
+     * @throws LoginException
+     * @throws SleepIQException
+     */
+    public void setFoundationOutlet(String bedId, FoundationOutlet outlet, FoundationOutletOperation operation)
+            throws LoginException, SleepIQException;
+
     /**
      * Create a default implementation instance of this interface. Each call to
      * this method will create a new object.
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationAdjustmentRequest.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationAdjustmentRequest.java
new file mode 100644 (file)
index 0000000..b90bb04
--- /dev/null
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.dto;
+
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.Side;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link FoundationAdjustmentRequest} is used control the actuator
+ * on the side of a bed.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+public class FoundationAdjustmentRequest {
+    @SerializedName("side")
+    private Side side;
+
+    @SerializedName("actuator")
+    private FoundationActuator actuator;
+
+    @SerializedName("position")
+    private int position;
+
+    @SerializedName("speed")
+    private FoundationActuatorSpeed speed;
+
+    public Side getSide() {
+        return side;
+    }
+
+    public void setSide(Side side) {
+        this.side = side;
+    }
+
+    public FoundationAdjustmentRequest withSide(Side side) {
+        setSide(side);
+        return this;
+    }
+
+    public FoundationActuator getFoundationActuator() {
+        return actuator;
+    }
+
+    public void setFoundationActuator(FoundationActuator actuator) {
+        this.actuator = actuator;
+    }
+
+    public FoundationAdjustmentRequest withFoundationActuator(FoundationActuator actuator) {
+        setFoundationActuator(actuator);
+        return this;
+    }
+
+    public int getFoundationPosition() {
+        return position;
+    }
+
+    public void setFoundationPosition(int position) {
+        this.position = position;
+    }
+
+    public FoundationAdjustmentRequest withFoundationPosition(int position) {
+        setFoundationPosition(position);
+        return this;
+    }
+
+    public FoundationActuatorSpeed getFoundationActuartorSpeed() {
+        return speed;
+    }
+
+    public void setFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
+        this.speed = speed;
+    }
+
+    public FoundationAdjustmentRequest withFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
+        setFoundationActuatorSpeed(speed);
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SleepNumberRequest [side=");
+        builder.append(side);
+        builder.append(", actuator=");
+        builder.append(actuator);
+        builder.append(", position=");
+        builder.append(position);
+        builder.append(", speed=");
+        builder.append(speed);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationFeaturesResponse.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationFeaturesResponse.java
new file mode 100644 (file)
index 0000000..2de5e67
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link FoundationFeaturesResponse} holds the features of the foundation
+ * returned from the sleepiq API.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+public class FoundationFeaturesResponse {
+    private static final int BOARD_AS_SINGLE = 0x01;
+    private static final int MASSAGE_AND_LIGHT = 0x02;
+    private static final int FOOT_CONTROL = 0x04;
+    private static final int FOOT_WARMING = 0x08;
+    private static final int UNDER_BED_LIGHT = 0x10;
+
+    @SerializedName("fsBedType")
+    private int bedType;
+
+    @SerializedName("fsBoardFaults")
+    private int boardFaults;
+
+    @SerializedName("fsBoardFeatures")
+    private int boardFeatures;
+
+    @SerializedName("fsBoardHWRevisionCode")
+    private int boardHWRev;
+
+    @SerializedName("fsBoardStatus")
+    private int boardStatus;
+
+    @SerializedName("fsLeftUnderbedLightPWM")
+    private int leftUnderbedLightPWM;
+
+    @SerializedName("fsRightUnderbedLightPWM")
+    private int rightUnderbedLightPWM;
+
+    public int getBedType() {
+        return bedType;
+    }
+
+    public int getBoardFaults() {
+        return boardFaults;
+    }
+
+    public int getBoardFeatures() {
+        return boardFeatures;
+    }
+
+    public boolean isBoardAsSingle() {
+        return (boardFeatures & BOARD_AS_SINGLE) > 0;
+    }
+
+    public boolean hasMassageAndLight() {
+        return (boardFeatures & MASSAGE_AND_LIGHT) > 0;
+    }
+
+    public boolean hasFootControl() {
+        return (boardFeatures & FOOT_CONTROL) > 0;
+    }
+
+    public boolean hasFootWarming() {
+        return (boardFeatures & FOOT_WARMING) > 0;
+    }
+
+    public boolean hasUnderBedLight() {
+        return (boardFeatures & UNDER_BED_LIGHT) > 0;
+    }
+
+    public int getBoardHWRev() {
+        return boardHWRev;
+    }
+
+    public int getBoardStatus() {
+        return boardStatus;
+    }
+
+    public int getLeftUnderbedLightPWM() {
+        return leftUnderbedLightPWM;
+    }
+
+    public int getRightUnderbedLightPWM() {
+        return rightUnderbedLightPWM;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("FoundationFeaturesResponse [");
+        builder.append("bedType=");
+        builder.append(bedType);
+        builder.append(", boardFaults=");
+        builder.append(boardFaults);
+        builder.append(", boardFeatures=");
+        builder.append(boardFeatures);
+        builder.append(", boardHWRevisionCode=");
+        builder.append(boardHWRev);
+        builder.append(", boardStatus=");
+        builder.append(boardStatus);
+        builder.append(", leftUnderbedLightPWM=");
+        builder.append(leftUnderbedLightPWM);
+        builder.append(", rightUnderbedLightPWM=");
+        builder.append(rightUnderbedLightPWM);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationOutletRequest.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationOutletRequest.java
new file mode 100644 (file)
index 0000000..2cd46a1
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.dto;
+
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link FoundationOutletRequest} is used to control an outlet on the bed foundation.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+public class FoundationOutletRequest {
+    @SerializedName("outletId")
+    private FoundationOutlet outlet;
+
+    @SerializedName("setting")
+    private FoundationOutletOperation operation;
+
+    public FoundationOutlet getFoundationOutlet() {
+        return outlet;
+    }
+
+    public void setFoundationOutlet(FoundationOutlet outlet) {
+        this.outlet = outlet;
+    }
+
+    public FoundationOutletRequest withFoundationOutlet(FoundationOutlet outlet) {
+        setFoundationOutlet(outlet);
+        return this;
+    }
+
+    public FoundationOutletOperation getFoundationOutletOperation() {
+        return operation;
+    }
+
+    public void setFoundationOutletOperation(FoundationOutletOperation operation) {
+        this.operation = operation;
+    }
+
+    public FoundationOutletRequest withFoundationOutletOperation(FoundationOutletOperation operation) {
+        setFoundationOutletOperation(operation);
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SleepNumberRequest [outlet=");
+        builder.append(outlet);
+        builder.append(", operation=");
+        builder.append(operation);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPosition.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPosition.java
new file mode 100644 (file)
index 0000000..7dae9f1
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.dto;
+
+/**
+ * The {@link FoundationPosition} holds the head or foot position of a bed side.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+public class FoundationPosition {
+    private Integer foundationPosition;
+
+    public Integer getFoundationPosition() {
+        return foundationPosition;
+    }
+
+    public void setFoundationPosition(Integer foundationPosition) {
+        this.foundationPosition = foundationPosition;
+    }
+
+    public FoundationPosition withFoundationPosition(Integer foundationPosition) {
+        setFoundationPosition(foundationPosition);
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("FoundationPosition [foundationPosition=");
+        builder.append(foundationPosition);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPositionRequest.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPositionRequest.java
new file mode 100644 (file)
index 0000000..564f9b4
--- /dev/null
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.dto;
+
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.Side;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link FoundationPositionRequest} is used to set the position of the head or foot
+ * of a side of a bed..
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+public class FoundationPositionRequest {
+    @SerializedName("side")
+    private Side side;
+
+    @SerializedName("position")
+    private int position;
+
+    @SerializedName("actuator")
+    private FoundationActuator actuator;
+
+    @SerializedName("speed")
+    private FoundationActuatorSpeed speed;
+
+    public Side getSide() {
+        return side;
+    }
+
+    public void setSide(Side side) {
+        this.side = side;
+    }
+
+    public FoundationPositionRequest withSide(Side side) {
+        setSide(side);
+        return this;
+    }
+
+    public int getPosition() {
+        return position;
+    }
+
+    public void setPosition(int position) {
+        this.position = position;
+    }
+
+    public FoundationPositionRequest withPosition(int position) {
+        setPosition(position);
+        return this;
+    }
+
+    public FoundationActuator getFoundationActuartor() {
+        return actuator;
+    }
+
+    public void setFoundationActuator(FoundationActuator actuator) {
+        this.actuator = actuator;
+    }
+
+    public FoundationPositionRequest withFoundationActuator(FoundationActuator actuator) {
+        setFoundationActuator(actuator);
+        return this;
+    }
+
+    public FoundationActuatorSpeed getFoundationActuartorSpeed() {
+        return speed;
+    }
+
+    public void setFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
+        this.speed = speed;
+    }
+
+    public FoundationPositionRequest withFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
+        setFoundationActuatorSpeed(speed);
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SleepNumberRequest [side=");
+        builder.append(side);
+        builder.append(", position=");
+        builder.append(position);
+        builder.append(", actuator=");
+        builder.append(actuator);
+        builder.append(", speed=");
+        builder.append(speed);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPresetRequest.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationPresetRequest.java
new file mode 100644 (file)
index 0000000..0420210
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.dto;
+
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
+import org.openhab.binding.sleepiq.internal.api.enums.Side;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link FoundationPresetRequest} is used to set a preset for a bed side.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+public class FoundationPresetRequest {
+    @SerializedName("side")
+    private Side side;
+
+    @SerializedName("preset")
+    private FoundationPreset preset;
+
+    @SerializedName("speed")
+    private FoundationActuatorSpeed speed;
+
+    public Side getSide() {
+        return side;
+    }
+
+    public void setSide(Side side) {
+        this.side = side;
+    }
+
+    public FoundationPresetRequest withSide(Side side) {
+        setSide(side);
+        return this;
+    }
+
+    public FoundationPreset getFoundationPreset() {
+        return preset;
+    }
+
+    public void setFoundationPreset(FoundationPreset preset) {
+        this.preset = preset;
+    }
+
+    public FoundationPresetRequest withFoundationPreset(FoundationPreset preset) {
+        setFoundationPreset(preset);
+        return this;
+    }
+
+    public FoundationActuatorSpeed getFoundationActuartorSpeed() {
+        return speed;
+    }
+
+    public void setFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
+        this.speed = speed;
+    }
+
+    public FoundationPresetRequest withFoundationActuatorSpeed(FoundationActuatorSpeed speed) {
+        setFoundationActuatorSpeed(speed);
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SleepNumberRequest [side=");
+        builder.append(side);
+        builder.append(", preset=");
+        builder.append(preset);
+        builder.append(", speed=");
+        builder.append(speed);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationStatusResponse.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/dto/FoundationStatusResponse.java
new file mode 100644 (file)
index 0000000..26174e8
--- /dev/null
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.dto;
+
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link FoundationStatusResponse} holds the status of the foundation
+ * returned from the sleepiq API.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+public class FoundationStatusResponse {
+    @SerializedName("fsType")
+    private String type;
+
+    @SerializedName("fsRightHeadPosition")
+    private FoundationPosition rightHeadPosition;
+
+    @SerializedName("fsRightFootPosition")
+    private FoundationPosition rightFootPosition;
+
+    @SerializedName("fsLeftHeadPosition")
+    private FoundationPosition leftHeadPosition;
+
+    @SerializedName("fsLeftFootPosition")
+    private FoundationPosition leftFootPosition;
+
+    @SerializedName("fsCurrentPositionPresetRight")
+    private FoundationPreset currentPositionPresetRight;
+
+    @SerializedName("fsCurrentPositionPresetLeft")
+    private FoundationPreset currentPositionPresetLeft;
+
+    @SerializedName("fsOutletsOn")
+    private boolean outletsOn;
+
+    public String getType() {
+        return type;
+    }
+
+    public int getRightHeadPosition() {
+        return rightHeadPosition.getFoundationPosition().intValue();
+    }
+
+    public int getLeftHeadPosition() {
+        return leftHeadPosition.getFoundationPosition().intValue();
+    }
+
+    public int getRightFootPosition() {
+        return rightFootPosition.getFoundationPosition().intValue();
+    }
+
+    public int getLeftFootPosition() {
+        return leftFootPosition.getFoundationPosition().intValue();
+    }
+
+    public FoundationPreset getCurrentPositionPresetRight() {
+        return currentPositionPresetRight;
+    }
+
+    public FoundationPreset getCurrentPositionPresetLeft() {
+        return currentPositionPresetLeft;
+    }
+
+    public boolean getOutletsOn() {
+        return outletsOn;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("FoundationStatusResponse [");
+        builder.append("type=");
+        builder.append(type);
+        builder.append("rightHeadPosition=");
+        builder.append(rightHeadPosition);
+        builder.append(", leftHeadPosition=");
+        builder.append(leftHeadPosition);
+        builder.append(", rightFootPosition=");
+        builder.append(rightFootPosition);
+        builder.append(", leftFootPosition=");
+        builder.append(leftFootPosition);
+        builder.append(", currentPositionPresetRight=");
+        builder.append(currentPositionPresetRight);
+        builder.append(", currentPositionPresetLeft=");
+        builder.append(currentPositionPresetLeft);
+        builder.append(", outletsOn=");
+        builder.append(outletsOn);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationActuator.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationActuator.java
new file mode 100644 (file)
index 0000000..cebba31
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.enums;
+
+import static org.openhab.binding.sleepiq.internal.SleepIQBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link FoundationActuator} represents actuators at the head and foot of the bed side.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public enum FoundationActuator {
+    @SerializedName("H")
+    HEAD("H"),
+
+    @SerializedName("F")
+    FOOT("F");
+
+    private final String actuator;
+
+    FoundationActuator(final String actuator) {
+        this.actuator = actuator;
+    }
+
+    public String value() {
+        return actuator;
+    }
+
+    public static FoundationActuator forValue(String value) {
+        for (FoundationActuator s : FoundationActuator.values()) {
+            if (s.actuator.equals(value)) {
+                return s;
+            }
+        }
+        throw new IllegalArgumentException("Invalid actuator: " + value);
+    }
+
+    public static FoundationActuator convertFromChannelId(String channelId) {
+        FoundationActuator localActuator;
+        switch (channelId) {
+            case CHANNEL_RIGHT_POSITION_HEAD:
+            case CHANNEL_LEFT_POSITION_HEAD:
+                localActuator = FoundationActuator.HEAD;
+                break;
+            case CHANNEL_RIGHT_POSITION_FOOT:
+            case CHANNEL_LEFT_POSITION_FOOT:
+                localActuator = FoundationActuator.FOOT;
+                break;
+            default:
+                throw new IllegalArgumentException("Can't convert channel to actuator: " + channelId);
+        }
+        return localActuator;
+    }
+
+    @Override
+    public String toString() {
+        return actuator;
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationActuatorSpeed.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationActuatorSpeed.java
new file mode 100644 (file)
index 0000000..38987c0
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.enums;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link FoundationActuatorSpeed} represents speed with which the actuator operates.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public enum FoundationActuatorSpeed {
+    FAST(0),
+    SLOW(1);
+
+    private final int speed;
+
+    FoundationActuatorSpeed(final int speed) {
+        this.speed = speed;
+    }
+
+    public int value() {
+        return speed;
+    }
+
+    public static FoundationActuatorSpeed forValue(int value) {
+        for (FoundationActuatorSpeed s : FoundationActuatorSpeed.values()) {
+            if (s.speed == value) {
+                return s;
+            }
+        }
+        throw new IllegalArgumentException("Invalid speed: " + value);
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(speed);
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationOutlet.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationOutlet.java
new file mode 100644 (file)
index 0000000..cd8c917
--- /dev/null
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.enums;
+
+import static org.openhab.binding.sleepiq.internal.SleepIQBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link FoundationOutlet} represents the outlets available on the foundation.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public enum FoundationOutlet {
+    RIGHT_NIGHT_STAND(1),
+    LEFT_NIGHT_STAND(2),
+    RIGHT_UNDER_BED_LIGHT(3),
+    LEFT_UNDER_BED_LIGHT(4);
+
+    private final int outlet;
+
+    FoundationOutlet(final int outlet) {
+        this.outlet = outlet;
+    }
+
+    public int value() {
+        return outlet;
+    }
+
+    public static FoundationOutlet forValue(int value) {
+        for (FoundationOutlet s : FoundationOutlet.values()) {
+            if (s.outlet == value) {
+                return s;
+            }
+        }
+        throw new IllegalArgumentException("Invalid outlet: " + value);
+    }
+
+    public static FoundationOutlet convertFromChannelId(String channelId) {
+        FoundationOutlet localOutlet;
+        switch (channelId) {
+            case CHANNEL_RIGHT_NIGHT_STAND_OUTLET:
+                localOutlet = FoundationOutlet.RIGHT_NIGHT_STAND;
+                break;
+            case CHANNEL_LEFT_NIGHT_STAND_OUTLET:
+                localOutlet = FoundationOutlet.LEFT_NIGHT_STAND;
+                break;
+            case CHANNEL_RIGHT_UNDER_BED_LIGHT:
+                localOutlet = FoundationOutlet.RIGHT_UNDER_BED_LIGHT;
+                break;
+            case CHANNEL_LEFT_UNDER_BED_LIGHT:
+                localOutlet = FoundationOutlet.LEFT_UNDER_BED_LIGHT;
+                break;
+            default:
+                throw new IllegalArgumentException("Can't convert channel to outlet: " + channelId);
+        }
+        return localOutlet;
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(outlet);
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationOutletOperation.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationOutletOperation.java
new file mode 100644 (file)
index 0000000..fb14729
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.enums;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link FoundationOutletOperation} represents the controls on a foundation outlet.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public enum FoundationOutletOperation {
+    OFF(0),
+    ON(1);
+
+    private final int outletControl;
+
+    FoundationOutletOperation(final int outletControl) {
+        this.outletControl = outletControl;
+    }
+
+    public int value() {
+        return outletControl;
+    }
+
+    public static FoundationOutletOperation forValue(int value) {
+        for (FoundationOutletOperation s : FoundationOutletOperation.values()) {
+            if (s.outletControl == value) {
+                return s;
+            }
+        }
+        throw new IllegalArgumentException("Invalid outletControl: " + value);
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(outletControl);
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationPreset.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/enums/FoundationPreset.java
new file mode 100644 (file)
index 0000000..f813bfe
--- /dev/null
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.enums;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link FoundationPreset} represents preset bed positions for a bed side.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public enum FoundationPreset {
+    NOT_AT_PRESET(0),
+    FAVORITE(1),
+    READ(2),
+    WATCH_TV(3),
+    FLAT(4),
+    ZERO_G(5),
+    SNORE(6);
+
+    private final int preset;
+
+    FoundationPreset(final int preset) {
+        this.preset = preset;
+    }
+
+    public int value() {
+        return preset;
+    }
+
+    public static FoundationPreset forValue(int value) {
+        for (FoundationPreset s : FoundationPreset.values()) {
+            if (s.preset == value) {
+                return s;
+            }
+        }
+        throw new IllegalArgumentException("Invalid preset: " + value);
+    }
+
+    public static FoundationPreset convertFromStatus(String currentPositionPreset) {
+        FoundationPreset preset;
+        switch (currentPositionPreset.toLowerCase()) {
+            case "not at preset":
+                preset = FoundationPreset.NOT_AT_PRESET;
+                break;
+            case "favorite":
+                preset = FoundationPreset.FAVORITE;
+                break;
+            case "read":
+                preset = FoundationPreset.READ;
+                break;
+            case "watch tv":
+                preset = FoundationPreset.WATCH_TV;
+                break;
+            case "flat":
+                preset = FoundationPreset.FLAT;
+                break;
+            case "zero g":
+                preset = FoundationPreset.ZERO_G;
+                break;
+            case "snore":
+                preset = FoundationPreset.SNORE;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown preset value: " + currentPositionPreset);
+        }
+        return preset;
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(preset);
+    }
+}
index c806e3ec644aa32e9d7d3e2d227051ff3a73344d..5a99eed3458206cc674efdede2e1c22c58684bb9 100644 (file)
@@ -27,8 +27,12 @@ public class Endpoints {
     private static final String FAMILY_STATUS = "/rest/bed/familyStatus";
     private static final String PAUSE_MODE = "/rest/bed/%s/pauseMode";
     private static final String SLEEP_DATA = "/rest/sleepData";
-    private static final String SET_SLEEP_NUMBER = "/rest/bed/%s/sleepNumber";
-    private static final String SET_PAUSE_MODE = "/rest/bed/%s/pauseMode";
+    private static final String SLEEP_NUMBER = "/rest/bed/%s/sleepNumber";
+    private static final String FOUNDATION_STATUS = "/rest/bed/%s/foundation/status";
+    private static final String FOUNDATION_FEATURES = "/rest/bed/%s/foundation/system";
+    private static final String FOUNDATION_POSITION = "/rest/bed/%s/foundation/adjustment/micro";
+    private static final String FOUNDATION_PRESET = "/rest/bed/%s/foundation/preset";
+    private static final String FOUNDATION_OUTLET = "/rest/bed/%s/foundation/outlet";
 
     public static String login() {
         return LOGIN;
@@ -54,12 +58,28 @@ public class Endpoints {
         return SLEEP_DATA;
     }
 
-    public static String setSleepNumber(String bedId) {
-        return String.format(SET_SLEEP_NUMBER, bedId);
+    public static String sleepNumber(String bedId) {
+        return String.format(SLEEP_NUMBER, bedId);
     }
 
-    public static String setPauseMode(String bedId) {
-        return String.format(SET_PAUSE_MODE, bedId);
+    public static String foundationStatus(String bedId) {
+        return String.format(FOUNDATION_STATUS, bedId);
+    }
+
+    public static String foundationFeatures(String bedId) {
+        return String.format(FOUNDATION_FEATURES, bedId);
+    }
+
+    public static String foundationPosition(String bedId) {
+        return String.format(FOUNDATION_POSITION, bedId);
+    }
+
+    public static String foundationPreset(String bedId) {
+        return String.format(FOUNDATION_PRESET, bedId);
+    }
+
+    public static String foundationOutlet(String bedId) {
+        return String.format(FOUNDATION_OUTLET, bedId);
     }
 
     private Endpoints() {
index 64e99adb24bb2ab1046845da1973776f1a57df59..1a0d1b1c7b187dfe9883c9336cf4f4a847fba96a 100644 (file)
@@ -15,9 +15,19 @@ package org.openhab.binding.sleepiq.internal.api.impl;
 import java.time.ZonedDateTime;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationPosition;
 import org.openhab.binding.sleepiq.internal.api.dto.SleepNumberRequest;
 import org.openhab.binding.sleepiq.internal.api.dto.TimeSince;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
 import org.openhab.binding.sleepiq.internal.api.enums.Side;
+import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationActuatorSpeedTypeAdapter;
+import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationOutletOperationTypeAdapter;
+import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationOutletTypeAdapter;
+import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationPositionTypeAdapter;
+import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.FoundationPresetTypeAdapter;
 import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.SideTypeAdapter;
 import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.SleepNumberRequestAdapter;
 import org.openhab.binding.sleepiq.internal.api.impl.typeadapters.TimeSinceTypeAdapter;
@@ -43,6 +53,11 @@ public class GsonGenerator {
         builder.registerTypeAdapter(TimeSince.class, new TimeSinceTypeAdapter());
         builder.registerTypeAdapter(SleepNumberRequest.class, new SleepNumberRequestAdapter());
         builder.registerTypeAdapter(Side.class, new SideTypeAdapter());
+        builder.registerTypeAdapter(FoundationPreset.class, new FoundationPresetTypeAdapter());
+        builder.registerTypeAdapter(FoundationActuatorSpeed.class, new FoundationActuatorSpeedTypeAdapter());
+        builder.registerTypeAdapter(FoundationOutlet.class, new FoundationOutletTypeAdapter());
+        builder.registerTypeAdapter(FoundationOutletOperation.class, new FoundationOutletOperationTypeAdapter());
+        builder.registerTypeAdapter(FoundationPosition.class, new FoundationPositionTypeAdapter());
 
         if (prettyPrint) {
             builder.setPrettyPrinting();
index 587ee3085435627c5e4bc318a40da763f7e1b80d..48f891c2fd5132fc2ca886fa3c0a02602eb841f0 100644 (file)
@@ -36,11 +36,17 @@ import org.openhab.binding.sleepiq.internal.api.Configuration;
 import org.openhab.binding.sleepiq.internal.api.LoginException;
 import org.openhab.binding.sleepiq.internal.api.ResponseFormatException;
 import org.openhab.binding.sleepiq.internal.api.SleepIQ;
+import org.openhab.binding.sleepiq.internal.api.SleepIQException;
 import org.openhab.binding.sleepiq.internal.api.UnauthorizedException;
 import org.openhab.binding.sleepiq.internal.api.dto.Bed;
 import org.openhab.binding.sleepiq.internal.api.dto.BedsResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.Failure;
 import org.openhab.binding.sleepiq.internal.api.dto.FamilyStatusResponse;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationOutletRequest;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationPositionRequest;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationPresetRequest;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.LoginInfo;
 import org.openhab.binding.sleepiq.internal.api.dto.LoginRequest;
 import org.openhab.binding.sleepiq.internal.api.dto.PauseModeResponse;
@@ -48,6 +54,11 @@ import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.SleepNumberRequest;
 import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
 import org.openhab.binding.sleepiq.internal.api.dto.SleepersResponse;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
 import org.openhab.binding.sleepiq.internal.api.enums.Side;
 import org.openhab.binding.sleepiq.internal.api.enums.SleepDataInterval;
 import org.slf4j.Logger;
@@ -60,6 +71,7 @@ import com.google.gson.JsonSyntaxException;
  * The {@link SleepIQImpl} class handles all interactions with the sleepiq service.
  *
  * @author Gregory Moyer - Initial contribution
+ * @author Mark Hilbush - Added foundation functionality
  */
 @NonNullByDefault
 public class SleepIQImpl implements SleepIQ {
@@ -225,7 +237,7 @@ public class SleepIQImpl implements SleepIQ {
         String body = GSON.toJson(new SleepNumberRequest().withBedId(bedId).withSleepNumber(sleepNumber).withSide(side),
                 SleepNumberRequest.class);
         logger.debug("SleepIQ: setSleepNumber: Request body={}", body);
-        cloudRequest(Endpoints.setSleepNumber(bedId), null, body);
+        cloudRequest(Endpoints.sleepNumber(bedId), null, body);
     }
 
     @Override
@@ -234,7 +246,68 @@ public class SleepIQImpl implements SleepIQ {
         logger.debug("SleepIQ: setPauseMode: command={}", pauseMode);
         Map<String, String> requestParameters = new HashMap<>();
         requestParameters.put("mode", pauseMode ? "on" : "off");
-        cloudRequest(Endpoints.setPauseMode(bedId), requestParameters, "");
+        cloudRequest(Endpoints.pauseMode(bedId), requestParameters, "");
+    }
+
+    @Override
+    public FoundationFeaturesResponse getFoundationFeatures(String bedId)
+            throws LoginException, ResponseFormatException, CommunicationException {
+        try {
+            String contentResponse = cloudRequest(Endpoints.foundationFeatures(bedId));
+            FoundationFeaturesResponse response = GSON.fromJson(contentResponse, FoundationFeaturesResponse.class);
+            if (response != null) {
+                logger.debug("SleepIQ: {}", response);
+                return response;
+            } else {
+                throw new ResponseFormatException("Failed to get a valid 'foundationFeatures' response from cloud");
+            }
+        } catch (JsonSyntaxException e) {
+            throw new ResponseFormatException("Failed to parse 'foundationFeatures' response");
+        }
+    }
+
+    @Override
+    public FoundationStatusResponse getFoundationStatus(String bedId) throws LoginException, SleepIQException {
+        try {
+            String contentResponse = cloudRequest(Endpoints.foundationStatus(bedId));
+            FoundationStatusResponse response = GSON.fromJson(contentResponse, FoundationStatusResponse.class);
+            if (response != null) {
+                logger.debug("SleepIQ: {}", response);
+                return response;
+            } else {
+                throw new ResponseFormatException("Failed to get a valid 'foundationStatus' response from cloud");
+            }
+        } catch (JsonSyntaxException e) {
+            throw new ResponseFormatException("Failed to parse 'foundationStatus' response");
+        }
+    }
+
+    @Override
+    public void setFoundationPreset(String bedId, Side side, FoundationPreset preset, FoundationActuatorSpeed speed)
+            throws LoginException, SleepIQException {
+        String body = GSON.toJson(new FoundationPresetRequest().withSide(side).withFoundationPreset(preset)
+                .withFoundationActuatorSpeed(speed), FoundationPresetRequest.class);
+        logger.debug("SleepIQ: setFoundationPreset: Request body={}", body);
+        cloudRequest(Endpoints.foundationPreset(bedId), null, body);
+    }
+
+    @Override
+    public void setFoundationPosition(String bedId, Side side, FoundationActuator actuator, int position,
+            FoundationActuatorSpeed speed) throws LoginException, SleepIQException {
+        String body = GSON.toJson(new FoundationPositionRequest().withSide(side).withPosition(position)
+                .withFoundationActuator(actuator).withFoundationActuatorSpeed(speed), FoundationPositionRequest.class);
+        logger.debug("SleepIQ: setFoundationPosition: Request body={}", body);
+        cloudRequest(Endpoints.foundationPosition(bedId), null, body);
+    }
+
+    @Override
+    public void setFoundationOutlet(String bedId, FoundationOutlet outlet, FoundationOutletOperation operation)
+            throws LoginException, SleepIQException {
+        String body = GSON.toJson(
+                new FoundationOutletRequest().withFoundationOutlet(outlet).withFoundationOutletOperation(operation),
+                FoundationOutletRequest.class);
+        logger.debug("SleepIQ: setFoundationOutlet: Request body={}", body);
+        cloudRequest(Endpoints.foundationOutlet(bedId), null, body);
     }
 
     private String cloudRequest(String endpoint)
@@ -249,7 +322,7 @@ public class SleepIQImpl implements SleepIQ {
 
     private String cloudRequest(String endpoint, @Nullable Map<String, String> parameters, @Nullable String body)
             throws LoginException, ResponseFormatException, CommunicationException {
-        logger.debug("SleepIQ: cloudGetRequest: Invoke endpoint={}", endpoint);
+        logger.debug("SleepIQ: cloudRequest: Invoke endpoint={}", endpoint);
         ContentResponse response = (body == null ? doGet(endpoint, parameters) : doPut(endpoint, parameters, body));
         if (isUnauthorized(response)) {
             logger.debug("SleepIQ: cloudGetRequest: UNAUTHORIZED, reset login");
@@ -258,6 +331,7 @@ public class SleepIQImpl implements SleepIQ {
             response = (body == null ? doGet(endpoint, parameters) : doPut(endpoint, parameters, body));
         }
         if (isNotOk(response)) {
+            logger.debug("SleepIQ.cloudRequest: ResponseFormatException on call to endpoint {}", endpoint);
             throw new ResponseFormatException(String.format("Cloud API returned error: status=%d, message=%s",
                     response.getStatus(), HttpStatus.getCode(response.getStatus()).getMessage()));
         }
@@ -282,7 +356,7 @@ public class SleepIQImpl implements SleepIQ {
         return doRequest(request, parameters);
     }
 
-    private ContentResponse doRequest(Request request, @Nullable Map<String, String> parameters)
+    private synchronized ContentResponse doRequest(Request request, @Nullable Map<String, String> parameters)
             throws CommunicationException {
         try {
             if (parameters != null) {
@@ -291,12 +365,13 @@ public class SleepIQImpl implements SleepIQ {
                 }
             }
             addCookiesToRequest(request);
-            logger.debug("SleepIQ: doPut: request url={}", request.getURI());
+            logger.debug("SleepIQ: doRequest: request url={}", request.getURI());
             ContentResponse response = request.send();
-            logger.trace("SleepIQ: doPut: status={} response={}", response.getStatus(), response.getContentAsString());
+            logger.trace("SleepIQ: doRequest: status={} response={}", response.getStatus(),
+                    response.getContentAsString());
             return response;
         } catch (InterruptedException | TimeoutException | ExecutionException e) {
-            logger.debug("SleepIQ: doPut: Exception message={}", e.getMessage(), e);
+            logger.debug("SleepIQ: doRequest: Exception message={}", e.getMessage(), e);
             throw new CommunicationException("Communication error while accessing API: " + e.getMessage());
         }
     }
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationActuatorSpeedTypeAdapter.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationActuatorSpeedTypeAdapter.java
new file mode 100644 (file)
index 0000000..2d156e8
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
+
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * The {@link FoundationActuatorSpeedTypeAdapter} converts the FoundationActuatorSpeed enum
+ * to the format expected by the sleepiq API.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class FoundationActuatorSpeedTypeAdapter implements JsonSerializer<FoundationActuatorSpeed> {
+
+    @Override
+    public JsonElement serialize(FoundationActuatorSpeed speed, Type typeOfSrc, JsonSerializationContext context) {
+        return new JsonPrimitive(speed.value());
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationOutletOperationTypeAdapter.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationOutletOperationTypeAdapter.java
new file mode 100644 (file)
index 0000000..6d27249
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
+
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * The {@link FoundationOutletOperationTypeAdapter} converts the FoundationOutletOperation enum to the
+ * format expected by the sleepiq API.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class FoundationOutletOperationTypeAdapter implements JsonSerializer<FoundationOutletOperation> {
+
+    @Override
+    public JsonElement serialize(FoundationOutletOperation operation, Type typeOfSrc,
+            JsonSerializationContext context) {
+        return new JsonPrimitive(operation.value());
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationOutletTypeAdapter.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationOutletTypeAdapter.java
new file mode 100644 (file)
index 0000000..176a200
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
+
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * The {@link FoundationOutletTypeAdapter} converts the FoundationOutlet enum to the
+ * format expected by the sleepiq API.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class FoundationOutletTypeAdapter implements JsonSerializer<FoundationOutlet> {
+
+    @Override
+    public JsonElement serialize(FoundationOutlet outlet, Type typeOfSrc, JsonSerializationContext context) {
+        return new JsonPrimitive(outlet.value());
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationPositionTypeAdapter.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationPositionTypeAdapter.java
new file mode 100644 (file)
index 0000000..35a5c17
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
+
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationPosition;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+/**
+ * The {@link FoundationPositionTypeAdapter} converts the hex string position from the
+ * foundation status response into an integer value. The position is returned by the API
+ * as a two character hex string (e.g. "3f"). The position can be between 0 and 100, inclusive.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class FoundationPositionTypeAdapter implements JsonDeserializer<FoundationPosition> {
+
+    @Override
+    public @Nullable FoundationPosition deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2)
+            throws JsonParseException {
+        try {
+            return new FoundationPosition().withFoundationPosition(Integer.parseInt(element.getAsString(), 16));
+        } catch (NumberFormatException e) {
+            // If we can't parse it, just set to 0
+            return new FoundationPosition().withFoundationPosition(Integer.valueOf(0));
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationPresetTypeAdapter.java b/bundles/org.openhab.binding.sleepiq/src/main/java/org/openhab/binding/sleepiq/internal/api/impl/typeadapters/FoundationPresetTypeAdapter.java
new file mode 100644 (file)
index 0000000..3e9c66b
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.internal.api.impl.typeadapters;
+
+import java.lang.reflect.Type;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * The {@link FoundationPresetTypeAdapter} converts the FoundationPreset enum to the
+ * format expected by the sleepiq API.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class FoundationPresetTypeAdapter
+        implements JsonDeserializer<FoundationPreset>, JsonSerializer<FoundationPreset> {
+
+    @Override
+    public JsonElement serialize(FoundationPreset preset, Type typeOfSrc, JsonSerializationContext context) {
+        return new JsonPrimitive(preset.value());
+    }
+
+    @Override
+    public @Nullable FoundationPreset deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2)
+            throws JsonParseException {
+        return FoundationPreset.convertFromStatus(element.getAsString());
+    }
+}
index 655a3d5c40aa98d506bbbf00039d883f9d695a19..25481b66ace7475ef6dacf3442c057605029e271 100644 (file)
@@ -36,7 +36,7 @@ public class SideTypeAdapter implements JsonDeserializer<Side>, JsonSerializer<S
 
     @Override
     public JsonElement serialize(Side side, Type typeOfSrc, JsonSerializationContext context) {
-        return new JsonPrimitive(side.value());
+        return new JsonPrimitive(side.equals(Side.LEFT) ? "L" : "R");
     }
 
     @Override
index aa9c3b0734179671a4744d0a68248b871ec04ef3..b4977d597c2aaf8cb63c123e8fed64f892f945f6 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.sleepiq.internal.handler;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.sleepiq.internal.api.dto.BedStatus;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
 
 /**
@@ -27,10 +28,23 @@ public interface BedStatusListener {
     /**
      * This method will be called whenever a new bed status is received by the cloud handler.
      *
-     * @param cloud the cloud service that can be used to gather additional information
-     * @param status the status returned from the cloud service
+     * @param status the bed status returned from the cloud service
      */
     public void onBedStateChanged(BedStatus status);
 
+    /**
+     * This method will be called whenever a new foundation status is received by the cloud handler.
+     *
+     * @param status the foundation status returned from the cloud service
+     */
+    public void onFoundationStateChanged(String bedId, FoundationStatusResponse status);
+
+    /**
+     * Determine if bed has a foundation installed.
+     *
+     * @return true if bed has a foundation; otherwise falase
+     */
+    public boolean isFoundationInstalled();
+
     public void onSleeperChanged(@Nullable Sleeper sleeper);
 }
index 30f316084476a52842a56ab7fe4ac1b09df28cf8..880a70811386e6b87613ae3af31eb1d731b11be7 100644 (file)
@@ -32,14 +32,22 @@ import org.openhab.binding.sleepiq.internal.SleepIQBindingConstants;
 import org.openhab.binding.sleepiq.internal.SleepIQConfigStatusMessage;
 import org.openhab.binding.sleepiq.internal.api.Configuration;
 import org.openhab.binding.sleepiq.internal.api.LoginException;
+import org.openhab.binding.sleepiq.internal.api.ResponseFormatException;
 import org.openhab.binding.sleepiq.internal.api.SleepIQ;
 import org.openhab.binding.sleepiq.internal.api.SleepIQException;
 import org.openhab.binding.sleepiq.internal.api.UnauthorizedException;
 import org.openhab.binding.sleepiq.internal.api.dto.Bed;
 import org.openhab.binding.sleepiq.internal.api.dto.BedStatus;
 import org.openhab.binding.sleepiq.internal.api.dto.FamilyStatusResponse;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
 import org.openhab.binding.sleepiq.internal.api.enums.Side;
 import org.openhab.binding.sleepiq.internal.api.enums.SleepDataInterval;
 import org.openhab.binding.sleepiq.internal.config.SleepIQCloudConfiguration;
@@ -146,11 +154,15 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
      */
     public void registerBedStatusListener(final BedStatusListener listener) {
         bedStatusListeners.add(listener);
-        scheduler.execute(() -> {
+        /*
+         * Delay the initial sleeper and status update to give some time for the property update
+         * to determine if a foundation is installed.
+         */
+        scheduler.schedule(() -> {
             refreshSleepers();
             refreshBedStatus();
             updateListenerManagement();
-        });
+        }, 10L, TimeUnit.SECONDS);
     }
 
     /**
@@ -188,11 +200,8 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
      * @param bedId the bed identifier
      * @return the identified {@link Bed} or <code>null</code> if no such bed exists
      */
-    public @Nullable Bed getBed(final @Nullable String bedId) {
+    public @Nullable Bed getBed(final String bedId) {
         logger.debug("CloudHandler: Get bed object for bedId={}", bedId);
-        if (bedId == null) {
-            return null;
-        }
         List<Bed> beds = getBeds();
         if (beds != null) {
             for (Bed bed : beds) {
@@ -211,11 +220,8 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
      * @param side the side of the bed
      * @return the sleeper or null if sleeper not found
      */
-    public @Nullable Sleeper getSleeper(@Nullable String bedId, Side side) {
+    public @Nullable Sleeper getSleeper(String bedId, Side side) {
         logger.debug("CloudHandler: Get sleeper object for bedId={}, side={}", bedId, side);
-        if (bedId == null) {
-            return null;
-        }
         List<Sleeper> localSleepers = sleepers;
         if (localSleepers != null) {
             for (Sleeper sleeper : localSleepers) {
@@ -234,10 +240,7 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
      * @param sleepNumber the sleep number multiple of 5 between 5 and 100
      * @param side the chamber to set
      */
-    public void setSleepNumber(@Nullable String bedId, Side side, int sleepNumber) {
-        if (bedId == null) {
-            return;
-        }
+    public void setSleepNumber(String bedId, Side side, int sleepNumber) {
         try {
             cloud.setSleepNumber(bedId, side, sleepNumber);
         } catch (SleepIQException e) {
@@ -251,10 +254,7 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
      * @param bedId the bed identifier
      * @param mode turn pause mode on or off
      */
-    public void setPauseMode(@Nullable String bedId, boolean mode) {
-        if (bedId == null) {
-            return;
-        }
+    public void setPauseMode(String bedId, boolean mode) {
         try {
             cloud.setPauseMode(bedId, mode);
         } catch (SleepIQException e) {
@@ -262,6 +262,99 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
         }
     }
 
+    /**
+     * Get the foundation features of the specified bed
+     *
+     * @param bedId the bed identifier
+     */
+    public @Nullable FoundationFeaturesResponse getFoundationFeatures(String bedId) {
+        try {
+            return cloud.getFoundationFeatures(bedId);
+        } catch (ResponseFormatException e) {
+            logger.debug("CloudHandler: Unable to parse foundation features response for bed={}", bedId);
+        } catch (SleepIQException e) {
+            logger.debug("CloudHandler: Exception getting foundation features, bed={}", bedId, e);
+        }
+        return null;
+    }
+
+    /**
+     * Get the foundation status of the specified bed
+     *
+     * @param bedId the bed identifier
+     */
+    public @Nullable FoundationStatusResponse getFoundationStatus(String bedId) {
+        try {
+            return cloud.getFoundationStatus(bedId);
+        } catch (ResponseFormatException e) {
+            logger.debug("CloudHandler: Unable to parse foundation status response for bed={}", bedId);
+        } catch (SleepIQException e) {
+            logger.debug("CloudHandler: Exception getting foundation status, bed={}: {}", bedId, e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Set a foundation adjustment preset.
+     *
+     * @param bedId the bed identifier
+     * @param side the side of the bed
+     * @param preset the preset to be applied
+     * @param speed the speed with which to make the adjustment
+     */
+    public void setFoundationPreset(String bedId, Side side, FoundationPreset preset, FoundationActuatorSpeed speed) {
+        try {
+            cloud.setFoundationPreset(bedId, side, preset, speed);
+        } catch (ResponseFormatException e) {
+            logger.debug("CloudHandler: ResponseFormatException setting foundation preset for bed={}: {}", bedId,
+                    e.getMessage());
+        } catch (SleepIQException e) {
+            logger.debug("CloudHandler: Exception setting the foundation preset for bed={}", bedId, e);
+        }
+        return;
+    }
+
+    /**
+     * Set a foundation position on head or foot of bed side.
+     *
+     * @param bedId the bed identifier
+     * @param side the side of the bed
+     * @param actuator the head or foot of the bed
+     * @param position the new position of the actuator
+     * @param speed the speed with which to make the adjustment
+     */
+    public void setFoundationPosition(String bedId, Side side, FoundationActuator actuator, int position,
+            FoundationActuatorSpeed speed) {
+        try {
+            cloud.setFoundationPosition(bedId, side, actuator, position, speed);
+        } catch (ResponseFormatException e) {
+            logger.debug("CloudHandler: ResponseFormatException setting foundation position for bed={}: {}", bedId,
+                    e.getMessage());
+        } catch (SleepIQException e) {
+            logger.debug("CloudHandler: Exception setting the foundation position for bed={}", bedId, e);
+        }
+        return;
+    }
+
+    /**
+     * Operate an outlet on the foundation.
+     *
+     * @param bedId the bed identifier
+     * @param outlet the outlet to operate
+     * @param operation the operation (On or Off) performed on the outlet
+     */
+    public void setFoundationOutlet(String bedId, FoundationOutlet outlet, FoundationOutletOperation operation) {
+        try {
+            cloud.setFoundationOutlet(bedId, outlet, operation);
+        } catch (ResponseFormatException e) {
+            logger.debug("CloudHandler: ResponseFormatException setting the foundation outlet for bed={}: {}", bedId,
+                    e.getMessage());
+        } catch (SleepIQException e) {
+            logger.debug("CloudHandler: Exception setting the foundation outlet for bed={}", bedId, e);
+        }
+        return;
+    }
+
     /**
      * Update the given properties with attributes of the given bed. If no properties are given, a new map will be
      * created.
@@ -289,22 +382,61 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
         return properties;
     }
 
+    /**
+     * Update the given foundation properties with features of the given bed foundation.
+     *
+     * @param bed the source of data
+     * @param features the foundation features to update (this may be <code>null</code>)
+     * @return the given map (or a new map if no map was given) with updated/set properties from the supplied bed
+     */
+    public Map<String, String> updateFeatures(final String bedId, final @Nullable FoundationFeaturesResponse features,
+            Map<String, String> properties) {
+        if (features != null) {
+            logger.debug("CloudHandler: Updating foundation properties for bed={}", bedId);
+            properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION, "Installed");
+            properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HW_REV,
+                    String.valueOf(features.getBoardHWRev()));
+            properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_IS_BOARD_AS_SINGLE,
+                    features.isBoardAsSingle() ? "yes" : "no");
+            properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HAS_MASSAGE_AND_LIGHT,
+                    features.hasMassageAndLight() ? "yes" : "no");
+            properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HAS_FOOT_CONTROL,
+                    features.hasFootControl() ? "yes" : "no");
+            properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HAS_FOOT_WARMER,
+                    features.hasFootWarming() ? "yes" : "no");
+            properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION_HAS_UNDER_BED_LIGHT,
+                    features.hasUnderBedLight() ? "yes" : "no");
+        } else {
+            logger.debug("CloudHandler: Foundation not installed on bed={}", bedId);
+            properties.put(SleepIQBindingConstants.PROPERTY_FOUNDATION, "Not installed");
+        }
+        return properties;
+    }
+
     /**
      * Retrieve the latest status on all beds and update all registered listeners
-     * with bed status, sleepers and sleep data.
+     * with bed status, foundation status, sleepers and sleep data.
      */
     private void refreshBedStatus() {
         logger.debug("CloudHandler: Refreshing BED STATUS, updating chanels with status, sleepers, and sleep data");
         try {
             FamilyStatusResponse familyStatus = cloud.getFamilyStatus();
-            if (familyStatus != null && familyStatus.getBeds() != null) {
+            if (familyStatus.getBeds() != null) {
                 updateStatus(ThingStatus.ONLINE);
                 for (BedStatus bedStatus : familyStatus.getBeds()) {
-                    logger.debug("CloudHandler: Informing listeners with bed status for bedId={}",
-                            bedStatus.getBedId());
+                    String bedId = bedStatus.getBedId();
+                    logger.debug("CloudHandler: Informing listeners with bed status for bedId={}", bedId);
                     bedStatusListeners.stream().forEach(l -> l.onBedStateChanged(bedStatus));
-                }
 
+                    // Get foundation status only if bed has a foundation
+                    bedStatusListeners.stream().filter(l -> l.isFoundationInstalled()).forEach(l -> {
+                        try {
+                            l.onFoundationStateChanged(bedId, cloud.getFoundationStatus(bedStatus.getBedId()));
+                        } catch (SleepIQException e) {
+                            logger.debug("CloudHandler: Exception getting foundation status for bedId={}", bedId);
+                        }
+                    });
+                }
                 List<Sleeper> localSleepers = sleepers;
                 if (localSleepers != null) {
                     for (Sleeper sleeper : localSleepers) {
@@ -358,7 +490,7 @@ public class SleepIQCloudHandler extends ConfigStatusBridgeHandler {
     private void createCloudConnection() throws LoginException {
         SleepIQCloudConfiguration bindingConfig = getConfigAs(SleepIQCloudConfiguration.class);
         Configuration cloudConfig = new Configuration().withUsername(bindingConfig.username)
-                .withPassword(bindingConfig.password).withLogging(logger.isTraceEnabled());
+                .withPassword(bindingConfig.password);
         logger.debug("CloudHandler: Authenticating at the SleepIQ cloud service");
         cloud = SleepIQ.create(cloudConfig, httpClient);
         cloud.login();
index e353aa58896c1894a07143818017d0f1ee1a8a20..189372fe10edecf0e6901749cbfec58ad5b1c07f 100644 (file)
@@ -23,8 +23,15 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.sleepiq.internal.api.dto.Bed;
 import org.openhab.binding.sleepiq.internal.api.dto.BedSideStatus;
 import org.openhab.binding.sleepiq.internal.api.dto.BedStatus;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
 import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
 import org.openhab.binding.sleepiq.internal.api.enums.Side;
 import org.openhab.binding.sleepiq.internal.config.SleepIQBedConfiguration;
 import org.openhab.core.library.types.DecimalType;
@@ -60,13 +67,15 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
 
     private final Logger logger = LoggerFactory.getLogger(SleepIQDualBedHandler.class);
 
-    private volatile @Nullable String bedId;
+    private volatile String bedId = "";
 
     private @Nullable Sleeper sleeperLeft;
     private @Nullable Sleeper sleeperRight;
 
     private @Nullable BedStatus previousStatus;
 
+    private @Nullable FoundationFeaturesResponse foundationFeatures;
+
     public SleepIQDualBedHandler(final Thing thing) {
         super(thing);
     }
@@ -84,12 +93,15 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Incorrect bridge thing found");
             return;
         }
-        bedId = getConfigAs(SleepIQBedConfiguration.class).bedId;
-        if (bedId == null) {
+        String localBedId = getConfigAs(SleepIQBedConfiguration.class).bedId;
+        if (localBedId == null) {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                     "Bed id not found in configuration");
             return;
         }
+        bedId = localBedId;
+        // Assume the bed has a foundation until we determine otherwise
+        setFoundationFeatures(new FoundationFeaturesResponse());
 
         logger.debug("BedHandler: Registering SleepIQ bed status listener for bedId={}", bedId);
         SleepIQCloudHandler cloudHandler = (SleepIQCloudHandler) handler;
@@ -111,7 +123,7 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
         if (cloudHandler != null) {
             cloudHandler.unregisterBedStatusListener(this);
         }
-        bedId = null;
+        bedId = "";
     }
 
     @Override
@@ -128,12 +140,14 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
             // Channels will be refreshed automatically by cloud handler
             return;
         }
+        String channelId = channelUID.getId();
+        String groupId = channelUID.getGroupId();
 
-        switch (channelUID.getId()) {
+        switch (channelId) {
             case CHANNEL_LEFT_SLEEP_NUMBER:
             case CHANNEL_RIGHT_SLEEP_NUMBER:
                 if (command instanceof DecimalType) {
-                    Side side = Side.convertFromGroup(channelUID.getGroupId());
+                    Side side = Side.convertFromGroup(groupId);
                     logger.debug("BedHandler: Set sleepnumber to {} for bedId={}, side={}", command, bedId, side);
                     SleepIQCloudHandler cloudHandler = getCloudHandler();
                     if (cloudHandler != null) {
@@ -144,7 +158,7 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
             case CHANNEL_LEFT_PRIVACY_MODE:
             case CHANNEL_RIGHT_PRIVACY_MODE:
                 if (command instanceof OnOffType) {
-                    Side side = Side.convertFromGroup(channelUID.getGroupId());
+                    Side side = Side.convertFromGroup(groupId);
                     logger.debug("BedHandler: Set sleepnumber to {} for bedId={}, side={}", command, bedId, side);
                     SleepIQCloudHandler cloudHandler = getCloudHandler();
                     if (cloudHandler != null) {
@@ -152,6 +166,60 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
                     }
                 }
                 break;
+            case CHANNEL_LEFT_FOUNDATION_PRESET:
+            case CHANNEL_RIGHT_FOUNDATION_PRESET:
+                logger.debug("Received command {} on channel {} to set preset", command, channelUID);
+                if (isFoundationInstalled() && command instanceof DecimalType) {
+                    try {
+                        Side side = Side.convertFromGroup(groupId);
+                        FoundationPreset preset = FoundationPreset.forValue(((DecimalType) command).intValue());
+                        logger.debug("BedHandler: Set foundation preset to {} for bedId={}, side={}", command, bedId,
+                                side);
+                        SleepIQCloudHandler cloudHandler = getCloudHandler();
+                        if (cloudHandler != null) {
+                            cloudHandler.setFoundationPreset(bedId, side, preset, FoundationActuatorSpeed.SLOW);
+                        }
+                    } catch (IllegalArgumentException e) {
+                        logger.info("BedHandler: Foundation preset invalid: {} must be 1-6", command);
+                    }
+                }
+                break;
+            case CHANNEL_LEFT_NIGHT_STAND_OUTLET:
+            case CHANNEL_RIGHT_NIGHT_STAND_OUTLET:
+            case CHANNEL_LEFT_UNDER_BED_LIGHT:
+            case CHANNEL_RIGHT_UNDER_BED_LIGHT:
+                logger.debug("Received command {} on channel {} to control outlet", command, channelUID);
+                if (isFoundationInstalled() && command instanceof OnOffType) {
+                    try {
+                        logger.debug("BedHandler: Set foundation outlet channel {} to {} for bedId={}", channelId,
+                                command, bedId);
+                        FoundationOutlet outlet = FoundationOutlet.convertFromChannelId(channelId);
+                        SleepIQCloudHandler cloudHandler = getCloudHandler();
+                        if (cloudHandler != null) {
+                            FoundationOutletOperation operation = command == OnOffType.ON ? FoundationOutletOperation.ON
+                                    : FoundationOutletOperation.OFF;
+                            cloudHandler.setFoundationOutlet(bedId, outlet, operation);
+                        }
+                    } catch (IllegalArgumentException e) {
+                        logger.info("BedHandler: Can't convert channel {} to foundation outlet", channelId);
+                    }
+                }
+                break;
+            case CHANNEL_LEFT_POSITION_HEAD:
+            case CHANNEL_RIGHT_POSITION_HEAD:
+                logger.debug("Received command {} on channel {} to set position", command, channelUID);
+                if (groupId != null && isFoundationInstalled() && command instanceof DecimalType) {
+                    setFoundationPosition(groupId, channelId, command);
+                }
+
+            case CHANNEL_LEFT_POSITION_FOOT:
+            case CHANNEL_RIGHT_POSITION_FOOT:
+                logger.debug("Received command {} on channel {} to set position", command, channelUID);
+                if (groupId != null && isFoundationInstalled() && isFoundationFootAdjustable()
+                        && command instanceof DecimalType) {
+                    setFoundationPosition(groupId, channelId, command);
+                }
+                break;
         }
     }
 
@@ -179,6 +247,7 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
             return;
         }
         logger.debug("BedHandler: Updating bed status channels for bed {}", bedId);
+        BedStatus localPreviousStatus = previousStatus;
 
         BedSideStatus left = status.getLeftSide();
         updateState(CHANNEL_LEFT_IN_BED, left.isInBed() ? OnOffType.ON : OnOffType.OFF);
@@ -187,8 +256,8 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
         updateState(CHANNEL_LEFT_LAST_LINK, new StringType(left.getLastLink().toString()));
         updateState(CHANNEL_LEFT_ALERT_ID, new DecimalType(left.getAlertId()));
         updateState(CHANNEL_LEFT_ALERT_DETAILED_MESSAGE, new StringType(left.getAlertDetailedMessage()));
-        if (previousStatus != null) {
-            updateSleepDataChannels(previousStatus.getLeftSide(), left, sleeperLeft);
+        if (localPreviousStatus != null) {
+            updateSleepDataChannels(localPreviousStatus.getLeftSide(), left, sleeperLeft);
         }
 
         BedSideStatus right = status.getRightSide();
@@ -198,13 +267,57 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
         updateState(CHANNEL_RIGHT_LAST_LINK, new StringType(right.getLastLink().toString()));
         updateState(CHANNEL_RIGHT_ALERT_ID, new DecimalType(right.getAlertId()));
         updateState(CHANNEL_RIGHT_ALERT_DETAILED_MESSAGE, new StringType(right.getAlertDetailedMessage()));
-        if (previousStatus != null) {
-            updateSleepDataChannels(previousStatus.getRightSide(), right, sleeperRight);
+        if (localPreviousStatus != null) {
+            updateSleepDataChannels(localPreviousStatus.getRightSide(), right, sleeperRight);
         }
 
         previousStatus = status;
     }
 
+    @Override
+    public void onFoundationStateChanged(String bedId, final @Nullable FoundationStatusResponse status) {
+        if (status == null || !bedId.equals(this.bedId)) {
+            return;
+        }
+        logger.debug("BedHandler: Updating foundation status channels for bed {}", bedId);
+        updateState(CHANNEL_LEFT_POSITION_HEAD, new DecimalType(status.getLeftHeadPosition()));
+        updateState(CHANNEL_LEFT_POSITION_FOOT, new DecimalType(status.getLeftFootPosition()));
+        updateState(CHANNEL_RIGHT_POSITION_HEAD, new DecimalType(status.getRightHeadPosition()));
+        updateState(CHANNEL_RIGHT_POSITION_FOOT, new DecimalType(status.getRightFootPosition()));
+        updateState(CHANNEL_LEFT_FOUNDATION_PRESET, new DecimalType(status.getCurrentPositionPresetLeft().value()));
+        updateState(CHANNEL_RIGHT_FOUNDATION_PRESET, new DecimalType(status.getCurrentPositionPresetRight().value()));
+    }
+
+    @Override
+    public boolean isFoundationInstalled() {
+        return foundationFeatures != null;
+    }
+
+    private void setFoundationFeatures(@Nullable FoundationFeaturesResponse features) {
+        foundationFeatures = features;
+    }
+
+    private boolean isFoundationFootAdjustable() {
+        FoundationFeaturesResponse localFoundationFeatures = foundationFeatures;
+        return localFoundationFeatures != null ? localFoundationFeatures.hasFootControl() : false;
+    }
+
+    private void setFoundationPosition(String groupId, String channelId, Command command) {
+        try {
+            logger.debug("BedHandler: Set foundation position channel {} to {} for bedId={}", channelId, command,
+                    bedId);
+            Side side = Side.convertFromGroup(groupId);
+            FoundationActuator actuator = FoundationActuator.convertFromChannelId(channelId);
+            int position = ((DecimalType) command).intValue();
+            SleepIQCloudHandler cloudHandler = getCloudHandler();
+            if (cloudHandler != null) {
+                cloudHandler.setFoundationPosition(bedId, side, actuator, position, FoundationActuatorSpeed.SLOW);
+            }
+        } catch (IllegalArgumentException e) {
+            logger.info("BedHandler: Can't convert channel {} to foundation position", channelId);
+        }
+    }
+
     private void updateSleepDataChannels(BedSideStatus previousSideStatus, BedSideStatus currentSideStatus,
             @Nullable Sleeper sleeper) {
         if (sleeper == null) {
@@ -331,6 +444,13 @@ public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatus
                 return;
             }
             updateProperties(cloudHandler.updateProperties(bed, editProperties()));
+
+            logger.debug("BedHandler: Checking if foundation is installed for bedId={}", bedId);
+            if (isFoundationInstalled()) {
+                FoundationFeaturesResponse foundationFeaturesResponse = cloudHandler.getFoundationFeatures(bedId);
+                updateProperties(cloudHandler.updateFeatures(bedId, foundationFeaturesResponse, editProperties()));
+                setFoundationFeatures(foundationFeaturesResponse);
+            }
         }
     }
 
index 0e1bff9e3bc676288c5bb2afce10c464920af521..28ae093e9dbc7ba959df28dbea85b21be6c347c8 100644 (file)
@@ -33,6 +33,18 @@ channel-type.sleepiq.alertIdType.label = Alert ID
 channel-type.sleepiq.alertIdType.description = Identifier for an alert condition with the chamber
 channel-type.sleepiq.firstNameType.label = First Name
 channel-type.sleepiq.firstNameType.description = The first name of the sleeper
+channel-type.sleepiq.foundationPositionFootType.label = Foot Position
+channel-type.sleepiq.foundationPositionFootType.description = The position of the adjustable foot
+channel-type.sleepiq.foundationPositionHeadType.label = Head Position
+channel-type.sleepiq.foundationPositionHeadType.description = The position of the adjustable head
+channel-type.sleepiq.foundationPresetType.label = Preset
+channel-type.sleepiq.foundationPresetType.description = Bed position preset
+channel-type.sleepiq.foundationPresetType.state.option.1 = Favorite
+channel-type.sleepiq.foundationPresetType.state.option.2 = Read
+channel-type.sleepiq.foundationPresetType.state.option.3 = Watch TV
+channel-type.sleepiq.foundationPresetType.state.option.4 = Flat
+channel-type.sleepiq.foundationPresetType.state.option.5 = Zero G
+channel-type.sleepiq.foundationPresetType.state.option.6 = Snore
 channel-type.sleepiq.inBedType.label = In Bed
 channel-type.sleepiq.inBedType.description = The presence of a person or object on the chamber
 channel-type.sleepiq.lastLinkType.label = Last Link
@@ -43,6 +55,8 @@ channel-type.sleepiq.monthlyAverageRespirationRateType.label = Monthly Avg Respi
 channel-type.sleepiq.monthlyAverageRespirationRateType.description = The average respiration rate for the past month
 channel-type.sleepiq.monthlySleepIQType.label = Monthly Sleep IQ
 channel-type.sleepiq.monthlySleepIQType.description = The overall Sleep Quotient for the past month
+channel-type.sleepiq.nightStandOutletType.label = Night Stand
+channel-type.sleepiq.nightStandOutletType.description = The night stand outlet
 channel-type.sleepiq.pressureType.label = Pressure
 channel-type.sleepiq.pressureType.description = The current pressure inside the chamber
 channel-type.sleepiq.privacyMode.label = Privacy Mode
@@ -69,6 +83,13 @@ channel-type.sleepiq.todaySleepRestfulSecondsType.label = Today's Sleep Restful
 channel-type.sleepiq.todaySleepRestfulSecondsType.description = The total duration of restful sleep for today in seconds
 channel-type.sleepiq.todaySleepRestlessSecondsType.label = Today's Sleep Restless Duration
 channel-type.sleepiq.todaySleepRestlessSecondsType.description = The total duration of restless sleep for today in seconds
+channel-type.sleepiq.underBedLightType.label = Under Bed Light
+channel-type.sleepiq.underBedLightType.description = The under bed lighting
+
+# channel types
+
+channel-type.sleepiq.nightLightOutletType.label = Night Light
+channel-type.sleepiq.nightLightOutletType.description = The night light outlet
 
 # configuration messages
 
index 9942604ea28206db91c4305dd59772e94f3f54b0..a35c6aba22b5fef0c22372650b2d26685861d5a7 100644 (file)
@@ -69,7 +69,7 @@
                </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>
index 5388b928bbbec1698ac3666966e6605c86d8c32c..9bb8a4cfd62486b676f58a30b0aa082b19cff4c1 100644 (file)
                        </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>
diff --git a/bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/FoundationStatusResponseTest.java b/bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/FoundationStatusResponseTest.java
new file mode 100644 (file)
index 0000000..adbe511
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.api.model;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.FileReader;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.sleepiq.api.test.AbstractTest;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
+import org.openhab.binding.sleepiq.internal.api.impl.GsonGenerator;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link FoundationStatusResponseTest} tests deserialization of a foundation status
+ * response object.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class FoundationStatusResponseTest extends AbstractTest {
+    private static Gson gson = GsonGenerator.create(true);
+
+    @Test
+    public void testDeserializeAllFields() throws Exception {
+        try (FileReader reader = new FileReader(getTestDataFile("foundation-status-response.json"))) {
+            FoundationStatusResponse status = gson.fromJson(reader, FoundationStatusResponse.class);
+
+            assertNotNull(status);
+            assertEquals(0x0f, status.getLeftHeadPosition());
+            assertEquals(0x21, status.getLeftFootPosition());
+            assertEquals(0x0d, status.getRightHeadPosition());
+            assertEquals(0x04, status.getRightFootPosition());
+            assertEquals(FoundationPreset.SNORE, status.getCurrentPositionPresetLeft());
+            assertEquals(FoundationPreset.READ, status.getCurrentPositionPresetRight());
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationOutletTest.java b/bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationOutletTest.java
new file mode 100644 (file)
index 0000000..de574da
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.api.model;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.sleepiq.api.test.AbstractTest;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationOutletRequest;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
+import org.openhab.binding.sleepiq.internal.api.impl.GsonGenerator;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link SetFoundationOutletTest} tests serialization of the foundation outlet request.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class SetFoundationOutletTest extends AbstractTest {
+    private static Gson gson = GsonGenerator.create(true);
+
+    @Test
+    public void testSerializeAllFields() throws Exception {
+        FoundationOutletRequest preset = new FoundationOutletRequest()
+                .withFoundationOutlet(FoundationOutlet.RIGHT_UNDER_BED_LIGHT)
+                .withFoundationOutletOperation(FoundationOutletOperation.ON);
+        assertEquals(readJson("set-foundation-outlet.json"), gson.toJson(preset));
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationPositionTest.java b/bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationPositionTest.java
new file mode 100644 (file)
index 0000000..23b8520
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.api.model;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.sleepiq.api.test.AbstractTest;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationPositionRequest;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.Side;
+import org.openhab.binding.sleepiq.internal.api.impl.GsonGenerator;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link SetFoundationPositionTest} tests serialization of the foundation position request.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class SetFoundationPositionTest extends AbstractTest {
+    private static Gson gson = GsonGenerator.create(true);
+
+    @Test
+    public void testSerializeAllFields() throws Exception {
+        FoundationPositionRequest preset = new FoundationPositionRequest().withSide(Side.RIGHT).withPosition(75)
+                .withFoundationActuator(FoundationActuator.HEAD)
+                .withFoundationActuatorSpeed(FoundationActuatorSpeed.FAST);
+        assertEquals(readJson("set-foundation-position.json"), gson.toJson(preset));
+    }
+}
diff --git a/bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationPresetTest.java b/bundles/org.openhab.binding.sleepiq/src/test/java/org/openhab/binding/sleepiq/api/model/SetFoundationPresetTest.java
new file mode 100644 (file)
index 0000000..53b6a07
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.sleepiq.api.model;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.sleepiq.api.test.AbstractTest;
+import org.openhab.binding.sleepiq.internal.api.dto.FoundationPresetRequest;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
+import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
+import org.openhab.binding.sleepiq.internal.api.enums.Side;
+import org.openhab.binding.sleepiq.internal.api.impl.GsonGenerator;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link SetFoundationPresetTest} tests serialization of the foundation preset request.
+ *
+ * @author Mark Hilbush - Initial contribution
+ */
+@NonNullByDefault
+public class SetFoundationPresetTest extends AbstractTest {
+    private static Gson gson = GsonGenerator.create(true);
+
+    @Test
+    public void testSerializeAllFields() throws Exception {
+        FoundationPresetRequest preset = new FoundationPresetRequest().withSide(Side.RIGHT)
+                .withFoundationPreset(FoundationPreset.WATCH_TV)
+                .withFoundationActuatorSpeed(FoundationActuatorSpeed.FAST);
+        assertEquals(readJson("set-foundation-preset.json"), gson.toJson(preset));
+    }
+}
index 28cbabfbf5b58c52fa84b1b2b3e8cd473b0e461e..fc09b790b91b5c6105ef5c6178225fcc7c4e7db6 100644 (file)
@@ -34,17 +34,6 @@ import com.google.gson.Gson;
 public class SleeperTest extends AbstractTest {
     private static Gson gson = GsonGenerator.create(true);
 
-    @Test
-    public void testSerializeAllFields() throws Exception {
-        Sleeper sleeper = new Sleeper().withAccountId("-5555555555555555555").withAccountOwner(true).withActive(true)
-                .withAvatar("").withBedId("-9999999999999999999").withBirthMonth(6).withBirthYear("1970")
-                .withChild(false).withDuration("").withEmail("alice@domain.com").withEmailValidated(true)
-                .withFirstName("Alice").withHeight(64).withLastLogin("2017-02-17 20:19:36 CST").withLicenseVersion(6L)
-                .withMale(false).withSide(Side.RIGHT).withSleeperId("-1111111111111111111").withSleepGoal(450)
-                .withTimezone("US/Pacific").withUsername("alice@domain.com").withWeight(110).withZipCode("90210");
-        assertEquals(readJson("sleeper.json"), gson.toJson(sleeper));
-    }
-
     @Test
     public void testSerializeLastLoginNull() throws Exception {
         Sleeper sleeper = new Sleeper().withLastLogin("null");
diff --git a/bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/foundation-status-response.json b/bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/foundation-status-response.json
new file mode 100644 (file)
index 0000000..b6df201
--- /dev/null
@@ -0,0 +1,30 @@
+{
+  "fsType": "Single",
+  "fsNeedsHoming": false,
+  "fsIsMoving": false,
+  "fsStatusSummary": "48",
+  "fsConfigured": true,
+  "fsCurrentPositionPreset": "66",
+  "fsTimerPositionPreset": "00",
+  
+  "fsCurrentPositionPresetLeft": "Snore",
+  "fsLeftHeadPosition": "0f",
+  "fsLeftFootPosition": "21",
+  "fsLeftHeadActuatorMotorStatus": "08",
+  "fsLeftFootActuatorMotorStatus": "00",
+  "fsTimerPositionPresetLeft": "No timer running, thus no preset to active",
+  "fsLeftPositionTimerMSB": "00",
+  "fsLeftPositionTimerLSB": "00",
+
+  "fsCurrentPositionPresetRight": "Read",
+  "fsRightHeadPosition": "0d",
+  "fsRightFootPosition": "04",
+  "fsRightHeadActuatorMotorStatus": "08",
+  "fsRightFootActuatorMotorStatus": "00",
+  "fsTimerPositionPresetRight": "No timer running, thus no preset to active",
+  "fsRightPositionTimerMSB": "00",
+  "fsRightPositionTimerLSB": "00",
+
+  "fsOutletsOn": true,
+  "fsTimedOutletsOn": false
+}
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-outlet.json b/bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-outlet.json
new file mode 100644 (file)
index 0000000..b4532f8
--- /dev/null
@@ -0,0 +1,4 @@
+{
+  "outletId": 3,
+  "setting": 1
+}
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-position.json b/bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-position.json
new file mode 100644 (file)
index 0000000..87580d2
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "side": "R",
+  "position": 75,
+  "actuator": "H",
+  "speed": 0
+}
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-preset.json b/bundles/org.openhab.binding.sleepiq/src/test/resources/org/openhab/binding/sleepiq/api/model/set-foundation-preset.json
new file mode 100644 (file)
index 0000000..1614fe2
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "side": "R",
+  "preset": 3,
+  "speed": 0
+}
\ No newline at end of file