]> git.basschouten.com Git - openhab-addons.git/blob
fbc5231ca1cbadb2dc085433ac7d0b401a36c66d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.salus.internal.handler;
14
15 import static java.math.RoundingMode.HALF_EVEN;
16 import static java.util.Objects.requireNonNull;
17 import static org.openhab.binding.salus.internal.SalusBindingConstants.Channels.It600.EXPECTED_TEMPERATURE;
18 import static org.openhab.binding.salus.internal.SalusBindingConstants.Channels.It600.TEMPERATURE;
19 import static org.openhab.binding.salus.internal.SalusBindingConstants.Channels.It600.WORK_TYPE;
20 import static org.openhab.binding.salus.internal.SalusBindingConstants.It600Device.HoldType.AUTO;
21 import static org.openhab.binding.salus.internal.SalusBindingConstants.It600Device.HoldType.MANUAL;
22 import static org.openhab.binding.salus.internal.SalusBindingConstants.It600Device.HoldType.OFF;
23 import static org.openhab.binding.salus.internal.SalusBindingConstants.It600Device.HoldType.TEMPORARY_MANUAL;
24 import static org.openhab.binding.salus.internal.SalusBindingConstants.SalusDevice.DSN;
25 import static org.openhab.core.library.unit.SIUnits.CELSIUS;
26 import static org.openhab.core.thing.ThingStatus.OFFLINE;
27 import static org.openhab.core.thing.ThingStatus.ONLINE;
28 import static org.openhab.core.thing.ThingStatusDetail.BRIDGE_UNINITIALIZED;
29 import static org.openhab.core.thing.ThingStatusDetail.COMMUNICATION_ERROR;
30 import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
31
32 import java.math.BigDecimal;
33 import java.math.MathContext;
34 import java.util.ArrayList;
35 import java.util.Optional;
36 import java.util.Set;
37 import java.util.SortedSet;
38
39 import org.eclipse.jdt.annotation.NonNullByDefault;
40 import org.openhab.binding.salus.internal.rest.DeviceProperty;
41 import org.openhab.binding.salus.internal.rest.SalusApiException;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.QuantityType;
44 import org.openhab.core.library.types.StringType;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.binding.BaseThingHandler;
48 import org.openhab.core.types.Command;
49 import org.openhab.core.types.RefreshType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * @author Martin GrzeĊ›lowski - Initial contribution
55  */
56 @NonNullByDefault
57 public class It600Handler extends BaseThingHandler {
58     private static final BigDecimal ONE_HUNDRED = new BigDecimal(100);
59     private static final Set<String> REQUIRED_CHANNELS = Set.of("ep_9:sIT600TH:LocalTemperature_x100",
60             "ep_9:sIT600TH:HeatingSetpoint_x100", "ep_9:sIT600TH:SetHeatingSetpoint_x100", "ep_9:sIT600TH:HoldType",
61             "ep_9:sIT600TH:SetHoldType");
62     private final Logger logger;
63     @NonNullByDefault({})
64     private String dsn;
65     @NonNullByDefault({})
66     private CloudApi cloudApi;
67
68     public It600Handler(Thing thing) {
69         super(thing);
70         logger = LoggerFactory.getLogger(It600Handler.class.getName() + "[" + thing.getUID().getId() + "]");
71     }
72
73     @Override
74     public void initialize() {
75         {
76             var bridge = getBridge();
77             if (bridge == null) {
78                 updateStatus(OFFLINE, BRIDGE_UNINITIALIZED, "@text/it600-handler.initialize.errors.no-bridge");
79                 return;
80             }
81             if (!(bridge.getHandler() instanceof CloudBridgeHandler cloudHandler)) {
82                 updateStatus(OFFLINE, BRIDGE_UNINITIALIZED, "@text/it600-handler.initialize.errors.bridge-wrong-type");
83                 return;
84             }
85             this.cloudApi = cloudHandler;
86         }
87
88         dsn = (String) getConfig().get(DSN);
89
90         if ("".equals(dsn)) {
91             updateStatus(OFFLINE, CONFIGURATION_ERROR,
92                     "@text/it600-handler.initialize.errors.no-dsn [\"" + DSN + "\"]");
93             return;
94         }
95
96         try {
97             var device = this.cloudApi.findDevice(dsn);
98             // no device in cloud
99             if (device.isEmpty()) {
100                 updateStatus(OFFLINE, COMMUNICATION_ERROR,
101                         "@text/it600-handler.initialize.errors.dsn-not-found [\"" + dsn + "\"]");
102                 return;
103             }
104             // device is not connected
105             if (!device.get().isConnected()) {
106                 updateStatus(OFFLINE, COMMUNICATION_ERROR,
107                         "@text/it600-handler.initialize.errors.dsn-not-connected [\"" + dsn + "\"]");
108                 return;
109             }
110             // device is missing properties
111             try {
112                 var deviceProperties = findDeviceProperties().stream().map(DeviceProperty::getName).toList();
113                 var result = new ArrayList<>(REQUIRED_CHANNELS);
114                 result.removeAll(deviceProperties);
115                 if (!result.isEmpty()) {
116                     updateStatus(OFFLINE, CONFIGURATION_ERROR,
117                             "@text/it600-handler.initialize.errors.missing-channels [\"" + dsn + "\", \""
118                                     + String.join(", ", result) + "\"]");
119                     return;
120                 }
121             } catch (SalusApiException ex) {
122                 updateStatus(OFFLINE, COMMUNICATION_ERROR, ex.getLocalizedMessage());
123                 return;
124             }
125         } catch (Exception e) {
126             updateStatus(OFFLINE, COMMUNICATION_ERROR, "@text/it600-handler.initialize.errors.general-error");
127             return;
128         }
129
130         // done
131         updateStatus(ONLINE);
132     }
133
134     @Override
135     public void handleCommand(ChannelUID channelUID, Command command) {
136         try {
137             var id = channelUID.getId();
138             switch (id) {
139                 case TEMPERATURE:
140                     handleCommandForTemperature(channelUID, command);
141                     break;
142                 case EXPECTED_TEMPERATURE:
143                     handleCommandForExpectedTemperature(channelUID, command);
144                     break;
145                 case WORK_TYPE:
146                     handleCommandForWorkType(channelUID, command);
147                     break;
148                 default:
149                     logger.warn("Unknown channel `{}` for command `{}`", id, command);
150             }
151         } catch (SalusApiException e) {
152             logger.debug("Error while handling command `{}` on channel `{}`", command, channelUID, e);
153             updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getLocalizedMessage());
154         }
155     }
156
157     private void handleCommandForTemperature(ChannelUID channelUID, Command command) throws SalusApiException {
158         if (!(command instanceof RefreshType)) {
159             // only refresh commands are supported for temp channel
160             return;
161         }
162
163         findLongProperty("ep_9:sIT600TH:LocalTemperature_x100", "LocalTemperature_x100")
164                 .map(DeviceProperty.LongDeviceProperty::getValue).map(BigDecimal::new)
165                 .map(value -> value.divide(ONE_HUNDRED, new MathContext(5, HALF_EVEN))).map(DecimalType::new)
166                 .ifPresent(state -> {
167                     updateState(channelUID, state);
168                     updateStatus(ONLINE);
169                 });
170     }
171
172     private void handleCommandForExpectedTemperature(ChannelUID channelUID, Command command) throws SalusApiException {
173         if (command instanceof RefreshType) {
174             findLongProperty("ep_9:sIT600TH:HeatingSetpoint_x100", "HeatingSetpoint_x100")
175                     .map(DeviceProperty.LongDeviceProperty::getValue).map(BigDecimal::new)
176                     .map(value -> value.divide(ONE_HUNDRED, new MathContext(5, HALF_EVEN))).map(DecimalType::new)
177                     .ifPresent(state -> {
178                         updateState(channelUID, state);
179                         updateStatus(ONLINE);
180                     });
181             return;
182         }
183
184         BigDecimal rawValue = null;
185         if (command instanceof QuantityType<?> commandAsQuantityType) {
186             rawValue = requireNonNull(commandAsQuantityType.toUnit(CELSIUS)).toBigDecimal();
187         } else if (command instanceof DecimalType commandAsDecimalType) {
188             rawValue = commandAsDecimalType.toBigDecimal();
189         }
190
191         if (rawValue != null) {
192             var value = rawValue.multiply(ONE_HUNDRED).longValue();
193             var property = findLongProperty("ep_9:sIT600TH:SetHeatingSetpoint_x100", "SetHeatingSetpoint_x100");
194             if (property.isEmpty()) {
195                 return;
196             }
197             var wasSet = cloudApi.setValueForProperty(dsn, property.get().getName(), value);
198             if (wasSet) {
199                 findLongProperty("ep_9:sIT600TH:HeatingSetpoint_x100", "HeatingSetpoint_x100")
200                         .ifPresent(prop -> prop.setValue(value));
201                 findLongProperty("ep_9:sIT600TH:HoldType", "HoldType").ifPresent(prop -> prop.setValue((long) MANUAL));
202                 updateStatus(ONLINE);
203             }
204             return;
205         }
206
207         logger.debug("Does not know how to handle command `{}` ({}) on channel `{}`!", command,
208                 command.getClass().getSimpleName(), channelUID);
209     }
210
211     private void handleCommandForWorkType(ChannelUID channelUID, Command command) throws SalusApiException {
212         if (command instanceof RefreshType) {
213             findLongProperty("ep_9:sIT600TH:HoldType", "HoldType").map(DeviceProperty.LongDeviceProperty::getValue)
214                     .map(value -> switch (value.intValue()) {
215                         case AUTO -> "AUTO";
216                         case MANUAL -> "MANUAL";
217                         case TEMPORARY_MANUAL -> "TEMPORARY_MANUAL";
218                         case OFF -> "OFF";
219                         default -> {
220                             logger.warn("Unknown value {} for property HoldType!", value);
221                             yield "AUTO";
222                         }
223                     }).map(StringType::new).ifPresent(state -> {
224                         updateState(channelUID, state);
225                         updateStatus(ONLINE);
226                     });
227             return;
228         }
229
230         if (command instanceof StringType typedCommand) {
231             long value;
232             if ("AUTO".equals(typedCommand.toString())) {
233                 value = AUTO;
234             } else if ("MANUAL".equals(typedCommand.toString())) {
235                 value = MANUAL;
236             } else if ("TEMPORARY_MANUAL".equals(typedCommand.toString())) {
237                 value = TEMPORARY_MANUAL;
238             } else if ("OFF".equals(typedCommand.toString())) {
239                 value = OFF;
240             } else {
241                 logger.warn("Unknown value `{}` for property HoldType!", typedCommand);
242                 return;
243             }
244             var property = findLongProperty("ep_9:sIT600TH:SetHoldType", "SetHoldType");
245             if (property.isEmpty()) {
246                 return;
247             }
248             cloudApi.setValueForProperty(dsn, property.get().getName(), value);
249             updateStatus(ONLINE);
250             return;
251         }
252
253         logger.debug("Does not know how to handle command `{}` ({}) on channel `{}`!", command,
254                 command.getClass().getSimpleName(), channelUID);
255     }
256
257     private Optional<DeviceProperty.LongDeviceProperty> findLongProperty(String name, String shortName)
258             throws SalusApiException {
259         var deviceProperties = findDeviceProperties();
260         var property = deviceProperties.stream().filter(p -> p.getName().equals(name))
261                 .filter(DeviceProperty.LongDeviceProperty.class::isInstance)
262                 .map(DeviceProperty.LongDeviceProperty.class::cast).findAny();
263         if (property.isEmpty()) {
264             property = deviceProperties.stream().filter(p -> p.getName().contains(shortName))
265                     .filter(DeviceProperty.LongDeviceProperty.class::isInstance)
266                     .map(DeviceProperty.LongDeviceProperty.class::cast).findAny();
267         }
268         if (property.isEmpty()) {
269             logger.debug("{}/{} property not found!", name, shortName);
270         }
271         return property;
272     }
273
274     private SortedSet<DeviceProperty<?>> findDeviceProperties() throws SalusApiException {
275         return this.cloudApi.findPropertiesForDevice(dsn);
276     }
277 }