2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.sleepiq.internal.handler;
15 import static org.openhab.binding.sleepiq.internal.SleepIQBindingConstants.*;
17 import java.util.Collections;
19 import java.util.concurrent.TimeUnit;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.sleepiq.internal.api.dto.Bed;
24 import org.openhab.binding.sleepiq.internal.api.dto.BedSideStatus;
25 import org.openhab.binding.sleepiq.internal.api.dto.BedStatus;
26 import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
27 import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
28 import org.openhab.binding.sleepiq.internal.api.enums.Side;
29 import org.openhab.binding.sleepiq.internal.config.SleepIQBedConfiguration;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.OnOffType;
32 import org.openhab.core.library.types.QuantityType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.library.unit.Units;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.ThingStatusInfo;
41 import org.openhab.core.thing.ThingTypeUID;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.thing.binding.ThingHandler;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * The {@link SleepIQDualBedHandler} is responsible for handling channel state updates from the cloud service.
53 * @author Gregory Moyer - Initial contribution
56 public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatusListener {
57 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Collections.singleton(THING_TYPE_DUAL_BED);
59 private static final long GET_SLEEP_DATA_DELAY_MINUTES = 5;
61 private final Logger logger = LoggerFactory.getLogger(SleepIQDualBedHandler.class);
63 private volatile @Nullable String bedId;
65 private @Nullable Sleeper sleeperLeft;
66 private @Nullable Sleeper sleeperRight;
68 private @Nullable BedStatus previousStatus;
70 public SleepIQDualBedHandler(final Thing thing) {
75 public void initialize() {
76 Bridge bridge = getBridge();
78 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
79 "No cloud service bridge has been configured");
82 ThingHandler handler = bridge.getHandler();
83 if (!(handler instanceof SleepIQCloudHandler)) {
84 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Incorrect bridge thing found");
87 bedId = getConfigAs(SleepIQBedConfiguration.class).bedId;
89 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
90 "Bed id not found in configuration");
94 logger.debug("BedHandler: Registering SleepIQ bed status listener for bedId={}", bedId);
95 SleepIQCloudHandler cloudHandler = (SleepIQCloudHandler) handler;
96 cloudHandler.registerBedStatusListener(this);
98 if (ThingStatus.ONLINE != bridge.getStatus()) {
99 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
101 updateStatus(ThingStatus.ONLINE);
102 scheduler.execute(() -> {
109 public void dispose() {
110 SleepIQCloudHandler cloudHandler = getCloudHandler();
111 if (cloudHandler != null) {
112 cloudHandler.unregisterBedStatusListener(this);
118 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
119 super.bridgeStatusChanged(bridgeStatusInfo);
120 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
126 public void handleCommand(final ChannelUID channelUID, final Command command) {
127 if (command == RefreshType.REFRESH) {
128 // Channels will be refreshed automatically by cloud handler
132 switch (channelUID.getId()) {
133 case CHANNEL_LEFT_SLEEP_NUMBER:
134 case CHANNEL_RIGHT_SLEEP_NUMBER:
135 if (command instanceof DecimalType) {
136 Side side = Side.convertFromGroup(channelUID.getGroupId());
137 logger.debug("BedHandler: Set sleepnumber to {} for bedId={}, side={}", command, bedId, side);
138 SleepIQCloudHandler cloudHandler = getCloudHandler();
139 if (cloudHandler != null) {
140 cloudHandler.setSleepNumber(bedId, side, ((DecimalType) command).intValue());
144 case CHANNEL_LEFT_PRIVACY_MODE:
145 case CHANNEL_RIGHT_PRIVACY_MODE:
146 if (command instanceof OnOffType) {
147 Side side = Side.convertFromGroup(channelUID.getGroupId());
148 logger.debug("BedHandler: Set sleepnumber to {} for bedId={}, side={}", command, bedId, side);
149 SleepIQCloudHandler cloudHandler = getCloudHandler();
150 if (cloudHandler != null) {
151 cloudHandler.setPauseMode(bedId, command == OnOffType.ON ? true : false);
159 public void onSleeperChanged(final @Nullable Sleeper sleeper) {
160 if (sleeper == null || !sleeper.getBedId().equals(bedId)) {
163 logger.debug("BedHandler: Updating sleeper information channels for bed={}, side={}", bedId, sleeper.getSide());
165 if (sleeper.getSide().equals(Side.LEFT)) {
166 sleeperLeft = sleeper;
167 updateState(CHANNEL_LEFT_FIRST_NAME, new StringType(sleeper.getFirstName()));
168 updateState(CHANNEL_LEFT_SLEEP_GOAL_MINUTES, new QuantityType<>(sleeper.getSleepGoal(), Units.MINUTE));
170 sleeperRight = sleeper;
171 updateState(CHANNEL_RIGHT_FIRST_NAME, new StringType(sleeper.getFirstName()));
172 updateState(CHANNEL_RIGHT_SLEEP_GOAL_MINUTES, new QuantityType<>(sleeper.getSleepGoal(), Units.MINUTE));
177 public void onBedStateChanged(final @Nullable BedStatus status) {
178 if (status == null || !status.getBedId().equals(bedId)) {
181 logger.debug("BedHandler: Updating bed status channels for bed {}", bedId);
183 BedSideStatus left = status.getLeftSide();
184 updateState(CHANNEL_LEFT_IN_BED, left.isInBed() ? OnOffType.ON : OnOffType.OFF);
185 updateState(CHANNEL_LEFT_SLEEP_NUMBER, new DecimalType(left.getSleepNumber()));
186 updateState(CHANNEL_LEFT_PRESSURE, new DecimalType(left.getPressure()));
187 updateState(CHANNEL_LEFT_LAST_LINK, new StringType(left.getLastLink().toString()));
188 updateState(CHANNEL_LEFT_ALERT_ID, new DecimalType(left.getAlertId()));
189 updateState(CHANNEL_LEFT_ALERT_DETAILED_MESSAGE, new StringType(left.getAlertDetailedMessage()));
190 if (previousStatus != null) {
191 updateSleepDataChannels(previousStatus.getLeftSide(), left, sleeperLeft);
194 BedSideStatus right = status.getRightSide();
195 updateState(CHANNEL_RIGHT_IN_BED, right.isInBed() ? OnOffType.ON : OnOffType.OFF);
196 updateState(CHANNEL_RIGHT_SLEEP_NUMBER, new DecimalType(right.getSleepNumber()));
197 updateState(CHANNEL_RIGHT_PRESSURE, new DecimalType(right.getPressure()));
198 updateState(CHANNEL_RIGHT_LAST_LINK, new StringType(right.getLastLink().toString()));
199 updateState(CHANNEL_RIGHT_ALERT_ID, new DecimalType(right.getAlertId()));
200 updateState(CHANNEL_RIGHT_ALERT_DETAILED_MESSAGE, new StringType(right.getAlertDetailedMessage()));
201 if (previousStatus != null) {
202 updateSleepDataChannels(previousStatus.getRightSide(), right, sleeperRight);
205 previousStatus = status;
208 private void updateSleepDataChannels(BedSideStatus previousSideStatus, BedSideStatus currentSideStatus,
209 @Nullable Sleeper sleeper) {
210 if (sleeper == null) {
211 logger.debug("BedHandler: Can't update sleep data channels because sleeper is null");
214 if (previousSideStatus.isInBed() && !currentSideStatus.isInBed()) {
215 logger.debug("BedHandler: Bed status changed from IN BED to OUT OF BED for {}, side {}", bedId,
217 scheduler.schedule(() -> {
218 updateDailySleepDataChannels(sleeper);
219 updateMonthlySleepDataChannels(sleeper);
220 }, GET_SLEEP_DATA_DELAY_MINUTES, TimeUnit.MINUTES);
224 public void updateDailySleepDataChannels(final @Nullable Sleeper sleeper) {
225 SleepIQCloudHandler cloudHandler = getCloudHandler();
226 if (cloudHandler == null || sleeper == null) {
229 SleepDataResponse sleepData = cloudHandler.getDailySleepData(sleeper.getSleeperId());
230 if (sleepData == null) {
231 logger.debug("BedHandler: Received no daily sleep data for bedId={}, sleeperId={}", sleeper.getBedId(),
232 sleeper.getSleeperId());
236 logger.debug("BedHandler: UPDATING DAILY SLEEP DATA CHANNELS for bedId={}, sleeperId={}", sleeper.getBedId(),
237 sleeper.getSleeperId());
238 if (sleepData.getSleepDataDays() == null || sleepData.getSleepDataDays().size() != 1) {
239 if (sleeper.getSide().equals(Side.LEFT)) {
240 updateState(CHANNEL_LEFT_TODAY_SLEEP_IQ, UnDefType.UNDEF);
241 updateState(CHANNEL_LEFT_TODAY_AVG_HEART_RATE, UnDefType.UNDEF);
242 updateState(CHANNEL_LEFT_TODAY_AVG_RESPIRATION_RATE, UnDefType.UNDEF);
243 updateState(CHANNEL_LEFT_TODAY_MESSAGE, UnDefType.UNDEF);
244 updateState(CHANNEL_LEFT_TODAY_SLEEP_DURATION_SECONDS, UnDefType.UNDEF);
246 updateState(CHANNEL_RIGHT_TODAY_SLEEP_IQ, UnDefType.UNDEF);
247 updateState(CHANNEL_RIGHT_TODAY_AVG_HEART_RATE, UnDefType.UNDEF);
248 updateState(CHANNEL_RIGHT_TODAY_AVG_RESPIRATION_RATE, UnDefType.UNDEF);
249 updateState(CHANNEL_RIGHT_TODAY_MESSAGE, UnDefType.UNDEF);
250 updateState(CHANNEL_RIGHT_TODAY_SLEEP_DURATION_SECONDS, UnDefType.UNDEF);
253 } else if (sleeper.getSide().equals(Side.LEFT)) {
254 updateState(CHANNEL_LEFT_TODAY_SLEEP_IQ, new DecimalType(sleepData.getAverageSleepIQ()));
255 updateState(CHANNEL_LEFT_TODAY_AVG_HEART_RATE, new DecimalType(sleepData.getAverageHeartRate()));
256 updateState(CHANNEL_LEFT_TODAY_AVG_RESPIRATION_RATE,
257 new DecimalType(sleepData.getAverageRespirationRate()));
258 updateState(CHANNEL_LEFT_TODAY_MESSAGE, new StringType(sleepData.getSleepDataDays().get(0).getMessage()));
259 updateState(CHANNEL_LEFT_TODAY_SLEEP_DURATION_SECONDS,
260 new QuantityType<>(sleepData.getTotalSleepSessionTime(), Units.SECOND));
261 } else if (sleeper.getSide().equals(Side.RIGHT)) {
262 updateState(CHANNEL_RIGHT_TODAY_SLEEP_IQ, new DecimalType(sleepData.getAverageSleepIQ()));
263 updateState(CHANNEL_RIGHT_TODAY_AVG_HEART_RATE, new DecimalType(sleepData.getAverageHeartRate()));
264 updateState(CHANNEL_RIGHT_TODAY_AVG_RESPIRATION_RATE,
265 new DecimalType(sleepData.getAverageRespirationRate()));
266 updateState(CHANNEL_RIGHT_TODAY_MESSAGE, new StringType(sleepData.getSleepDataDays().get(0).getMessage()));
267 updateState(CHANNEL_RIGHT_TODAY_SLEEP_DURATION_SECONDS,
268 new QuantityType<>(sleepData.getTotalSleepSessionTime(), Units.SECOND));
272 public void updateMonthlySleepDataChannels(final @Nullable Sleeper sleeper) {
273 SleepIQCloudHandler cloudHandler = getCloudHandler();
274 if (cloudHandler == null || sleeper == null) {
277 SleepDataResponse sleepData = cloudHandler.getMonthlySleepData(sleeper.getSleeperId());
278 if (sleepData == null) {
279 logger.debug("BedHandler: Received no monthly sleep data for bedId={}, sleeperId={}", sleeper.getBedId(),
280 sleeper.getSleeperId());
284 logger.debug("BedHandler: UPDATING MONTHLY SLEEP DATA CHANNELS for bedId={}, sleeperId={}", sleeper.getBedId(),
285 sleeper.getSleeperId());
286 if (sleeper.getSide().equals(Side.LEFT)) {
287 updateState(CHANNEL_LEFT_MONTHLY_SLEEP_IQ, new DecimalType(sleepData.getAverageSleepIQ()));
288 updateState(CHANNEL_LEFT_MONTHLY_AVG_HEART_RATE, new DecimalType(sleepData.getAverageHeartRate()));
289 updateState(CHANNEL_LEFT_MONTHLY_AVG_RESPIRATION_RATE,
290 new DecimalType(sleepData.getAverageRespirationRate()));
292 updateState(CHANNEL_RIGHT_MONTHLY_SLEEP_IQ, new DecimalType(sleepData.getAverageSleepIQ()));
293 updateState(CHANNEL_RIGHT_MONTHLY_AVG_HEART_RATE, new DecimalType(sleepData.getAverageHeartRate()));
294 updateState(CHANNEL_RIGHT_MONTHLY_AVG_RESPIRATION_RATE,
295 new DecimalType(sleepData.getAverageRespirationRate()));
299 private void updateProperties() {
300 logger.debug("BedHandler: Updating bed properties for bedId={}", bedId);
301 SleepIQCloudHandler cloudHandler = getCloudHandler();
302 if (cloudHandler != null) {
303 Bed bed = cloudHandler.getBed(bedId);
305 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
306 "No bed found with ID " + bedId);
309 updateProperties(cloudHandler.updateProperties(bed, editProperties()));
313 private @Nullable SleepIQCloudHandler getCloudHandler() {
314 Bridge bridge = getBridge();
315 if (bridge != null) {
316 return (SleepIQCloudHandler) bridge.getHandler();