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