]> git.basschouten.com Git - openhab-addons.git/blob
81baa1da726e53abd74ba38f15a0d12bc8b61c97
[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.avmfritz.internal.handler;
14
15 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.BINDING_ID;
16 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ACTUALTEMP;
17 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_BATTERY;
18 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_BATTERY_LOW;
19 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_BRIGHTNESS;
20 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_COLOR;
21 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_COLORTEMPERATURE;
22 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_COLORTEMPERATURE_ABS;
23 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_COMFORTTEMP;
24 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_CONTACT_STATE;
25 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_DEVICE_LOCKED;
26 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ECOTEMP;
27 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ENERGY;
28 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_HUMIDITY;
29 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_LAST_CHANGE;
30 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_LOCKED;
31 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_MODE;
32 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_NEXTTEMP;
33 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_NEXT_CHANGE;
34 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_OBSTRUCTION_ALARM;
35 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ON_OFF;
36 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_OUTLET;
37 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_POWER;
38 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_RADIATOR_MODE;
39 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ROLLERSHUTTER;
40 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_SETTEMP;
41 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_TEMPERATURE;
42 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_TEMPERATURE_ALARM;
43 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_VOLTAGE;
44 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CONFIG_CHANNEL_TEMP_OFFSET;
45 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_BOOST;
46 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_COMFORT;
47 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_ECO;
48 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_OFF;
49 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_ON;
50 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_UNKNOWN;
51 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_WINDOW_OPEN;
52 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.TEMP_FRITZ_MAX;
53 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.TEMP_FRITZ_OFF;
54 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.TEMP_FRITZ_ON;
55 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.TEMP_FRITZ_UNDEFINED;
56 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.fromCelsius;
57 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.normalizeCelsius;
58 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.toCelsius;
59
60 import java.math.BigDecimal;
61 import java.time.Instant;
62 import java.time.ZoneId;
63 import java.time.ZonedDateTime;
64 import java.util.Map;
65
66 import javax.measure.quantity.Temperature;
67
68 import org.eclipse.jdt.annotation.NonNullByDefault;
69 import org.eclipse.jdt.annotation.Nullable;
70 import org.openhab.binding.avmfritz.internal.config.AVMFritzDeviceConfiguration;
71 import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
72 import org.openhab.binding.avmfritz.internal.dto.AlertModel;
73 import org.openhab.binding.avmfritz.internal.dto.BatteryModel;
74 import org.openhab.binding.avmfritz.internal.dto.ColorControlModel;
75 import org.openhab.binding.avmfritz.internal.dto.DeviceModel;
76 import org.openhab.binding.avmfritz.internal.dto.HeatingModel;
77 import org.openhab.binding.avmfritz.internal.dto.HeatingModel.NextChangeModel;
78 import org.openhab.binding.avmfritz.internal.dto.HumidityModel;
79 import org.openhab.binding.avmfritz.internal.dto.LevelControlModel;
80 import org.openhab.binding.avmfritz.internal.dto.PowerMeterModel;
81 import org.openhab.binding.avmfritz.internal.dto.SimpleOnOffModel;
82 import org.openhab.binding.avmfritz.internal.dto.SwitchModel;
83 import org.openhab.binding.avmfritz.internal.dto.TemperatureModel;
84 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
85 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
86 import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaSetBlindTargetCallback.BlindCommand;
87 import org.openhab.core.config.core.Configuration;
88 import org.openhab.core.library.types.DateTimeType;
89 import org.openhab.core.library.types.DecimalType;
90 import org.openhab.core.library.types.HSBType;
91 import org.openhab.core.library.types.IncreaseDecreaseType;
92 import org.openhab.core.library.types.OnOffType;
93 import org.openhab.core.library.types.OpenClosedType;
94 import org.openhab.core.library.types.PercentType;
95 import org.openhab.core.library.types.QuantityType;
96 import org.openhab.core.library.types.StopMoveType;
97 import org.openhab.core.library.types.StringType;
98 import org.openhab.core.library.types.UpDownType;
99 import org.openhab.core.library.unit.SIUnits;
100 import org.openhab.core.library.unit.Units;
101 import org.openhab.core.thing.Bridge;
102 import org.openhab.core.thing.Channel;
103 import org.openhab.core.thing.ChannelUID;
104 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
105 import org.openhab.core.thing.Thing;
106 import org.openhab.core.thing.ThingStatus;
107 import org.openhab.core.thing.ThingStatusDetail;
108 import org.openhab.core.thing.ThingUID;
109 import org.openhab.core.thing.binding.BaseThingHandler;
110 import org.openhab.core.thing.binding.BridgeHandler;
111 import org.openhab.core.thing.binding.ThingHandlerCallback;
112 import org.openhab.core.thing.type.ChannelTypeUID;
113 import org.openhab.core.types.Command;
114 import org.openhab.core.types.RefreshType;
115 import org.openhab.core.types.State;
116 import org.openhab.core.types.UnDefType;
117 import org.slf4j.Logger;
118 import org.slf4j.LoggerFactory;
119
120 /**
121  * Abstract handler for a FRITZ! thing. Handles commands, which are sent to one of the channels.
122  *
123  * @author Robert Bausdorf - Initial contribution
124  * @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
125  * @author Christoph Weitkamp - Added support for groups
126  * @author Ulrich Mertin - Added support for HAN-FUN blinds
127  * @author Christoph Sommer - Added support for color temperature
128  * @author Tobias Lange - Added abs color temperature and fixed on/off behavior of light blub
129  */
130 @NonNullByDefault
131 public abstract class AVMFritzBaseThingHandler extends BaseThingHandler implements FritzAhaStatusListener {
132
133     private final Logger logger = LoggerFactory.getLogger(AVMFritzBaseThingHandler.class);
134
135     /**
136      * keeps track of the current state for handling of increase/decrease
137      */
138     private AVMFritzBaseModel currentDevice = new DeviceModel();
139     private @Nullable String identifier;
140
141     /**
142      * Constructor
143      *
144      * @param thing Thing object representing a FRITZ! device
145      */
146     public AVMFritzBaseThingHandler(Thing thing) {
147         super(thing);
148     }
149
150     @Override
151     public void initialize() {
152         final AVMFritzDeviceConfiguration config = getConfigAs(AVMFritzDeviceConfiguration.class);
153         final String newIdentifier = config.ain;
154         if (newIdentifier == null || newIdentifier.isBlank()) {
155             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
156                     "The 'ain' parameter must be configured.");
157         } else {
158             this.identifier = newIdentifier;
159             updateStatus(ThingStatus.UNKNOWN);
160         }
161     }
162
163     @Override
164     public void onDeviceAdded(AVMFritzBaseModel device) {
165         // nothing to do
166     }
167
168     @Override
169     public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
170         if (thing.getUID().equals(thingUID)) {
171             logger.debug("Update thing '{}' with device model: {}", thingUID, device);
172             if (device.getPresent() == 1) {
173                 updateStatus(ThingStatus.ONLINE);
174             } else {
175                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device not present");
176             }
177             currentDevice = device;
178
179             updateProperties(device, editProperties());
180
181             if (device.isPowermeter()) {
182                 updatePowermeter(device.getPowermeter());
183             }
184             if (device.isSwitchableOutlet()) {
185                 updateSwitchableOutlet(device.getSwitch());
186             }
187             if (device.isHeatingThermostat()) {
188                 updateHeatingThermostat(device.getHkr());
189             }
190             if (device instanceof DeviceModel) {
191                 DeviceModel deviceModel = (DeviceModel) device;
192                 if (deviceModel.isTemperatureSensor()) {
193                     updateTemperatureSensor(deviceModel.getTemperature());
194                 }
195                 if (deviceModel.isHumiditySensor()) {
196                     updateHumiditySensor(deviceModel.getHumidity());
197                 }
198                 if (deviceModel.isHANFUNAlarmSensor()) {
199                     if (deviceModel.isHANFUNBlinds()) {
200                         updateHANFUNBlindsAlarmSensor(deviceModel.getAlert());
201                     } else {
202                         updateHANFUNAlarmSensor(deviceModel.getAlert());
203                     }
204                 }
205                 if (deviceModel.isHANFUNBlinds()) {
206                     updateLevelControl(deviceModel.getLevelControlModel());
207                 } else if (deviceModel.isColorLight()) {
208                     updateColorLight(deviceModel.getColorControlModel(), deviceModel.getLevelControlModel(),
209                             deviceModel.getSimpleOnOffUnit());
210                 } else if (deviceModel.isDimmableLight() && !deviceModel.isHANFUNBlinds()) {
211                     updateDimmableLight(deviceModel.getLevelControlModel());
212                 } else if (deviceModel.isHANFUNUnit() && deviceModel.isHANFUNOnOff()) {
213                     updateSimpleOnOffUnit(deviceModel.getSimpleOnOffUnit());
214                 }
215             }
216         }
217     }
218
219     private void updateHANFUNAlarmSensor(@Nullable AlertModel alertModel) {
220         if (alertModel != null) {
221             updateThingChannelState(CHANNEL_CONTACT_STATE,
222                     AlertModel.ON.equals(alertModel.getState()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
223         }
224     }
225
226     private void updateHANFUNBlindsAlarmSensor(@Nullable AlertModel alertModel) {
227         if (alertModel != null) {
228             updateThingChannelState(CHANNEL_OBSTRUCTION_ALARM,
229                     OnOffType.from(alertModel.hasObstructionAlarmOccurred()));
230             updateThingChannelState(CHANNEL_TEMPERATURE_ALARM, OnOffType.from(alertModel.hasTemperaturAlarmOccurred()));
231             if (alertModel.hasUnknownAlarmOccurred()) {
232                 logger.warn("Unknown blinds alarm {}", alertModel.getState());
233             }
234         }
235     }
236
237     protected void updateTemperatureSensor(@Nullable TemperatureModel temperatureModel) {
238         if (temperatureModel != null) {
239             updateThingChannelState(CHANNEL_TEMPERATURE,
240                     new QuantityType<>(temperatureModel.getCelsius(), SIUnits.CELSIUS));
241             updateThingChannelConfiguration(CHANNEL_TEMPERATURE, CONFIG_CHANNEL_TEMP_OFFSET,
242                     temperatureModel.getOffset());
243         }
244     }
245
246     protected void updateHumiditySensor(@Nullable HumidityModel humidityModel) {
247         if (humidityModel != null) {
248             updateThingChannelState(CHANNEL_HUMIDITY,
249                     new QuantityType<>(humidityModel.getRelativeHumidity(), Units.PERCENT));
250         }
251     }
252
253     protected void updateLevelControl(@Nullable LevelControlModel levelControlModel) {
254         if (levelControlModel != null) {
255             updateThingChannelState(CHANNEL_ROLLERSHUTTER, new PercentType(levelControlModel.getLevelPercentage()));
256         }
257     }
258
259     private void updateDimmableLight(@Nullable LevelControlModel levelControlModel) {
260         if (levelControlModel != null) {
261             updateThingChannelState(CHANNEL_BRIGHTNESS, new PercentType(levelControlModel.getLevelPercentage()));
262         }
263     }
264
265     private void updateColorLight(@Nullable ColorControlModel colorControlModel,
266             @Nullable LevelControlModel levelControlModel, @Nullable SimpleOnOffModel simpleOnOff) {
267         if (colorControlModel != null && levelControlModel != null && simpleOnOff != null) {
268             DecimalType hue = new DecimalType(colorControlModel.hue);
269             PercentType saturation = ColorControlModel.toPercent(colorControlModel.saturation);
270             PercentType brightness;
271             if (simpleOnOff.state) {
272                 brightness = new PercentType(levelControlModel.getLevelPercentage());
273             } else {
274                 brightness = PercentType.ZERO;
275             }
276             updateThingChannelState(CHANNEL_COLOR, new HSBType(hue, saturation, brightness));
277
278             if (colorControlModel.currentMode == 4) {
279                 int temperature = colorControlModel.temperature;
280                 int tempMired = 1000000 / temperature;
281                 int tempMinMired = 1000000 / 2700;
282                 int tempMaxMired = 1000000 / 6500;
283                 int pct = (tempMired - tempMinMired) * 100 / (tempMaxMired - tempMinMired);
284                 updateThingChannelState(CHANNEL_COLORTEMPERATURE, new PercentType(pct));
285                 updateThingChannelState(CHANNEL_COLORTEMPERATURE_ABS,
286                         new QuantityType<>(BigDecimal.valueOf(temperature), Units.KELVIN));
287             }
288         }
289     }
290
291     private void updateHeatingThermostat(@Nullable HeatingModel heatingModel) {
292         if (heatingModel != null) {
293             updateThingChannelState(CHANNEL_MODE, new StringType(heatingModel.getMode()));
294             updateThingChannelState(CHANNEL_LOCKED,
295                     BigDecimal.ZERO.equals(heatingModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
296             updateThingChannelState(CHANNEL_DEVICE_LOCKED,
297                     BigDecimal.ZERO.equals(heatingModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
298             updateThingChannelState(CHANNEL_ACTUALTEMP,
299                     new QuantityType<>(toCelsius(heatingModel.getTist()), SIUnits.CELSIUS));
300             updateThingChannelState(CHANNEL_SETTEMP,
301                     new QuantityType<>(toCelsius(heatingModel.getTsoll()), SIUnits.CELSIUS));
302             updateThingChannelState(CHANNEL_ECOTEMP,
303                     new QuantityType<>(toCelsius(heatingModel.getAbsenk()), SIUnits.CELSIUS));
304             updateThingChannelState(CHANNEL_COMFORTTEMP,
305                     new QuantityType<>(toCelsius(heatingModel.getKomfort()), SIUnits.CELSIUS));
306             updateThingChannelState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
307             NextChangeModel nextChange = heatingModel.getNextchange();
308             if (nextChange != null) {
309                 int endPeriod = nextChange.getEndperiod();
310                 updateThingChannelState(CHANNEL_NEXT_CHANGE, endPeriod == 0 ? UnDefType.UNDEF
311                         : new DateTimeType(
312                                 ZonedDateTime.ofInstant(Instant.ofEpochSecond(endPeriod), ZoneId.systemDefault())));
313                 BigDecimal nextTemperature = nextChange.getTchange();
314                 updateThingChannelState(CHANNEL_NEXTTEMP, TEMP_FRITZ_UNDEFINED.equals(nextTemperature) ? UnDefType.UNDEF
315                         : new QuantityType<>(toCelsius(nextTemperature), SIUnits.CELSIUS));
316             }
317             updateBattery(heatingModel);
318         }
319     }
320
321     protected void updateBattery(BatteryModel batteryModel) {
322         BigDecimal batteryLevel = batteryModel.getBattery();
323         updateThingChannelState(CHANNEL_BATTERY,
324                 batteryLevel == null ? UnDefType.UNDEF : new DecimalType(batteryLevel));
325         BigDecimal lowBattery = batteryModel.getBatterylow();
326         if (lowBattery == null) {
327             updateThingChannelState(CHANNEL_BATTERY_LOW, UnDefType.UNDEF);
328         } else {
329             updateThingChannelState(CHANNEL_BATTERY_LOW, OnOffType.from(BatteryModel.BATTERY_ON.equals(lowBattery)));
330         }
331     }
332
333     private void updateSimpleOnOffUnit(@Nullable SimpleOnOffModel simpleOnOffUnit) {
334         if (simpleOnOffUnit != null) {
335             updateThingChannelState(CHANNEL_ON_OFF, OnOffType.from(simpleOnOffUnit.state));
336         }
337     }
338
339     private void updateSwitchableOutlet(@Nullable SwitchModel switchModel) {
340         if (switchModel != null) {
341             updateThingChannelState(CHANNEL_MODE, new StringType(switchModel.getMode()));
342             updateThingChannelState(CHANNEL_LOCKED,
343                     BigDecimal.ZERO.equals(switchModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
344             updateThingChannelState(CHANNEL_DEVICE_LOCKED,
345                     BigDecimal.ZERO.equals(switchModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
346             BigDecimal state = switchModel.getState();
347             if (state == null) {
348                 updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
349             } else {
350                 updateThingChannelState(CHANNEL_OUTLET, OnOffType.from(SwitchModel.ON.equals(state)));
351             }
352         }
353     }
354
355     private void updatePowermeter(@Nullable PowerMeterModel powerMeterModel) {
356         if (powerMeterModel != null) {
357             updateThingChannelState(CHANNEL_ENERGY, new QuantityType<>(powerMeterModel.getEnergy(), Units.WATT_HOUR));
358             updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), Units.WATT));
359             updateThingChannelState(CHANNEL_VOLTAGE, new QuantityType<>(powerMeterModel.getVoltage(), Units.VOLT));
360         }
361     }
362
363     /**
364      * Updates thing properties.
365      *
366      * @param device the {@link AVMFritzBaseModel}
367      * @param editProperties map of existing properties
368      */
369     protected void updateProperties(AVMFritzBaseModel device, Map<String, String> editProperties) {
370         editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
371         updateProperties(editProperties);
372     }
373
374     /**
375      * Updates thing channels and creates dynamic channels if missing.
376      *
377      * @param channelId ID of the channel to be updated.
378      * @param state State to be set.
379      */
380     protected void updateThingChannelState(String channelId, State state) {
381         Channel channel = thing.getChannel(channelId);
382         if (channel != null) {
383             updateState(channel.getUID(), state);
384         } else {
385             logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
386             createChannel(channelId);
387         }
388     }
389
390     /**
391      * Creates a {@link ChannelTypeUID} from the given channel id.
392      *
393      * @param channelId ID of the channel type UID to be created.
394      * @return the channel type UID
395      */
396     private ChannelTypeUID createChannelTypeUID(String channelId) {
397         int pos = channelId.indexOf(ChannelUID.CHANNEL_GROUP_SEPARATOR);
398         String id = pos > -1 ? channelId.substring(pos + 1) : channelId;
399         final ChannelTypeUID channelTypeUID;
400         switch (id) {
401             case CHANNEL_BATTERY:
402                 channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID();
403                 break;
404             case CHANNEL_VOLTAGE:
405                 channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_ELECTRIC_VOLTAGE.getUID();
406                 break;
407             default:
408                 channelTypeUID = new ChannelTypeUID(BINDING_ID, id);
409                 break;
410         }
411         return channelTypeUID;
412     }
413
414     /**
415      * Creates new channels for the thing.
416      *
417      * @param channelId ID of the channel to be created.
418      */
419     private void createChannel(String channelId) {
420         ThingHandlerCallback callback = getCallback();
421         if (callback != null) {
422             final ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
423             final ChannelTypeUID channelTypeUID = createChannelTypeUID(channelId);
424             final Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
425             updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
426         }
427     }
428
429     /**
430      * Updates thing channel configurations.
431      *
432      * @param channelId ID of the channel which configuration to be updated.
433      * @param configId ID of the configuration to be updated.
434      * @param value Value to be set.
435      */
436     protected void updateThingChannelConfiguration(String channelId, String configId, Object value) {
437         Channel channel = thing.getChannel(channelId);
438         if (channel != null) {
439             Configuration editConfig = channel.getConfiguration();
440             editConfig.put(configId, value);
441         }
442     }
443
444     @Override
445     public void onDeviceGone(ThingUID thingUID) {
446         if (thing.getUID().equals(thingUID)) {
447             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
448         }
449     }
450
451     @Override
452     public void handleCommand(ChannelUID channelUID, Command command) {
453         String channelId = channelUID.getIdWithoutGroup();
454         logger.debug("Handle command '{}' for channel {}", command, channelId);
455         if (command == RefreshType.REFRESH) {
456             handleRefreshCommand();
457             return;
458         }
459         FritzAhaWebInterface fritzBox = getWebInterface();
460         if (fritzBox == null) {
461             logger.debug("Cannot handle command '{}' because connection is missing", command);
462             return;
463         }
464         String ain = getIdentifier();
465         if (ain == null) {
466             logger.debug("Cannot handle command '{}' because AIN is missing", command);
467             return;
468         }
469         switch (channelId) {
470             case CHANNEL_MODE:
471             case CHANNEL_LOCKED:
472             case CHANNEL_DEVICE_LOCKED:
473             case CHANNEL_TEMPERATURE:
474             case CHANNEL_HUMIDITY:
475             case CHANNEL_ENERGY:
476             case CHANNEL_POWER:
477             case CHANNEL_VOLTAGE:
478             case CHANNEL_ACTUALTEMP:
479             case CHANNEL_ECOTEMP:
480             case CHANNEL_COMFORTTEMP:
481             case CHANNEL_NEXT_CHANGE:
482             case CHANNEL_NEXTTEMP:
483             case CHANNEL_BATTERY:
484             case CHANNEL_BATTERY_LOW:
485             case CHANNEL_CONTACT_STATE:
486             case CHANNEL_LAST_CHANGE:
487             case CHANNEL_OBSTRUCTION_ALARM:
488             case CHANNEL_TEMPERATURE_ALARM:
489                 logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
490                 break;
491             case CHANNEL_OUTLET:
492             case CHANNEL_ON_OFF:
493                 if (command instanceof OnOffType) {
494                     fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
495                 }
496                 break;
497             case CHANNEL_COLOR:
498             case CHANNEL_BRIGHTNESS:
499                 BigDecimal brightness = null;
500                 if (command instanceof HSBType) {
501                     HSBType hsbType = (HSBType) command;
502                     brightness = hsbType.getBrightness().toBigDecimal();
503                     fritzBox.setUnmappedHueAndSaturation(ain, hsbType.getHue().intValue(),
504                             ColorControlModel.fromPercent(hsbType.getSaturation()), 0);
505                 } else if (command instanceof PercentType) {
506                     brightness = ((PercentType) command).toBigDecimal();
507                 } else if (command instanceof OnOffType) {
508                     fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
509                 } else if (command instanceof IncreaseDecreaseType) {
510                     brightness = ((DeviceModel) currentDevice).getLevelControlModel().getLevelPercentage();
511                     if (IncreaseDecreaseType.INCREASE.equals(command)) {
512                         brightness.add(BigDecimal.TEN);
513                     } else {
514                         brightness.subtract(BigDecimal.TEN);
515                     }
516                 }
517                 if (brightness != null) {
518                     if (brightness.equals(BigDecimal.ZERO)) {
519                         fritzBox.setSwitch(ain, false);
520                     } else {
521                         fritzBox.setSwitch(ain, true);
522                         fritzBox.setLevelPercentage(ain, brightness);
523                     }
524                 }
525                 break;
526             case CHANNEL_COLORTEMPERATURE:
527                 BigDecimal colorTemperaturePct = null;
528                 if (command instanceof PercentType) {
529                     colorTemperaturePct = ((PercentType) command).toBigDecimal();
530                 }
531                 if (colorTemperaturePct != null) {
532                     int pct = colorTemperaturePct.intValue();
533                     // AHA-HTTP-Inteface docu say that the values typically between 2700K and 6500K
534                     int tempMinMired = 1000000 / 2700;
535                     int tempMaxMired = 1000000 / 6500;
536                     int tempScaledMired = tempMinMired + ((tempMaxMired - tempMinMired) * pct / 100);
537                     int tempKelvin = 1000000 / tempScaledMired;
538                     fritzBox.setColorTemperature(ain, tempKelvin, 0);
539                 }
540                 break;
541             case CHANNEL_COLORTEMPERATURE_ABS:
542                 BigDecimal colorTemperature = null;
543                 if (command instanceof QuantityType) {
544                     QuantityType<?> convertedCommand = ((QuantityType<?>) command).toInvertibleUnit(Units.KELVIN);
545                     if (convertedCommand != null) {
546                         colorTemperature = convertedCommand.toBigDecimal();
547                     }
548                 } else if (command instanceof DecimalType) {
549                     colorTemperature = ((DecimalType) command).toBigDecimal();
550                 }
551                 if (colorTemperature != null) {
552                     fritzBox.setColorTemperature(ain, colorTemperature.intValue(), 0);
553                 }
554                 break;
555             case CHANNEL_SETTEMP:
556                 BigDecimal temperature = null;
557                 if (command instanceof DecimalType) {
558                     temperature = normalizeCelsius(((DecimalType) command).toBigDecimal());
559                 } else if (command instanceof QuantityType) {
560                     @SuppressWarnings("unchecked")
561                     QuantityType<Temperature> convertedCommand = ((QuantityType<Temperature>) command)
562                             .toUnit(SIUnits.CELSIUS);
563                     if (convertedCommand != null) {
564                         temperature = normalizeCelsius(convertedCommand.toBigDecimal());
565                     } else {
566                         logger.warn("Unable to convert unit from '{}' to '{}'. Skipping command.",
567                                 ((QuantityType<?>) command).getUnit(), SIUnits.CELSIUS);
568                     }
569                 } else if (command instanceof IncreaseDecreaseType) {
570                     temperature = currentDevice.getHkr().getTsoll();
571                     if (IncreaseDecreaseType.INCREASE.equals(command)) {
572                         temperature.add(BigDecimal.ONE);
573                     } else {
574                         temperature.subtract(BigDecimal.ONE);
575                     }
576                 } else if (command instanceof OnOffType) {
577                     temperature = OnOffType.ON.equals(command) ? TEMP_FRITZ_ON : TEMP_FRITZ_OFF;
578                 }
579                 if (temperature != null) {
580                     fritzBox.setSetTemp(ain, fromCelsius(temperature));
581                     HeatingModel heatingModel = currentDevice.getHkr();
582                     heatingModel.setTsoll(temperature);
583                     updateState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
584                 }
585                 break;
586             case CHANNEL_RADIATOR_MODE:
587                 BigDecimal targetTemperature = null;
588                 if (command instanceof StringType) {
589                     switch (command.toString()) {
590                         case MODE_ON:
591                             targetTemperature = TEMP_FRITZ_ON;
592                             break;
593                         case MODE_OFF:
594                             targetTemperature = TEMP_FRITZ_OFF;
595                             break;
596                         case MODE_COMFORT:
597                             targetTemperature = currentDevice.getHkr().getKomfort();
598                             break;
599                         case MODE_ECO:
600                             targetTemperature = currentDevice.getHkr().getAbsenk();
601                             break;
602                         case MODE_BOOST:
603                             targetTemperature = TEMP_FRITZ_MAX;
604                             break;
605                         case MODE_UNKNOWN:
606                         case MODE_WINDOW_OPEN:
607                             logger.debug("Command '{}' is a read-only command for channel {}.", command, channelId);
608                             break;
609                     }
610                     if (targetTemperature != null) {
611                         fritzBox.setSetTemp(ain, targetTemperature);
612                         currentDevice.getHkr().setTsoll(targetTemperature);
613                         updateState(CHANNEL_SETTEMP, new QuantityType<>(toCelsius(targetTemperature), SIUnits.CELSIUS));
614                     }
615                 }
616                 break;
617             case CHANNEL_ROLLERSHUTTER:
618                 if (command instanceof StopMoveType) {
619                     StopMoveType rollershutterCommand = (StopMoveType) command;
620                     if (StopMoveType.STOP.equals(rollershutterCommand)) {
621                         fritzBox.setBlind(ain, BlindCommand.STOP);
622                     } else {
623                         logger.debug("Received unknown rollershutter StopMove command MOVE");
624                     }
625                 } else if (command instanceof UpDownType) {
626                     UpDownType rollershutterCommand = (UpDownType) command;
627                     if (UpDownType.UP.equals(rollershutterCommand)) {
628                         fritzBox.setBlind(ain, BlindCommand.OPEN);
629                     } else {
630                         fritzBox.setBlind(ain, BlindCommand.CLOSE);
631                     }
632                 } else if (command instanceof PercentType) {
633                     BigDecimal levelPercentage = ((PercentType) command).toBigDecimal();
634                     fritzBox.setLevelPercentage(ain, levelPercentage);
635                 } else {
636                     logger.debug("Received unknown rollershutter command type '{}'", command.toString());
637                 }
638                 break;
639             default:
640                 logger.debug("Received unknown channel {}", channelId);
641                 break;
642         }
643     }
644
645     /**
646      * Handles a command for a given action.
647      *
648      * @param action
649      * @param duration
650      */
651     protected void handleAction(String action, long duration) {
652         FritzAhaWebInterface fritzBox = getWebInterface();
653         if (fritzBox == null) {
654             logger.debug("Cannot handle action '{}' because connection is missing", action);
655             return;
656         }
657         String ain = getIdentifier();
658         if (ain == null) {
659             logger.debug("Cannot handle action '{}' because AIN is missing", action);
660             return;
661         }
662         if (duration < 0 || 86400 < duration) {
663             throw new IllegalArgumentException("Duration must not be less than zero or greater than 86400");
664         }
665         switch (action) {
666             case MODE_BOOST:
667                 fritzBox.setBoostMode(ain,
668                         duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
669                 break;
670             case MODE_WINDOW_OPEN:
671                 fritzBox.setWindowOpenMode(ain,
672                         duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
673                 break;
674             default:
675                 logger.debug("Received unknown action '{}'", action);
676                 break;
677         }
678     }
679
680     /**
681      * Provides the web interface object.
682      *
683      * @return The web interface object
684      */
685     private @Nullable FritzAhaWebInterface getWebInterface() {
686         Bridge bridge = getBridge();
687         if (bridge != null) {
688             BridgeHandler handler = bridge.getHandler();
689             if (handler instanceof AVMFritzBaseBridgeHandler) {
690                 return ((AVMFritzBaseBridgeHandler) handler).getWebInterface();
691             }
692         }
693         return null;
694     }
695
696     /**
697      * Handles a refresh command.
698      */
699     private void handleRefreshCommand() {
700         Bridge bridge = getBridge();
701         if (bridge != null) {
702             BridgeHandler handler = bridge.getHandler();
703             if (handler instanceof AVMFritzBaseBridgeHandler) {
704                 ((AVMFritzBaseBridgeHandler) handler).handleRefreshCommand();
705             }
706         }
707     }
708
709     /**
710      * Returns the AIN.
711      *
712      * @return the AIN
713      */
714     public @Nullable String getIdentifier() {
715         return identifier;
716     }
717 }