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

* Add foundation functionality

Signed-off-by: Mark Hilbush <mark@hilbush.com>
This commit is contained in:
Mark Hilbush
2023-03-28 11:27:31 -04:00
committed by GitHub
parent 1983bc36f1
commit 0a3d9ece3c
40 changed files with 2069 additions and 142 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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