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.surepetcare.internal.handler;
15 import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
17 import java.time.LocalTime;
18 import java.time.format.DateTimeFormatter;
19 import java.time.format.DateTimeParseException;
20 import java.util.List;
22 import javax.measure.quantity.Mass;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.openhab.binding.surepetcare.internal.SurePetcareAPIHelper;
26 import org.openhab.binding.surepetcare.internal.SurePetcareApiException;
27 import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice;
28 import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceControl;
29 import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceControl.Bowls.BowlSettings;
30 import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfew;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.library.types.QuantityType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.library.unit.SIUnits;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * The {@link SurePetcareDeviceHandler} is responsible for handling hubs and pet flaps created to represent Sure Petcare
47 * @author Rene Scherer - Initial Contribution
48 * @author Holger Eisold - Added pet feeder status
51 public class SurePetcareDeviceHandler extends SurePetcareBaseObjectHandler {
53 private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
55 private static final float BATTERY_FULL_VOLTAGE = 4 * 1.5f; // 4x AA batteries of 1.5V each
56 private static final float BATTERY_EMPTY_VOLTAGE = 4.2f; // 4x AA batteries of 1.05V each
57 private static final float LOW_BATTERY_THRESHOLD = 4 * 1.1f;
59 private final Logger logger = LoggerFactory.getLogger(SurePetcareDeviceHandler.class);
61 public SurePetcareDeviceHandler(Thing thing, SurePetcareAPIHelper petcareAPI) {
62 super(thing, petcareAPI);
63 logger.debug("Created device handler for type {}", thing.getThingTypeUID().getAsString());
67 public void handleCommand(ChannelUID channelUID, Command command) {
68 if (command instanceof RefreshType) {
69 updateThingCache.getValue();
70 } else if (channelUID.getId().startsWith(DEVICE_CHANNEL_CURFEW_BASE)) {
71 handleCurfewCommand(channelUID, command);
73 switch (channelUID.getId()) {
74 case DEVICE_CHANNEL_LOCKING_MODE:
75 if (command instanceof StringType) {
76 synchronized (petcareAPI) {
77 SurePetcareDevice device = petcareAPI.getDevice(thing.getUID().getId());
79 String newLockingModeIdStr = ((StringType) command).toString();
81 Integer newLockingModeId = Integer.valueOf(newLockingModeIdStr);
82 petcareAPI.setDeviceLockingMode(device, newLockingModeId);
83 updateState(DEVICE_CHANNEL_LOCKING_MODE,
84 new StringType(device.status.locking.modeId.toString()));
85 } catch (NumberFormatException e) {
86 logger.warn("Invalid locking mode: {}, ignoring command", newLockingModeIdStr);
87 } catch (SurePetcareApiException e) {
89 "Error from SurePetcare API. Can't update locking mode {} for device {}",
90 newLockingModeIdStr, device, e);
96 case DEVICE_CHANNEL_LED_MODE:
97 if (command instanceof StringType) {
98 synchronized (petcareAPI) {
99 SurePetcareDevice device = petcareAPI.getDevice(thing.getUID().getId());
100 if (device != null) {
101 String newLedModeIdStr = command.toString();
103 Integer newLedModeId = Integer.valueOf(newLedModeIdStr);
104 petcareAPI.setDeviceLedMode(device, newLedModeId);
105 updateState(DEVICE_CHANNEL_LOCKING_MODE,
106 new StringType(device.status.ledModeId.toString()));
107 } catch (NumberFormatException e) {
108 logger.warn("Invalid locking mode: {}, ignoring command", newLedModeIdStr);
109 } catch (SurePetcareApiException e) {
110 logger.warn("Error from SurePetcare API. Can't update LED mode {} for device {}",
111 newLedModeIdStr, device, e);
118 logger.warn("Update on unsupported channel {}, ignoring command", channelUID.getId());
124 protected void updateThing() {
125 SurePetcareDevice device = petcareAPI.getDevice(thing.getUID().getId());
126 if (device != null) {
127 logger.debug("Updating all thing channels for device : {}", device);
128 updateState(DEVICE_CHANNEL_ID, new DecimalType(device.id));
129 updateState(DEVICE_CHANNEL_NAME, new StringType(device.name));
130 updateState(DEVICE_CHANNEL_PRODUCT, new StringType(device.productId.toString()));
131 updateState(DEVICE_CHANNEL_ONLINE, OnOffType.from(device.status.online));
133 if (thing.getThingTypeUID().equals(THING_TYPE_HUB_DEVICE)) {
134 updateState(DEVICE_CHANNEL_LED_MODE, new StringType(device.status.ledModeId.toString()));
135 updateState(DEVICE_CHANNEL_PAIRING_MODE, new StringType(device.status.pairingModeId.toString()));
137 float batVol = device.status.battery;
138 updateState(DEVICE_CHANNEL_BATTERY_VOLTAGE, new DecimalType(batVol));
139 updateState(DEVICE_CHANNEL_BATTERY_LEVEL, new DecimalType(Math.min(
140 (batVol - BATTERY_EMPTY_VOLTAGE) / (BATTERY_FULL_VOLTAGE - BATTERY_EMPTY_VOLTAGE) * 100.0f,
142 updateState(DEVICE_CHANNEL_LOW_BATTERY, OnOffType.from(batVol < LOW_BATTERY_THRESHOLD));
143 updateState(DEVICE_CHANNEL_DEVICE_RSSI, new DecimalType(device.status.signal.deviceRssi));
144 updateState(DEVICE_CHANNEL_HUB_RSSI, new DecimalType(device.status.signal.hubRssi));
146 if (thing.getThingTypeUID().equals(THING_TYPE_FLAP_DEVICE)) {
147 updateThingCurfews(device);
148 updateState(DEVICE_CHANNEL_LOCKING_MODE, new StringType(device.status.locking.modeId.toString()));
149 } else if (thing.getThingTypeUID().equals(THING_TYPE_FEEDER_DEVICE)) {
150 int bowlId = device.control.bowls.bowlId;
151 List<BowlSettings> bowlSettings = device.control.bowls.bowlSettings;
152 int numBowls = bowlSettings.size();
154 if (bowlId == BOWL_ID_ONE_BOWL_USED) {
155 updateState(DEVICE_CHANNEL_BOWLS_FOOD,
156 new StringType(bowlSettings.get(0).foodId.toString()));
157 updateState(DEVICE_CHANNEL_BOWLS_TARGET, new QuantityType<Mass>(
158 device.control.bowls.bowlSettings.get(0).targetId, SIUnits.GRAM));
159 } else if (bowlId == BOWL_ID_TWO_BOWLS_USED) {
160 updateState(DEVICE_CHANNEL_BOWLS_FOOD_LEFT,
161 new StringType(bowlSettings.get(0).foodId.toString()));
162 updateState(DEVICE_CHANNEL_BOWLS_TARGET_LEFT,
163 new QuantityType<Mass>(bowlSettings.get(0).targetId, SIUnits.GRAM));
165 updateState(DEVICE_CHANNEL_BOWLS_FOOD_RIGHT,
166 new StringType(bowlSettings.get(1).foodId.toString()));
167 updateState(DEVICE_CHANNEL_BOWLS_TARGET_RIGHT,
168 new QuantityType<Mass>(bowlSettings.get(1).targetId, SIUnits.GRAM));
172 updateState(DEVICE_CHANNEL_BOWLS, new StringType(device.control.bowls.bowlId.toString()));
173 updateState(DEVICE_CHANNEL_BOWLS_CLOSE_DELAY,
174 new StringType(device.control.lid.closeDelayId.toString()));
175 updateState(DEVICE_CHANNEL_BOWLS_TRAINING_MODE,
176 new StringType(device.control.trainingModeId.toString()));
178 logger.warn("Unknown product type for device {}", thing.getUID().getAsString());
184 private void updateThingCurfews(SurePetcareDevice device) {
185 for (int i = 0; i < FLAP_MAX_NUMBER_OF_CURFEWS; i++) {
186 SurePetcareDeviceCurfew curfew = device.control.curfewList.get(i);
187 logger.debug("updateThingCurfews - Updating device curfew: {}", curfew.toString());
188 updateState(DEVICE_CHANNEL_CURFEW_ENABLED + (i + 1), OnOffType.from(curfew.enabled));
189 updateState(DEVICE_CHANNEL_CURFEW_LOCK_TIME + (i + 1),
190 new StringType(TIME_FORMATTER.format(curfew.lockTime)));
191 updateState(DEVICE_CHANNEL_CURFEW_UNLOCK_TIME + (i + 1),
192 new StringType(TIME_FORMATTER.format(curfew.unlockTime)));
196 private void handleCurfewCommand(ChannelUID channelUID, Command command) {
197 String channelUIDBase = channelUID.getIdWithoutGroup().substring(0,
198 channelUID.getIdWithoutGroup().length() - 1);
199 int slot = Integer.parseInt(channelUID.getAsString().substring(channelUID.getAsString().length() - 1));
201 synchronized (petcareAPI) {
202 SurePetcareDevice device = petcareAPI.getDevice(thing.getUID().getId());
203 if (device != null) {
205 SurePetcareDeviceControl existingControl = device.control;
206 SurePetcareDeviceCurfew curfew = existingControl.curfewList.get(slot - 1);
207 boolean requiresUpdate = false;
208 switch (channelUIDBase) {
209 case DEVICE_CHANNEL_CURFEW_ENABLED:
210 if (command instanceof OnOffType) {
211 if (curfew.enabled != command.equals(OnOffType.ON)) {
212 logger.debug("Enabling curfew slot: {}", slot);
213 requiresUpdate = true;
215 curfew.enabled = (command.equals(OnOffType.ON));
218 case DEVICE_CHANNEL_CURFEW_LOCK_TIME:
219 LocalTime newLockTime = LocalTime.parse(command.toString(), TIME_FORMATTER);
220 if (!(curfew.lockTime.equals(newLockTime)) && curfew.enabled) {
221 logger.debug("Changing curfew slot {} lock time to: {}", slot, newLockTime);
222 requiresUpdate = true;
224 curfew.lockTime = newLockTime;
227 case DEVICE_CHANNEL_CURFEW_UNLOCK_TIME:
228 LocalTime newUnlockTime = LocalTime.parse(command.toString(), TIME_FORMATTER);
229 if (!(curfew.unlockTime.equals(newUnlockTime)) && curfew.enabled) {
230 logger.debug("Changing curfew slot {} unlock time to: {}", slot, newUnlockTime);
231 requiresUpdate = true;
233 curfew.unlockTime = newUnlockTime;
240 if (requiresUpdate) {
242 logger.debug("Updating curfews: {}", existingControl.curfewList.toString());
243 petcareAPI.setCurfews(device, existingControl.curfewList);
244 updateThingCurfews(device);
245 } catch (SurePetcareApiException e) {
246 logger.warn("Error from SurePetcare API. Can't update curfews for device {}: {}", device,
250 } catch (DateTimeParseException e) {
251 logger.warn("Incorrect curfew time format HH:mm: {}", e.getMessage());