]> git.basschouten.com Git - openhab-addons.git/blob
b20b6884f086581bc6cf1d49e83774187034cfd0
[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.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;
49
50 /**
51  * The {@link SleepIQDualBedHandler} is responsible for handling channel state updates from the cloud service.
52  *
53  * @author Gregory Moyer - Initial contribution
54  */
55 @NonNullByDefault
56 public class SleepIQDualBedHandler extends BaseThingHandler implements BedStatusListener {
57     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Collections.singleton(THING_TYPE_DUAL_BED);
58
59     private static final long GET_SLEEP_DATA_DELAY_MINUTES = 5;
60
61     private final Logger logger = LoggerFactory.getLogger(SleepIQDualBedHandler.class);
62
63     private volatile @Nullable String bedId;
64
65     private @Nullable Sleeper sleeperLeft;
66     private @Nullable Sleeper sleeperRight;
67
68     private @Nullable BedStatus previousStatus;
69
70     public SleepIQDualBedHandler(final Thing thing) {
71         super(thing);
72     }
73
74     @Override
75     public void initialize() {
76         Bridge bridge = getBridge();
77         if (bridge == null) {
78             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
79                     "No cloud service bridge has been configured");
80             return;
81         }
82         ThingHandler handler = bridge.getHandler();
83         if (!(handler instanceof SleepIQCloudHandler)) {
84             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Incorrect bridge thing found");
85             return;
86         }
87         bedId = getConfigAs(SleepIQBedConfiguration.class).bedId;
88         if (bedId == null) {
89             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
90                     "Bed id not found in configuration");
91             return;
92         }
93
94         logger.debug("BedHandler: Registering SleepIQ bed status listener for bedId={}", bedId);
95         SleepIQCloudHandler cloudHandler = (SleepIQCloudHandler) handler;
96         cloudHandler.registerBedStatusListener(this);
97
98         if (ThingStatus.ONLINE != bridge.getStatus()) {
99             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
100         } else {
101             updateStatus(ThingStatus.ONLINE);
102             scheduler.execute(() -> {
103                 updateProperties();
104             });
105         }
106     }
107
108     @Override
109     public void dispose() {
110         SleepIQCloudHandler cloudHandler = getCloudHandler();
111         if (cloudHandler != null) {
112             cloudHandler.unregisterBedStatusListener(this);
113         }
114         bedId = null;
115     }
116
117     @Override
118     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
119         super.bridgeStatusChanged(bridgeStatusInfo);
120         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
121             updateProperties();
122         }
123     }
124
125     @Override
126     public void handleCommand(final ChannelUID channelUID, final Command command) {
127         if (command == RefreshType.REFRESH) {
128             // Channels will be refreshed automatically by cloud handler
129             return;
130         }
131
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());
141                     }
142                 }
143                 break;
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);
152                     }
153                 }
154                 break;
155         }
156     }
157
158     @Override
159     public void onSleeperChanged(final @Nullable Sleeper sleeper) {
160         if (sleeper == null || !sleeper.getBedId().equals(bedId)) {
161             return;
162         }
163         logger.debug("BedHandler: Updating sleeper information channels for bed={}, side={}", bedId, sleeper.getSide());
164
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));
169         } else {
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));
173         }
174     }
175
176     @Override
177     public void onBedStateChanged(final @Nullable BedStatus status) {
178         if (status == null || !status.getBedId().equals(bedId)) {
179             return;
180         }
181         logger.debug("BedHandler: Updating bed status channels for bed {}", bedId);
182
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);
192         }
193
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);
203         }
204
205         previousStatus = status;
206     }
207
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");
212             return;
213         }
214         if (previousSideStatus.isInBed() && !currentSideStatus.isInBed()) {
215             logger.debug("BedHandler: Bed status changed from IN BED to OUT OF BED for {}, side {}", bedId,
216                     sleeper.getSide());
217             scheduler.schedule(() -> {
218                 updateDailySleepDataChannels(sleeper);
219                 updateMonthlySleepDataChannels(sleeper);
220             }, GET_SLEEP_DATA_DELAY_MINUTES, TimeUnit.MINUTES);
221         }
222     }
223
224     public void updateDailySleepDataChannels(final @Nullable Sleeper sleeper) {
225         SleepIQCloudHandler cloudHandler = getCloudHandler();
226         if (cloudHandler == null || sleeper == null) {
227             return;
228         }
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());
233             return;
234         }
235
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);
245             } else {
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);
251             }
252             return;
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));
269         }
270     }
271
272     public void updateMonthlySleepDataChannels(final @Nullable Sleeper sleeper) {
273         SleepIQCloudHandler cloudHandler = getCloudHandler();
274         if (cloudHandler == null || sleeper == null) {
275             return;
276         }
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());
281             return;
282         }
283
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()));
291         } else {
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()));
296         }
297     }
298
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);
304             if (bed == null) {
305                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
306                         "No bed found with ID " + bedId);
307                 return;
308             }
309             updateProperties(cloudHandler.updateProperties(bed, editProperties()));
310         }
311     }
312
313     private @Nullable SleepIQCloudHandler getCloudHandler() {
314         Bridge bridge = getBridge();
315         if (bridge != null) {
316             return (SleepIQCloudHandler) bridge.getHandler();
317         }
318         return null;
319     }
320 }