]> git.basschouten.com Git - openhab-addons.git/blob
9a2df943742097bc308cc05c62b927c3021b4614
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.plugwiseha.internal.handler;
14
15 import static org.openhab.binding.plugwiseha.internal.PlugwiseHABindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.*;
17 import static org.openhab.core.thing.ThingStatus.*;
18 import static org.openhab.core.thing.ThingStatusDetail.BRIDGE_OFFLINE;
19 import static org.openhab.core.thing.ThingStatusDetail.COMMUNICATION_ERROR;
20 import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
21
22 import java.util.List;
23 import java.util.Map;
24
25 import javax.measure.Unit;
26 import javax.measure.quantity.Dimensionless;
27 import javax.measure.quantity.Power;
28 import javax.measure.quantity.Pressure;
29 import javax.measure.quantity.Temperature;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.binding.plugwiseha.internal.PlugwiseHABindingConstants;
34 import org.openhab.binding.plugwiseha.internal.api.exception.PlugwiseHAException;
35 import org.openhab.binding.plugwiseha.internal.api.model.PlugwiseHAController;
36 import org.openhab.binding.plugwiseha.internal.api.model.dto.Appliance;
37 import org.openhab.binding.plugwiseha.internal.config.PlugwiseHAThingConfig;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.unit.ImperialUnits;
41 import org.openhab.core.library.unit.SIUnits;
42 import org.openhab.core.library.unit.Units;
43 import org.openhab.core.thing.Channel;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingTypeUID;
47 import org.openhab.core.thing.binding.builder.ChannelBuilder;
48 import org.openhab.core.thing.binding.builder.ThingBuilder;
49 import org.openhab.core.thing.type.ChannelKind;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.State;
52 import org.openhab.core.types.UnDefType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * The {@link PlugwiseHAApplianceHandler} class is responsible for handling
58  * commands and status updates for the Plugwise Home Automation appliances.
59  * Extends @{link PlugwiseHABaseHandler}
60  *
61  * @author Bas van Wetten - Initial contribution
62  * @author Leo Siepel - finish initial contribution
63  *
64  */
65 @NonNullByDefault
66 public class PlugwiseHAApplianceHandler extends PlugwiseHABaseHandler<Appliance, PlugwiseHAThingConfig> {
67
68     private @Nullable Appliance appliance;
69     private final Logger logger = LoggerFactory.getLogger(PlugwiseHAApplianceHandler.class);
70
71     // Constructor
72
73     public PlugwiseHAApplianceHandler(Thing thing) {
74         super(thing);
75     }
76
77     public static boolean supportsThingType(ThingTypeUID thingTypeUID) {
78         return PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_VALVE.equals(thingTypeUID)
79                 || PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_PUMP.equals(thingTypeUID)
80                 || PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_BOILER.equals(thingTypeUID)
81                 || PlugwiseHABindingConstants.THING_TYPE_APPLIANCE_THERMOSTAT.equals(thingTypeUID);
82     }
83
84     // Overrides
85
86     @Override
87     protected synchronized void initialize(PlugwiseHAThingConfig config, PlugwiseHABridgeHandler bridgeHandler) {
88         if (thing.getStatus() == INITIALIZING) {
89             logger.debug("Initializing Plugwise Home Automation appliance handler with config = {}", config);
90             if (!config.isValid()) {
91                 updateStatus(OFFLINE, CONFIGURATION_ERROR,
92                         "Invalid configuration for Plugwise Home Automation appliance handler.");
93                 return;
94             }
95
96             try {
97                 PlugwiseHAController controller = bridgeHandler.getController();
98                 if (controller != null) {
99                     this.appliance = getEntity(controller);
100                     Appliance localAppliance = this.appliance;
101                     if (localAppliance != null) {
102                         if (localAppliance.isBatteryOperated()) {
103                             addBatteryChannels();
104                         }
105                         setApplianceProperties();
106                         updateStatus(ONLINE);
107                     } else {
108                         updateStatus(OFFLINE);
109                     }
110                 } else {
111                     updateStatus(OFFLINE, BRIDGE_OFFLINE);
112                 }
113             } catch (PlugwiseHAException e) {
114                 updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage());
115             }
116         }
117     }
118
119     @Override
120     protected @Nullable Appliance getEntity(PlugwiseHAController controller) throws PlugwiseHAException {
121         PlugwiseHAThingConfig config = getPlugwiseThingConfig();
122         Appliance appliance = controller.getAppliance(config.getId());
123
124         return appliance;
125     }
126
127     @Override
128     protected void handleCommand(Appliance entity, ChannelUID channelUID, Command command) throws PlugwiseHAException {
129         String channelID = channelUID.getIdWithoutGroup();
130
131         PlugwiseHABridgeHandler bridge = this.getPlugwiseHABridge();
132         if (bridge == null) {
133             return;
134         }
135
136         PlugwiseHAController controller = bridge.getController();
137         if (controller == null) {
138             return;
139         }
140
141         switch (channelID) {
142             case APPLIANCE_LOCK_CHANNEL:
143                 if (command instanceof OnOffType) {
144                     try {
145                         controller.setRelay(entity, (command == OnOffType.ON));
146                     } catch (PlugwiseHAException e) {
147                         logger.warn("Unable to switch relay lock {} for appliance '{}'", (State) command,
148                                 entity.getName());
149                     }
150                 }
151                 break;
152             case APPLIANCE_OFFSET_CHANNEL:
153                 if (command instanceof QuantityType) {
154                     Unit<Temperature> unit = entity.getOffsetTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
155                             ? SIUnits.CELSIUS
156                             : ImperialUnits.FAHRENHEIT;
157                     QuantityType<?> state = ((QuantityType<?>) command).toUnit(unit);
158
159                     if (state != null) {
160                         try {
161                             controller.setOffsetTemperature(entity, state.doubleValue());
162                         } catch (PlugwiseHAException e) {
163                             logger.warn("Unable to update setpoint for zone '{}': {} -> {}", entity.getName(),
164                                     entity.getSetpointTemperature().orElse(null), state.doubleValue());
165                         }
166                     }
167                 }
168                 break;
169             case APPLIANCE_POWER_CHANNEL:
170                 if (command instanceof OnOffType) {
171                     try {
172                         controller.setRelay(entity, command == OnOffType.ON);
173                     } catch (PlugwiseHAException e) {
174                         logger.warn("Unable to switch relay {} for appliance '{}'", (State) command, entity.getName());
175                     }
176                 }
177                 break;
178             case APPLIANCE_SETPOINT_CHANNEL:
179                 if (command instanceof QuantityType) {
180                     Unit<Temperature> unit = entity.getSetpointTemperatureUnit().orElse(UNIT_CELSIUS)
181                             .equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
182                     QuantityType<?> state = ((QuantityType<?>) command).toUnit(unit);
183
184                     if (state != null) {
185                         try {
186                             controller.setThermostat(entity, state.doubleValue());
187                         } catch (PlugwiseHAException e) {
188                             logger.warn("Unable to update setpoint for appliance '{}': {} -> {}", entity.getName(),
189                                     entity.getSetpointTemperature().orElse(null), state.doubleValue());
190                         }
191                     }
192                 }
193                 break;
194             default:
195                 logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID);
196         }
197     }
198
199     private State getDefaultState(String channelID) {
200         State state = UnDefType.NULL;
201         switch (channelID) {
202             case APPLIANCE_BATTERYLEVEL_CHANNEL:
203             case APPLIANCE_CHSTATE_CHANNEL:
204             case APPLIANCE_DHWSTATE_CHANNEL:
205             case APPLIANCE_COOLINGSTATE_CHANNEL:
206             case APPLIANCE_INTENDEDBOILERTEMP_CHANNEL:
207             case APPLIANCE_FLAMESTATE_CHANNEL:
208             case APPLIANCE_INTENDEDHEATINGSTATE_CHANNEL:
209             case APPLIANCE_MODULATIONLEVEL_CHANNEL:
210             case APPLIANCE_OTAPPLICATIONFAULTCODE_CHANNEL:
211             case APPLIANCE_DHWTEMPERATURE_CHANNEL:
212             case APPLIANCE_OTOEMFAULTCODE_CHANNEL:
213             case APPLIANCE_BOILERTEMPERATURE_CHANNEL:
214             case APPLIANCE_DHWSETPOINT_CHANNEL:
215             case APPLIANCE_MAXBOILERTEMPERATURE_CHANNEL:
216             case APPLIANCE_DHWCOMFORTMODE_CHANNEL:
217             case APPLIANCE_OFFSET_CHANNEL:
218             case APPLIANCE_POWER_USAGE_CHANNEL:
219             case APPLIANCE_SETPOINT_CHANNEL:
220             case APPLIANCE_TEMPERATURE_CHANNEL:
221             case APPLIANCE_VALVEPOSITION_CHANNEL:
222             case APPLIANCE_WATERPRESSURE_CHANNEL:
223                 state = UnDefType.NULL;
224                 break;
225             case APPLIANCE_BATTERYLEVELLOW_CHANNEL:
226             case APPLIANCE_LOCK_CHANNEL:
227             case APPLIANCE_POWER_CHANNEL:
228                 state = UnDefType.UNDEF;
229                 break;
230         }
231         return state;
232     }
233
234     @Override
235     protected void refreshChannel(Appliance entity, ChannelUID channelUID) {
236         String channelID = channelUID.getIdWithoutGroup();
237         State state = getDefaultState(channelID);
238         PlugwiseHAThingConfig config = getPlugwiseThingConfig();
239
240         switch (channelID) {
241             case APPLIANCE_BATTERYLEVEL_CHANNEL: {
242                 Double batteryLevel = entity.getBatteryLevel().orElse(null);
243
244                 if (batteryLevel != null) {
245                     batteryLevel = batteryLevel * 100;
246                     state = new QuantityType<Dimensionless>(batteryLevel.intValue(), Units.PERCENT);
247                     if (batteryLevel <= config.getLowBatteryPercentage()) {
248                         updateState(APPLIANCE_BATTERYLEVELLOW_CHANNEL, OnOffType.ON);
249                     } else {
250                         updateState(APPLIANCE_BATTERYLEVELLOW_CHANNEL, OnOffType.OFF);
251                     }
252                 }
253                 break;
254             }
255             case APPLIANCE_BATTERYLEVELLOW_CHANNEL: {
256                 Double batteryLevel = entity.getBatteryLevel().orElse(null);
257
258                 if (batteryLevel != null) {
259                     batteryLevel *= 100;
260                     if (batteryLevel <= config.getLowBatteryPercentage()) {
261                         state = OnOffType.ON;
262                     } else {
263                         state = OnOffType.OFF;
264                     }
265                 }
266                 break;
267             }
268             case APPLIANCE_CHSTATE_CHANNEL:
269                 if (entity.getCHState().isPresent()) {
270                     state = OnOffType.from(entity.getCHState().get());
271                 }
272                 break;
273             case APPLIANCE_DHWSTATE_CHANNEL:
274                 if (entity.getDHWState().isPresent()) {
275                     state = OnOffType.from(entity.getDHWState().get());
276                 }
277                 break;
278             case APPLIANCE_LOCK_CHANNEL:
279                 Boolean relayLockState = entity.getRelayLockState().orElse(null);
280                 if (relayLockState != null) {
281                     state = OnOffType.from(relayLockState);
282                 }
283                 break;
284             case APPLIANCE_OFFSET_CHANNEL:
285                 if (entity.getOffsetTemperature().isPresent()) {
286                     Unit<Temperature> unit = entity.getOffsetTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
287                             ? SIUnits.CELSIUS
288                             : ImperialUnits.FAHRENHEIT;
289                     state = new QuantityType<Temperature>(entity.getOffsetTemperature().get(), unit);
290                 }
291                 break;
292             case APPLIANCE_POWER_CHANNEL:
293                 if (entity.getRelayState().isPresent()) {
294                     state = OnOffType.from(entity.getRelayState().get());
295                 }
296                 break;
297             case APPLIANCE_POWER_USAGE_CHANNEL:
298                 if (entity.getPowerUsage().isPresent()) {
299                     state = new QuantityType<Power>(entity.getPowerUsage().get(), Units.WATT);
300                 }
301                 break;
302             case APPLIANCE_SETPOINT_CHANNEL:
303                 if (entity.getSetpointTemperature().isPresent()) {
304                     Unit<Temperature> unit = entity.getSetpointTemperatureUnit().orElse(UNIT_CELSIUS)
305                             .equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
306                     state = new QuantityType<Temperature>(entity.getSetpointTemperature().get(), unit);
307                 }
308                 break;
309             case APPLIANCE_TEMPERATURE_CHANNEL:
310                 if (entity.getTemperature().isPresent()) {
311                     Unit<Temperature> unit = entity.getTemperatureUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
312                             ? SIUnits.CELSIUS
313                             : ImperialUnits.FAHRENHEIT;
314                     state = new QuantityType<Temperature>(entity.getTemperature().get(), unit);
315                 }
316                 break;
317             case APPLIANCE_VALVEPOSITION_CHANNEL:
318                 if (entity.getValvePosition().isPresent()) {
319                     Double valvePosition = entity.getValvePosition().get() * 100;
320                     state = new QuantityType<Dimensionless>(valvePosition.intValue(), Units.PERCENT);
321                 }
322                 break;
323             case APPLIANCE_WATERPRESSURE_CHANNEL:
324                 if (entity.getWaterPressure().isPresent()) {
325                     Unit<Pressure> unit = HECTO(SIUnits.PASCAL);
326                     state = new QuantityType<Pressure>(entity.getWaterPressure().get(), unit);
327                 }
328                 break;
329             case APPLIANCE_COOLINGSTATE_CHANNEL:
330                 if (entity.getCoolingState().isPresent()) {
331                     state = OnOffType.from(entity.getCoolingState().get());
332                 }
333                 break;
334             case APPLIANCE_INTENDEDBOILERTEMP_CHANNEL:
335                 if (entity.getIntendedBoilerTemp().isPresent()) {
336                     Unit<Temperature> unit = entity.getIntendedBoilerTempUnit().orElse(UNIT_CELSIUS)
337                             .equals(UNIT_CELSIUS) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT;
338                     state = new QuantityType<Temperature>(entity.getIntendedBoilerTemp().get(), unit);
339                 }
340                 break;
341             case APPLIANCE_FLAMESTATE_CHANNEL:
342                 if (entity.getFlameState().isPresent()) {
343                     state = OnOffType.from(entity.getFlameState().get());
344                 }
345                 break;
346             case APPLIANCE_INTENDEDHEATINGSTATE_CHANNEL:
347                 if (entity.getIntendedHeatingState().isPresent()) {
348                     state = OnOffType.from(entity.getIntendedHeatingState().get());
349                 }
350                 break;
351             case APPLIANCE_MODULATIONLEVEL_CHANNEL:
352                 if (entity.getModulationLevel().isPresent()) {
353                     Double modulationLevel = entity.getModulationLevel().get() * 100;
354                     state = new QuantityType<Dimensionless>(modulationLevel.intValue(), Units.PERCENT);
355                 }
356                 break;
357             case APPLIANCE_OTAPPLICATIONFAULTCODE_CHANNEL:
358                 if (entity.getOTAppFaultCode().isPresent()) {
359                     state = new QuantityType<Dimensionless>(entity.getOTAppFaultCode().get().intValue(), Units.PERCENT);
360                 }
361                 break;
362             case APPLIANCE_DHWTEMPERATURE_CHANNEL:
363                 if (entity.getDHWTemp().isPresent()) {
364                     Unit<Temperature> unit = entity.getDHWTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
365                             ? SIUnits.CELSIUS
366                             : ImperialUnits.FAHRENHEIT;
367                     state = new QuantityType<Temperature>(entity.getDHWTemp().get(), unit);
368                 }
369                 break;
370             case APPLIANCE_OTOEMFAULTCODE_CHANNEL:
371                 if (entity.getOTOEMFaultcode().isPresent()) {
372                     state = new QuantityType<Dimensionless>(entity.getOTOEMFaultcode().get().intValue(), Units.PERCENT);
373                 }
374                 break;
375             case APPLIANCE_BOILERTEMPERATURE_CHANNEL:
376                 if (entity.getBoilerTemp().isPresent()) {
377                     Unit<Temperature> unit = entity.getBoilerTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
378                             ? SIUnits.CELSIUS
379                             : ImperialUnits.FAHRENHEIT;
380                     state = new QuantityType<Temperature>(entity.getBoilerTemp().get(), unit);
381                 }
382                 break;
383             case APPLIANCE_DHWSETPOINT_CHANNEL:
384                 if (entity.getDHTSetpoint().isPresent()) {
385                     Unit<Temperature> unit = entity.getDHTSetpointUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
386                             ? SIUnits.CELSIUS
387                             : ImperialUnits.FAHRENHEIT;
388                     state = new QuantityType<Temperature>(entity.getDHTSetpoint().get(), unit);
389                 }
390                 break;
391             case APPLIANCE_MAXBOILERTEMPERATURE_CHANNEL:
392                 if (entity.getMaxBoilerTemp().isPresent()) {
393                     Unit<Temperature> unit = entity.getMaxBoilerTempUnit().orElse(UNIT_CELSIUS).equals(UNIT_CELSIUS)
394                             ? SIUnits.CELSIUS
395                             : ImperialUnits.FAHRENHEIT;
396                     state = new QuantityType<Temperature>(entity.getMaxBoilerTemp().get(), unit);
397                 }
398                 break;
399             case APPLIANCE_DHWCOMFORTMODE_CHANNEL:
400                 if (entity.getDHWComfortMode().isPresent()) {
401                     state = OnOffType.from(entity.getDHWComfortMode().get());
402                 }
403                 break;
404             default:
405                 break;
406         }
407
408         if (state != UnDefType.NULL) {
409             updateState(channelID, state);
410         }
411     }
412
413     protected synchronized void addBatteryChannels() {
414         logger.debug("Battery operated appliance: {} detected: adding 'Battery level' and 'Battery low level' channels",
415                 thing.getLabel());
416
417         ChannelUID channelUIDBatteryLevel = new ChannelUID(getThing().getUID(), APPLIANCE_BATTERYLEVEL_CHANNEL);
418         ChannelUID channelUIDBatteryLevelLow = new ChannelUID(getThing().getUID(), APPLIANCE_BATTERYLEVELLOW_CHANNEL);
419
420         boolean channelBatteryLevelExists = false;
421         boolean channelBatteryLowExists = false;
422
423         List<Channel> channels = getThing().getChannels();
424         for (Channel channel : channels) {
425             if (channel.getUID().equals(channelUIDBatteryLevel)) {
426                 channelBatteryLevelExists = true;
427             } else if (channel.getUID().equals(channelUIDBatteryLevelLow)) {
428                 channelBatteryLowExists = true;
429             }
430             if (channelBatteryLevelExists && channelBatteryLowExists) {
431                 break;
432             }
433         }
434
435         if (!channelBatteryLevelExists) {
436             ThingBuilder thingBuilder = editThing();
437
438             Channel channelBatteryLevel = ChannelBuilder.create(channelUIDBatteryLevel, "Number")
439                     .withType(CHANNEL_TYPE_BATTERYLEVEL).withKind(ChannelKind.STATE).withLabel("Battery Level")
440                     .withDescription("Represents the battery level as a percentage (0-100%)").build();
441
442             thingBuilder.withChannel(channelBatteryLevel);
443
444             updateThing(thingBuilder.build());
445         }
446
447         if (!channelBatteryLowExists) {
448             ThingBuilder thingBuilder = editThing();
449
450             Channel channelBatteryLow = ChannelBuilder.create(channelUIDBatteryLevelLow, "Switch")
451                     .withType(CHANNEL_TYPE_BATTERYLEVELLOW).withKind(ChannelKind.STATE).withLabel("Battery Low Level")
452                     .withDescription("Switches ON when battery level gets below threshold level").build();
453
454             thingBuilder.withChannel(channelBatteryLow);
455
456             updateThing(thingBuilder.build());
457         }
458     }
459
460     protected void setApplianceProperties() {
461         Map<String, String> properties = editProperties();
462         logger.debug("Setting thing properties to {}", thing.getLabel());
463         Appliance localAppliance = this.appliance;
464         if (localAppliance != null) {
465             properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_DESCRIPTION, localAppliance.getDescription());
466             properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_TYPE, localAppliance.getType());
467             properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_FUNCTIONALITIES,
468                     String.join(", ", localAppliance.getActuatorFunctionalities().keySet()));
469
470             if (localAppliance.isZigbeeDevice()) {
471                 properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_ZB_TYPE,
472                         localAppliance.getZigbeeNode().getType());
473                 properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_ZB_REACHABLE,
474                         localAppliance.getZigbeeNode().getReachable());
475                 properties.put(PlugwiseHABindingConstants.APPLIANCE_PROPERTY_ZB_POWERSOURCE,
476                         localAppliance.getZigbeeNode().getPowerSource());
477                 properties.put(Thing.PROPERTY_MAC_ADDRESS, localAppliance.getZigbeeNode().getMacAddress());
478             }
479
480             properties.put(Thing.PROPERTY_FIRMWARE_VERSION, localAppliance.getModule().getFirmwareVersion());
481             properties.put(Thing.PROPERTY_HARDWARE_VERSION, localAppliance.getModule().getHardwareVersion());
482             properties.put(Thing.PROPERTY_VENDOR, localAppliance.getModule().getVendorName());
483             properties.put(Thing.PROPERTY_MODEL_ID, localAppliance.getModule().getVendorModel());
484         }
485         updateProperties(properties);
486     }
487 }