]> git.basschouten.com Git - openhab-addons.git/blob
54bdf97a8da9499d7a78eaaa310a402a1c2ab9b4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.sleepiq.internal.handler;
14
15 import static org.openhab.binding.sleepiq.internal.SleepIQBindingConstants.*;
16
17 import java.util.Set;
18 import java.util.concurrent.TimeUnit;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.sleepiq.internal.api.dto.Bed;
23 import org.openhab.binding.sleepiq.internal.api.dto.BedSideStatus;
24 import org.openhab.binding.sleepiq.internal.api.dto.BedStatus;
25 import org.openhab.binding.sleepiq.internal.api.dto.FoundationFeaturesResponse;
26 import org.openhab.binding.sleepiq.internal.api.dto.FoundationStatusResponse;
27 import org.openhab.binding.sleepiq.internal.api.dto.SleepDataResponse;
28 import org.openhab.binding.sleepiq.internal.api.dto.Sleeper;
29 import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuator;
30 import org.openhab.binding.sleepiq.internal.api.enums.FoundationActuatorSpeed;
31 import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutlet;
32 import org.openhab.binding.sleepiq.internal.api.enums.FoundationOutletOperation;
33 import org.openhab.binding.sleepiq.internal.api.enums.FoundationPreset;
34 import org.openhab.binding.sleepiq.internal.api.enums.Side;
35 import org.openhab.binding.sleepiq.internal.config.SleepIQBedConfiguration;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.Units;
41 import org.openhab.core.thing.Bridge;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.ThingStatusInfo;
47 import org.openhab.core.thing.ThingTypeUID;
48 import org.openhab.core.thing.binding.BaseThingHandler;
49 import org.openhab.core.thing.binding.ThingHandler;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.RefreshType;
52 import org.openhab.core.types.UnDefType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * The {@link SleepIQDualBedHandler} is responsible for handling channel state updates from the cloud service.
58  *
59  * @author Gregory Moyer - Initial contribution
60  */
61 @NonNullByDefault
62 public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatusListener {
63     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Set.of(THING_TYPE_DUAL_BED);
64
65     private static final long GET_SLEEP_DATA_DELAY_MINUTES = 5;
66
67     private final Logger logger = LoggerFactory.getLogger(SleepIQDualBedHandler.class);
68
69     private volatile String bedId = "";
70
71     private @Nullable Sleeper sleeperLeft;
72     private @Nullable Sleeper sleeperRight;
73
74     private @Nullable BedStatus previousStatus;
75
76     private @Nullable FoundationFeaturesResponse foundationFeatures;
77
78     public SleepIQDualBedHandler(final Thing thing) {
79         super(thing);
80     }
81
82     @Override
83     public void initialize() {
84         Bridge bridge = getBridge();
85         if (bridge == null) {
86             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
87                     "No cloud service bridge has been configured");
88             return;
89         }
90         ThingHandler handler = bridge.getHandler();
91         if (!(handler instanceof SleepIQCloudHandler)) {
92             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Incorrect bridge thing found");
93             return;
94         }
95         String localBedId = getConfigAs(SleepIQBedConfiguration.class).bedId;
96         if (localBedId == null) {
97             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
98                     "Bed id not found in configuration");
99             return;
100         }
101         bedId = localBedId;
102         // Assume the bed has a foundation until we determine otherwise
103         setFoundationFeatures(new FoundationFeaturesResponse());
104
105         logger.debug("BedHandler: Registering SleepIQ bed status listener for bedId={}", bedId);
106         SleepIQCloudHandler cloudHandler = (SleepIQCloudHandler) handler;
107         cloudHandler.registerBedStatusListener(this);
108
109         if (ThingStatus.ONLINE != bridge.getStatus()) {
110             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
111         } else {
112             updateStatus(ThingStatus.ONLINE);
113             scheduler.execute(() -> {
114                 updateProperties();
115             });
116         }
117     }
118
119     @Override
120     public void dispose() {
121         SleepIQCloudHandler cloudHandler = getCloudHandler();
122         if (cloudHandler != null) {
123             cloudHandler.unregisterBedStatusListener(this);
124         }
125         bedId = "";
126     }
127
128     @Override
129     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
130         super.bridgeStatusChanged(bridgeStatusInfo);
131         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
132             updateProperties();
133         }
134     }
135
136     @Override
137     public void handleCommand(final ChannelUID channelUID, final Command command) {
138         if (command == RefreshType.REFRESH) {
139             // Channels will be refreshed automatically by cloud handler
140             return;
141         }
142         String channelId = channelUID.getId();
143         String groupId = channelUID.getGroupId();
144
145         switch (channelId) {
146             case CHANNEL_LEFT_SLEEP_NUMBER:
147             case CHANNEL_RIGHT_SLEEP_NUMBER:
148                 if (command instanceof DecimalType decimalCommand) {
149                     Side side = Side.convertFromGroup(groupId);
150                     logger.debug("BedHandler: Set sleepnumber to {} for bedId={}, side={}", command, bedId, side);
151                     SleepIQCloudHandler cloudHandler = getCloudHandler();
152                     if (cloudHandler != null) {
153                         cloudHandler.setSleepNumber(bedId, side, decimalCommand.intValue());
154                     }
155                 }
156                 break;
157             case CHANNEL_LEFT_PRIVACY_MODE:
158             case CHANNEL_RIGHT_PRIVACY_MODE:
159                 if (command instanceof OnOffType) {
160                     Side side = Side.convertFromGroup(groupId);
161                     logger.debug("BedHandler: Set sleepnumber to {} for bedId={}, side={}", command, bedId, side);
162                     SleepIQCloudHandler cloudHandler = getCloudHandler();
163                     if (cloudHandler != null) {
164                         cloudHandler.setPauseMode(bedId, command == OnOffType.ON ? true : false);
165                     }
166                 }
167                 break;
168             case CHANNEL_LEFT_FOUNDATION_PRESET:
169             case CHANNEL_RIGHT_FOUNDATION_PRESET:
170                 logger.debug("Received command {} on channel {} to set preset", command, channelUID);
171                 if (isFoundationInstalled() && command instanceof DecimalType decimalCommand) {
172                     try {
173                         Side side = Side.convertFromGroup(groupId);
174                         FoundationPreset preset = FoundationPreset.forValue(decimalCommand.intValue());
175                         logger.debug("BedHandler: Set foundation preset to {} for bedId={}, side={}", command, bedId,
176                                 side);
177                         SleepIQCloudHandler cloudHandler = getCloudHandler();
178                         if (cloudHandler != null) {
179                             cloudHandler.setFoundationPreset(bedId, side, preset, FoundationActuatorSpeed.SLOW);
180                         }
181                     } catch (IllegalArgumentException e) {
182                         logger.info("BedHandler: Foundation preset invalid: {} must be 1-6", command);
183                     }
184                 }
185                 break;
186             case CHANNEL_LEFT_NIGHT_STAND_OUTLET:
187             case CHANNEL_RIGHT_NIGHT_STAND_OUTLET:
188             case CHANNEL_LEFT_UNDER_BED_LIGHT:
189             case CHANNEL_RIGHT_UNDER_BED_LIGHT:
190                 logger.debug("Received command {} on channel {} to control outlet", command, channelUID);
191                 if (isFoundationInstalled() && command instanceof OnOffType) {
192                     try {
193                         logger.debug("BedHandler: Set foundation outlet channel {} to {} for bedId={}", channelId,
194                                 command, bedId);
195                         FoundationOutlet outlet = FoundationOutlet.convertFromChannelId(channelId);
196                         SleepIQCloudHandler cloudHandler = getCloudHandler();
197                         if (cloudHandler != null) {
198                             FoundationOutletOperation operation = command == OnOffType.ON ? FoundationOutletOperation.ON
199                                     : FoundationOutletOperation.OFF;
200                             cloudHandler.setFoundationOutlet(bedId, outlet, operation);
201                         }
202                     } catch (IllegalArgumentException e) {
203                         logger.info("BedHandler: Can't convert channel {} to foundation outlet", channelId);
204                     }
205                 }
206                 break;
207             case CHANNEL_LEFT_POSITION_HEAD:
208             case CHANNEL_RIGHT_POSITION_HEAD:
209                 logger.debug("Received command {} on channel {} to set position", command, channelUID);
210                 if (groupId != null && isFoundationInstalled() && command instanceof DecimalType) {
211                     setFoundationPosition(groupId, channelId, command);
212                 }
213
214             case CHANNEL_LEFT_POSITION_FOOT:
215             case CHANNEL_RIGHT_POSITION_FOOT:
216                 logger.debug("Received command {} on channel {} to set position", command, channelUID);
217                 if (groupId != null && isFoundationInstalled() && isFoundationFootAdjustable()
218                         && command instanceof DecimalType) {
219                     setFoundationPosition(groupId, channelId, command);
220                 }
221                 break;
222         }
223     }
224
225     @Override
226     public void onSleeperChanged(final @Nullable Sleeper sleeper) {
227         if (sleeper == null || !sleeper.getBedId().equals(bedId)) {
228             return;
229         }
230         logger.debug("BedHandler: Updating sleeper information channels for bed={}, side={}", bedId, sleeper.getSide());
231
232         if (sleeper.getSide().equals(Side.LEFT)) {
233             sleeperLeft = sleeper;
234             updateState(CHANNEL_LEFT_FIRST_NAME, new StringType(sleeper.getFirstName()));
235             updateState(CHANNEL_LEFT_SLEEP_GOAL_MINUTES, new QuantityType<>(sleeper.getSleepGoal(), Units.MINUTE));
236         } else {
237             sleeperRight = sleeper;
238             updateState(CHANNEL_RIGHT_FIRST_NAME, new StringType(sleeper.getFirstName()));
239             updateState(CHANNEL_RIGHT_SLEEP_GOAL_MINUTES, new QuantityType<>(sleeper.getSleepGoal(), Units.MINUTE));
240         }
241     }
242
243     @Override
244     public void onBedStateChanged(final @Nullable BedStatus status) {
245         if (status == null || !status.getBedId().equals(bedId)) {
246             return;
247         }
248         logger.debug("BedHandler: Updating bed status channels for bed {}", bedId);
249         BedStatus localPreviousStatus = previousStatus;
250
251         BedSideStatus left = status.getLeftSide();
252         updateState(CHANNEL_LEFT_IN_BED, OnOffType.from(left.isInBed()));
253         updateState(CHANNEL_LEFT_SLEEP_NUMBER, new DecimalType(left.getSleepNumber()));
254         updateState(CHANNEL_LEFT_PRESSURE, new DecimalType(left.getPressure()));
255         updateState(CHANNEL_LEFT_LAST_LINK, new StringType(left.getLastLink().toString()));
256         updateState(CHANNEL_LEFT_ALERT_ID, new DecimalType(left.getAlertId()));
257         updateState(CHANNEL_LEFT_ALERT_DETAILED_MESSAGE, new StringType(left.getAlertDetailedMessage()));
258         if (localPreviousStatus != null) {
259             updateSleepDataChannels(localPreviousStatus.getLeftSide(), left, sleeperLeft);
260         }
261
262         BedSideStatus right = status.getRightSide();
263         updateState(CHANNEL_RIGHT_IN_BED, OnOffType.from(right.isInBed()));
264         updateState(CHANNEL_RIGHT_SLEEP_NUMBER, new DecimalType(right.getSleepNumber()));
265         updateState(CHANNEL_RIGHT_PRESSURE, new DecimalType(right.getPressure()));
266         updateState(CHANNEL_RIGHT_LAST_LINK, new StringType(right.getLastLink().toString()));
267         updateState(CHANNEL_RIGHT_ALERT_ID, new DecimalType(right.getAlertId()));
268         updateState(CHANNEL_RIGHT_ALERT_DETAILED_MESSAGE, new StringType(right.getAlertDetailedMessage()));
269         if (localPreviousStatus != null) {
270             updateSleepDataChannels(localPreviousStatus.getRightSide(), right, sleeperRight);
271         }
272
273         previousStatus = status;
274     }
275
276     @Override
277     public void onFoundationStateChanged(String bedId, final @Nullable FoundationStatusResponse status) {
278         if (status == null || !bedId.equals(this.bedId)) {
279             return;
280         }
281         logger.debug("BedHandler: Updating foundation status channels for bed {}", bedId);
282         updateState(CHANNEL_LEFT_POSITION_HEAD, new DecimalType(status.getLeftHeadPosition()));
283         updateState(CHANNEL_LEFT_POSITION_FOOT, new DecimalType(status.getLeftFootPosition()));
284         updateState(CHANNEL_RIGHT_POSITION_HEAD, new DecimalType(status.getRightHeadPosition()));
285         updateState(CHANNEL_RIGHT_POSITION_FOOT, new DecimalType(status.getRightFootPosition()));
286         updateState(CHANNEL_LEFT_FOUNDATION_PRESET, new DecimalType(status.getCurrentPositionPresetLeft().value()));
287         updateState(CHANNEL_RIGHT_FOUNDATION_PRESET, new DecimalType(status.getCurrentPositionPresetRight().value()));
288     }
289
290     @Override
291     public boolean isFoundationInstalled() {
292         return foundationFeatures != null;
293     }
294
295     private void setFoundationFeatures(@Nullable FoundationFeaturesResponse features) {
296         foundationFeatures = features;
297     }
298
299     private boolean isFoundationFootAdjustable() {
300         FoundationFeaturesResponse localFoundationFeatures = foundationFeatures;
301         return localFoundationFeatures != null ? localFoundationFeatures.hasFootControl() : false;
302     }
303
304     private void setFoundationPosition(String groupId, String channelId, Command command) {
305         try {
306             logger.debug("BedHandler: Set foundation position channel {} to {} for bedId={}", channelId, command,
307                     bedId);
308             Side side = Side.convertFromGroup(groupId);
309             FoundationActuator actuator = FoundationActuator.convertFromChannelId(channelId);
310             int position = ((DecimalType) command).intValue();
311             SleepIQCloudHandler cloudHandler = getCloudHandler();
312             if (cloudHandler != null) {
313                 cloudHandler.setFoundationPosition(bedId, side, actuator, position, FoundationActuatorSpeed.SLOW);
314             }
315         } catch (IllegalArgumentException e) {
316             logger.info("BedHandler: Can't convert channel {} to foundation position", channelId);
317         }
318     }
319
320     private void updateSleepDataChannels(BedSideStatus previousSideStatus, BedSideStatus currentSideStatus,
321             @Nullable Sleeper sleeper) {
322         if (sleeper == null) {
323             logger.debug("BedHandler: Can't update sleep data channels because sleeper is null");
324             return;
325         }
326         if (previousSideStatus.isInBed() && !currentSideStatus.isInBed()) {
327             logger.debug("BedHandler: Bed status changed from IN BED to OUT OF BED for {}, side {}", bedId,
328                     sleeper.getSide());
329             scheduler.schedule(() -> {
330                 updateDailySleepDataChannels(sleeper);
331                 updateMonthlySleepDataChannels(sleeper);
332             }, GET_SLEEP_DATA_DELAY_MINUTES, TimeUnit.MINUTES);
333         }
334     }
335
336     public void updateDailySleepDataChannels(final @Nullable Sleeper sleeper) {
337         SleepIQCloudHandler cloudHandler = getCloudHandler();
338         if (cloudHandler == null || sleeper == null) {
339             return;
340         }
341         SleepDataResponse sleepData = cloudHandler.getDailySleepData(sleeper.getSleeperId());
342         if (sleepData == null) {
343             logger.debug("BedHandler: Received no daily sleep data for bedId={}, sleeperId={}", sleeper.getBedId(),
344                     sleeper.getSleeperId());
345             return;
346         }
347
348         logger.debug("BedHandler: UPDATING DAILY SLEEP DATA CHANNELS for bedId={}, sleeperId={}", sleeper.getBedId(),
349                 sleeper.getSleeperId());
350         if (sleepData.getSleepDataDays() == null || sleepData.getSleepDataDays().size() != 1) {
351             if (sleeper.getSide().equals(Side.LEFT)) {
352                 updateState(CHANNEL_LEFT_TODAY_SLEEP_IQ, UnDefType.UNDEF);
353                 updateState(CHANNEL_LEFT_TODAY_AVG_HEART_RATE, UnDefType.UNDEF);
354                 updateState(CHANNEL_LEFT_TODAY_AVG_RESPIRATION_RATE, UnDefType.UNDEF);
355                 updateState(CHANNEL_LEFT_TODAY_MESSAGE, UnDefType.UNDEF);
356                 updateState(CHANNEL_LEFT_TODAY_SLEEP_DURATION_SECONDS, UnDefType.UNDEF);
357                 updateState(CHANNEL_LEFT_TODAY_SLEEP_IN_BED_SECONDS, UnDefType.UNDEF);
358                 updateState(CHANNEL_LEFT_TODAY_SLEEP_OUT_OF_BED_SECONDS, UnDefType.UNDEF);
359                 updateState(CHANNEL_LEFT_TODAY_SLEEP_RESTFUL_SECONDS, UnDefType.UNDEF);
360                 updateState(CHANNEL_LEFT_TODAY_SLEEP_RESTLESS_SECONDS, UnDefType.UNDEF);
361             } else {
362                 updateState(CHANNEL_RIGHT_TODAY_SLEEP_IQ, UnDefType.UNDEF);
363                 updateState(CHANNEL_RIGHT_TODAY_AVG_HEART_RATE, UnDefType.UNDEF);
364                 updateState(CHANNEL_RIGHT_TODAY_AVG_RESPIRATION_RATE, UnDefType.UNDEF);
365                 updateState(CHANNEL_RIGHT_TODAY_MESSAGE, UnDefType.UNDEF);
366                 updateState(CHANNEL_RIGHT_TODAY_SLEEP_DURATION_SECONDS, UnDefType.UNDEF);
367                 updateState(CHANNEL_RIGHT_TODAY_SLEEP_IN_BED_SECONDS, UnDefType.UNDEF);
368                 updateState(CHANNEL_RIGHT_TODAY_SLEEP_OUT_OF_BED_SECONDS, UnDefType.UNDEF);
369                 updateState(CHANNEL_RIGHT_TODAY_SLEEP_RESTFUL_SECONDS, UnDefType.UNDEF);
370                 updateState(CHANNEL_RIGHT_TODAY_SLEEP_RESTLESS_SECONDS, UnDefType.UNDEF);
371             }
372             return;
373         } else if (sleeper.getSide().equals(Side.LEFT)) {
374             updateState(CHANNEL_LEFT_TODAY_SLEEP_IQ, new DecimalType(sleepData.getAverageSleepIQ()));
375             updateState(CHANNEL_LEFT_TODAY_AVG_HEART_RATE, new DecimalType(sleepData.getAverageHeartRate()));
376             updateState(CHANNEL_LEFT_TODAY_AVG_RESPIRATION_RATE,
377                     new DecimalType(sleepData.getAverageRespirationRate()));
378             updateState(CHANNEL_LEFT_TODAY_MESSAGE, new StringType(sleepData.getSleepDataDays().get(0).getMessage()));
379             updateState(CHANNEL_LEFT_TODAY_SLEEP_DURATION_SECONDS,
380                     new QuantityType<>(sleepData.getTotalSleepSessionTime(), Units.SECOND));
381             updateState(CHANNEL_LEFT_TODAY_SLEEP_IN_BED_SECONDS,
382                     new QuantityType<>(sleepData.getTotalInBedSeconds(), Units.SECOND));
383             updateState(CHANNEL_LEFT_TODAY_SLEEP_OUT_OF_BED_SECONDS,
384                     new QuantityType<>(sleepData.getTotalOutOfBedSeconds(), Units.SECOND));
385             updateState(CHANNEL_LEFT_TODAY_SLEEP_RESTFUL_SECONDS,
386                     new QuantityType<>(sleepData.getTotalRestfulSeconds(), Units.SECOND));
387             updateState(CHANNEL_LEFT_TODAY_SLEEP_RESTLESS_SECONDS,
388                     new QuantityType<>(sleepData.getTotalRestlessSeconds(), Units.SECOND));
389         } else if (sleeper.getSide().equals(Side.RIGHT)) {
390             updateState(CHANNEL_RIGHT_TODAY_SLEEP_IQ, new DecimalType(sleepData.getAverageSleepIQ()));
391             updateState(CHANNEL_RIGHT_TODAY_AVG_HEART_RATE, new DecimalType(sleepData.getAverageHeartRate()));
392             updateState(CHANNEL_RIGHT_TODAY_AVG_RESPIRATION_RATE,
393                     new DecimalType(sleepData.getAverageRespirationRate()));
394             updateState(CHANNEL_RIGHT_TODAY_MESSAGE, new StringType(sleepData.getSleepDataDays().get(0).getMessage()));
395             updateState(CHANNEL_RIGHT_TODAY_SLEEP_DURATION_SECONDS,
396                     new QuantityType<>(sleepData.getTotalSleepSessionTime(), Units.SECOND));
397             updateState(CHANNEL_RIGHT_TODAY_SLEEP_IN_BED_SECONDS,
398                     new QuantityType<>(sleepData.getTotalInBedSeconds(), Units.SECOND));
399             updateState(CHANNEL_RIGHT_TODAY_SLEEP_OUT_OF_BED_SECONDS,
400                     new QuantityType<>(sleepData.getTotalOutOfBedSeconds(), Units.SECOND));
401             updateState(CHANNEL_RIGHT_TODAY_SLEEP_RESTFUL_SECONDS,
402                     new QuantityType<>(sleepData.getTotalRestfulSeconds(), Units.SECOND));
403             updateState(CHANNEL_RIGHT_TODAY_SLEEP_RESTLESS_SECONDS,
404                     new QuantityType<>(sleepData.getTotalRestlessSeconds(), Units.SECOND));
405         }
406     }
407
408     public void updateMonthlySleepDataChannels(final @Nullable Sleeper sleeper) {
409         SleepIQCloudHandler cloudHandler = getCloudHandler();
410         if (cloudHandler == null || sleeper == null) {
411             return;
412         }
413         SleepDataResponse sleepData = cloudHandler.getMonthlySleepData(sleeper.getSleeperId());
414         if (sleepData == null) {
415             logger.debug("BedHandler: Received no monthly sleep data for bedId={}, sleeperId={}", sleeper.getBedId(),
416                     sleeper.getSleeperId());
417             return;
418         }
419
420         logger.debug("BedHandler: UPDATING MONTHLY SLEEP DATA CHANNELS for bedId={}, sleeperId={}", sleeper.getBedId(),
421                 sleeper.getSleeperId());
422         if (sleeper.getSide().equals(Side.LEFT)) {
423             updateState(CHANNEL_LEFT_MONTHLY_SLEEP_IQ, new DecimalType(sleepData.getAverageSleepIQ()));
424             updateState(CHANNEL_LEFT_MONTHLY_AVG_HEART_RATE, new DecimalType(sleepData.getAverageHeartRate()));
425             updateState(CHANNEL_LEFT_MONTHLY_AVG_RESPIRATION_RATE,
426                     new DecimalType(sleepData.getAverageRespirationRate()));
427         } else {
428             updateState(CHANNEL_RIGHT_MONTHLY_SLEEP_IQ, new DecimalType(sleepData.getAverageSleepIQ()));
429             updateState(CHANNEL_RIGHT_MONTHLY_AVG_HEART_RATE, new DecimalType(sleepData.getAverageHeartRate()));
430             updateState(CHANNEL_RIGHT_MONTHLY_AVG_RESPIRATION_RATE,
431                     new DecimalType(sleepData.getAverageRespirationRate()));
432         }
433     }
434
435     private void updateProperties() {
436         logger.debug("BedHandler: Updating bed properties for bedId={}", bedId);
437         SleepIQCloudHandler cloudHandler = getCloudHandler();
438         if (cloudHandler != null) {
439             Bed bed = cloudHandler.getBed(bedId);
440             if (bed == null) {
441                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
442                         "No bed found with ID " + bedId);
443                 return;
444             }
445             updateProperties(cloudHandler.updateProperties(bed, editProperties()));
446
447             logger.debug("BedHandler: Checking if foundation is installed for bedId={}", bedId);
448             if (isFoundationInstalled()) {
449                 FoundationFeaturesResponse foundationFeaturesResponse = cloudHandler.getFoundationFeatures(bedId);
450                 updateProperties(cloudHandler.updateFeatures(bedId, foundationFeaturesResponse, editProperties()));
451                 setFoundationFeatures(foundationFeaturesResponse);
452             }
453         }
454     }
455
456     private @Nullable SleepIQCloudHandler getCloudHandler() {
457         Bridge bridge = getBridge();
458         if (bridge != null) {
459             return (SleepIQCloudHandler) bridge.getHandler();
460         }
461         return null;
462     }
463 }