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.avmfritz.internal.handler;
15 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.*;
16 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.*;
18 import java.math.BigDecimal;
19 import java.time.Instant;
20 import java.time.ZoneId;
21 import java.time.ZonedDateTime;
24 import javax.measure.quantity.Temperature;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.avmfritz.internal.config.AVMFritzDeviceConfiguration;
29 import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
30 import org.openhab.binding.avmfritz.internal.dto.AlertModel;
31 import org.openhab.binding.avmfritz.internal.dto.BatteryModel;
32 import org.openhab.binding.avmfritz.internal.dto.ColorControlModel;
33 import org.openhab.binding.avmfritz.internal.dto.DeviceModel;
34 import org.openhab.binding.avmfritz.internal.dto.HeatingModel;
35 import org.openhab.binding.avmfritz.internal.dto.HeatingModel.NextChangeModel;
36 import org.openhab.binding.avmfritz.internal.dto.HumidityModel;
37 import org.openhab.binding.avmfritz.internal.dto.LevelControlModel;
38 import org.openhab.binding.avmfritz.internal.dto.PowerMeterModel;
39 import org.openhab.binding.avmfritz.internal.dto.SimpleOnOffModel;
40 import org.openhab.binding.avmfritz.internal.dto.SwitchModel;
41 import org.openhab.binding.avmfritz.internal.dto.TemperatureModel;
42 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
43 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
44 import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaSetBlindTargetCallback.BlindCommand;
45 import org.openhab.core.config.core.Configuration;
46 import org.openhab.core.library.types.DateTimeType;
47 import org.openhab.core.library.types.DecimalType;
48 import org.openhab.core.library.types.HSBType;
49 import org.openhab.core.library.types.IncreaseDecreaseType;
50 import org.openhab.core.library.types.OnOffType;
51 import org.openhab.core.library.types.OpenClosedType;
52 import org.openhab.core.library.types.PercentType;
53 import org.openhab.core.library.types.QuantityType;
54 import org.openhab.core.library.types.StopMoveType;
55 import org.openhab.core.library.types.StringType;
56 import org.openhab.core.library.types.UpDownType;
57 import org.openhab.core.library.unit.SIUnits;
58 import org.openhab.core.library.unit.Units;
59 import org.openhab.core.thing.Bridge;
60 import org.openhab.core.thing.Channel;
61 import org.openhab.core.thing.ChannelUID;
62 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
63 import org.openhab.core.thing.Thing;
64 import org.openhab.core.thing.ThingStatus;
65 import org.openhab.core.thing.ThingStatusDetail;
66 import org.openhab.core.thing.ThingUID;
67 import org.openhab.core.thing.binding.BaseThingHandler;
68 import org.openhab.core.thing.binding.BridgeHandler;
69 import org.openhab.core.thing.binding.ThingHandlerCallback;
70 import org.openhab.core.thing.type.ChannelTypeUID;
71 import org.openhab.core.types.Command;
72 import org.openhab.core.types.RefreshType;
73 import org.openhab.core.types.State;
74 import org.openhab.core.types.UnDefType;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
79 * Abstract handler for a FRITZ! thing. Handles commands, which are sent to one of the channels.
81 * @author Robert Bausdorf - Initial contribution
82 * @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
83 * @author Christoph Weitkamp - Added support for groups
84 * @author Ulrich Mertin - Added support for HAN-FUN blinds
87 public abstract class AVMFritzBaseThingHandler extends BaseThingHandler implements FritzAhaStatusListener {
89 private final Logger logger = LoggerFactory.getLogger(AVMFritzBaseThingHandler.class);
92 * keeps track of the current state for handling of increase/decrease
94 private AVMFritzBaseModel currentDevice = new DeviceModel();
95 private @Nullable String identifier;
100 * @param thing Thing object representing a FRITZ! device
102 public AVMFritzBaseThingHandler(Thing thing) {
107 public void initialize() {
108 final AVMFritzDeviceConfiguration config = getConfigAs(AVMFritzDeviceConfiguration.class);
109 final String newIdentifier = config.ain;
110 if (newIdentifier == null || newIdentifier.isBlank()) {
111 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
112 "The 'ain' parameter must be configured.");
114 this.identifier = newIdentifier;
115 updateStatus(ThingStatus.UNKNOWN);
120 public void onDeviceAdded(AVMFritzBaseModel device) {
125 public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
126 if (thing.getUID().equals(thingUID)) {
127 logger.debug("Update thing '{}' with device model: {}", thingUID, device);
128 if (device.getPresent() == 1) {
129 updateStatus(ThingStatus.ONLINE);
131 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device not present");
133 currentDevice = device;
135 updateProperties(device, editProperties());
137 if (device.isPowermeter()) {
138 updatePowermeter(device.getPowermeter());
140 if (device.isSwitchableOutlet()) {
141 updateSwitchableOutlet(device.getSwitch());
143 if (device.isHeatingThermostat()) {
144 updateHeatingThermostat(device.getHkr());
146 if (device instanceof DeviceModel) {
147 DeviceModel deviceModel = (DeviceModel) device;
148 if (deviceModel.isTemperatureSensor()) {
149 updateTemperatureSensor(deviceModel.getTemperature());
151 if (deviceModel.isHumiditySensor()) {
152 updateHumiditySensor(deviceModel.getHumidity());
154 if (deviceModel.isHANFUNAlarmSensor()) {
155 if (deviceModel.isHANFUNBlinds()) {
156 updateHANFUNBlindsAlarmSensor(deviceModel.getAlert());
158 updateHANFUNAlarmSensor(deviceModel.getAlert());
161 if (deviceModel.isHANFUNBlinds()) {
162 updateLevelControl(deviceModel.getLevelControlModel());
163 } else if (deviceModel.isColorLight()) {
164 updateColorLight(deviceModel.getColorControlModel(), deviceModel.getLevelControlModel());
165 } else if (deviceModel.isDimmableLight() && !deviceModel.isHANFUNBlinds()) {
166 updateDimmableLight(deviceModel.getLevelControlModel());
167 } else if (deviceModel.isHANFUNUnit() && deviceModel.isHANFUNOnOff()) {
168 updateSimpleOnOffUnit(deviceModel.getSimpleOnOffUnit());
174 private void updateHANFUNAlarmSensor(@Nullable AlertModel alertModel) {
175 if (alertModel != null) {
176 updateThingChannelState(CHANNEL_CONTACT_STATE,
177 AlertModel.ON.equals(alertModel.getState()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
181 private void updateHANFUNBlindsAlarmSensor(@Nullable AlertModel alertModel) {
182 if (alertModel != null) {
183 updateThingChannelState(CHANNEL_OBSTRUCTION_ALARM,
184 OnOffType.from(alertModel.hasObstructionAlarmOccurred()));
185 updateThingChannelState(CHANNEL_TEMPERATURE_ALARM, OnOffType.from(alertModel.hasTemperaturAlarmOccurred()));
186 if (alertModel.hasUnknownAlarmOccurred()) {
187 logger.warn("Unknown blinds alarm {}", alertModel.getState());
192 protected void updateTemperatureSensor(@Nullable TemperatureModel temperatureModel) {
193 if (temperatureModel != null) {
194 updateThingChannelState(CHANNEL_TEMPERATURE,
195 new QuantityType<>(temperatureModel.getCelsius(), SIUnits.CELSIUS));
196 updateThingChannelConfiguration(CHANNEL_TEMPERATURE, CONFIG_CHANNEL_TEMP_OFFSET,
197 temperatureModel.getOffset());
201 protected void updateHumiditySensor(@Nullable HumidityModel humidityModel) {
202 if (humidityModel != null) {
203 updateThingChannelState(CHANNEL_HUMIDITY,
204 new QuantityType<>(humidityModel.getRelativeHumidity(), Units.PERCENT));
208 protected void updateLevelControl(@Nullable LevelControlModel levelControlModel) {
209 if (levelControlModel != null) {
210 updateThingChannelState(CHANNEL_ROLLERSHUTTER, new PercentType(levelControlModel.getLevelPercentage()));
214 private void updateDimmableLight(@Nullable LevelControlModel levelControlModel) {
215 if (levelControlModel != null) {
216 updateThingChannelState(CHANNEL_BRIGHTNESS, new PercentType(levelControlModel.getLevelPercentage()));
220 private void updateColorLight(@Nullable ColorControlModel colorControlModel,
221 @Nullable LevelControlModel levelControlModel) {
222 if (colorControlModel != null && levelControlModel != null) {
223 DecimalType hue = new DecimalType(colorControlModel.hue);
224 PercentType saturation = ColorControlModel.toPercent(colorControlModel.saturation);
225 PercentType brightness = new PercentType(levelControlModel.getLevelPercentage());
226 updateThingChannelState(CHANNEL_COLOR, new HSBType(hue, saturation, brightness));
230 private void updateHeatingThermostat(@Nullable HeatingModel heatingModel) {
231 if (heatingModel != null) {
232 updateThingChannelState(CHANNEL_MODE, new StringType(heatingModel.getMode()));
233 updateThingChannelState(CHANNEL_LOCKED,
234 BigDecimal.ZERO.equals(heatingModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
235 updateThingChannelState(CHANNEL_DEVICE_LOCKED,
236 BigDecimal.ZERO.equals(heatingModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
237 updateThingChannelState(CHANNEL_ACTUALTEMP,
238 new QuantityType<>(toCelsius(heatingModel.getTist()), SIUnits.CELSIUS));
239 updateThingChannelState(CHANNEL_SETTEMP,
240 new QuantityType<>(toCelsius(heatingModel.getTsoll()), SIUnits.CELSIUS));
241 updateThingChannelState(CHANNEL_ECOTEMP,
242 new QuantityType<>(toCelsius(heatingModel.getAbsenk()), SIUnits.CELSIUS));
243 updateThingChannelState(CHANNEL_COMFORTTEMP,
244 new QuantityType<>(toCelsius(heatingModel.getKomfort()), SIUnits.CELSIUS));
245 updateThingChannelState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
246 NextChangeModel nextChange = heatingModel.getNextchange();
247 if (nextChange != null) {
248 int endPeriod = nextChange.getEndperiod();
249 updateThingChannelState(CHANNEL_NEXT_CHANGE, endPeriod == 0 ? UnDefType.UNDEF
251 ZonedDateTime.ofInstant(Instant.ofEpochSecond(endPeriod), ZoneId.systemDefault())));
252 BigDecimal nextTemperature = nextChange.getTchange();
253 updateThingChannelState(CHANNEL_NEXTTEMP, TEMP_FRITZ_UNDEFINED.equals(nextTemperature) ? UnDefType.UNDEF
254 : new QuantityType<>(toCelsius(nextTemperature), SIUnits.CELSIUS));
256 updateBattery(heatingModel);
260 protected void updateBattery(BatteryModel batteryModel) {
261 BigDecimal batteryLevel = batteryModel.getBattery();
262 updateThingChannelState(CHANNEL_BATTERY,
263 batteryLevel == null ? UnDefType.UNDEF : new DecimalType(batteryLevel));
264 BigDecimal lowBattery = batteryModel.getBatterylow();
265 if (lowBattery == null) {
266 updateThingChannelState(CHANNEL_BATTERY_LOW, UnDefType.UNDEF);
268 updateThingChannelState(CHANNEL_BATTERY_LOW, OnOffType.from(BatteryModel.BATTERY_ON.equals(lowBattery)));
272 private void updateSimpleOnOffUnit(@Nullable SimpleOnOffModel simpleOnOffUnit) {
273 if (simpleOnOffUnit != null) {
274 updateThingChannelState(CHANNEL_ON_OFF, OnOffType.from(simpleOnOffUnit.state));
278 private void updateSwitchableOutlet(@Nullable SwitchModel switchModel) {
279 if (switchModel != null) {
280 updateThingChannelState(CHANNEL_MODE, new StringType(switchModel.getMode()));
281 updateThingChannelState(CHANNEL_LOCKED,
282 BigDecimal.ZERO.equals(switchModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
283 updateThingChannelState(CHANNEL_DEVICE_LOCKED,
284 BigDecimal.ZERO.equals(switchModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
285 BigDecimal state = switchModel.getState();
287 updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
289 updateThingChannelState(CHANNEL_OUTLET, OnOffType.from(SwitchModel.ON.equals(state)));
294 private void updatePowermeter(@Nullable PowerMeterModel powerMeterModel) {
295 if (powerMeterModel != null) {
296 updateThingChannelState(CHANNEL_ENERGY, new QuantityType<>(powerMeterModel.getEnergy(), Units.WATT_HOUR));
297 updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), Units.WATT));
298 updateThingChannelState(CHANNEL_VOLTAGE, new QuantityType<>(powerMeterModel.getVoltage(), Units.VOLT));
303 * Updates thing properties.
305 * @param device the {@link AVMFritzBaseModel}
306 * @param editProperties map of existing properties
308 protected void updateProperties(AVMFritzBaseModel device, Map<String, String> editProperties) {
309 editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
310 updateProperties(editProperties);
314 * Updates thing channels and creates dynamic channels if missing.
316 * @param channelId ID of the channel to be updated.
317 * @param state State to be set.
319 protected void updateThingChannelState(String channelId, State state) {
320 Channel channel = thing.getChannel(channelId);
321 if (channel != null) {
322 updateState(channel.getUID(), state);
324 logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
325 createChannel(channelId);
330 * Creates a {@link ChannelTypeUID} from the given channel id.
332 * @param channelId ID of the channel type UID to be created.
333 * @return the channel type UID
335 private ChannelTypeUID createChannelTypeUID(String channelId) {
336 int pos = channelId.indexOf(ChannelUID.CHANNEL_GROUP_SEPARATOR);
337 String id = pos > -1 ? channelId.substring(pos + 1) : channelId;
338 final ChannelTypeUID channelTypeUID;
340 case CHANNEL_BATTERY:
341 channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID();
343 case CHANNEL_VOLTAGE:
344 channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_ELECTRIC_VOLTAGE.getUID();
347 channelTypeUID = new ChannelTypeUID(BINDING_ID, id);
350 return channelTypeUID;
354 * Creates new channels for the thing.
356 * @param channelId ID of the channel to be created.
358 private void createChannel(String channelId) {
359 ThingHandlerCallback callback = getCallback();
360 if (callback != null) {
361 final ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
362 final ChannelTypeUID channelTypeUID = createChannelTypeUID(channelId);
363 final Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
364 updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
369 * Updates thing channel configurations.
371 * @param channelId ID of the channel which configuration to be updated.
372 * @param configId ID of the configuration to be updated.
373 * @param value Value to be set.
375 protected void updateThingChannelConfiguration(String channelId, String configId, Object value) {
376 Channel channel = thing.getChannel(channelId);
377 if (channel != null) {
378 Configuration editConfig = channel.getConfiguration();
379 editConfig.put(configId, value);
384 public void onDeviceGone(ThingUID thingUID) {
385 if (thing.getUID().equals(thingUID)) {
386 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
391 public void handleCommand(ChannelUID channelUID, Command command) {
392 String channelId = channelUID.getIdWithoutGroup();
393 logger.debug("Handle command '{}' for channel {}", command, channelId);
394 if (command == RefreshType.REFRESH) {
395 handleRefreshCommand();
398 FritzAhaWebInterface fritzBox = getWebInterface();
399 if (fritzBox == null) {
400 logger.debug("Cannot handle command '{}' because connection is missing", command);
403 String ain = getIdentifier();
405 logger.debug("Cannot handle command '{}' because AIN is missing", command);
411 case CHANNEL_DEVICE_LOCKED:
412 case CHANNEL_TEMPERATURE:
413 case CHANNEL_HUMIDITY:
416 case CHANNEL_VOLTAGE:
417 case CHANNEL_ACTUALTEMP:
418 case CHANNEL_ECOTEMP:
419 case CHANNEL_COMFORTTEMP:
420 case CHANNEL_NEXT_CHANGE:
421 case CHANNEL_NEXTTEMP:
422 case CHANNEL_BATTERY:
423 case CHANNEL_BATTERY_LOW:
424 case CHANNEL_CONTACT_STATE:
425 case CHANNEL_LAST_CHANGE:
426 case CHANNEL_OBSTRUCTION_ALARM:
427 case CHANNEL_TEMPERATURE_ALARM:
428 logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
432 if (command instanceof OnOffType) {
433 fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
437 case CHANNEL_BRIGHTNESS:
438 BigDecimal brightness = null;
439 if (command instanceof HSBType) {
440 HSBType hsbType = (HSBType) command;
441 brightness = hsbType.getBrightness().toBigDecimal();
442 fritzBox.setUnmappedHueAndSaturation(ain, hsbType.getHue().intValue(),
443 ColorControlModel.fromPercent(hsbType.getSaturation()), 0);
444 } else if (command instanceof PercentType) {
445 brightness = ((PercentType) command).toBigDecimal();
446 } else if (command instanceof OnOffType) {
447 fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
448 } else if (command instanceof IncreaseDecreaseType) {
449 brightness = ((DeviceModel) currentDevice).getLevelControlModel().getLevelPercentage();
450 if (IncreaseDecreaseType.INCREASE.equals(command)) {
451 brightness.add(BigDecimal.TEN);
453 brightness.subtract(BigDecimal.TEN);
456 if (brightness != null) {
457 fritzBox.setLevelPercentage(ain, brightness);
460 case CHANNEL_SETTEMP:
461 BigDecimal temperature = null;
462 if (command instanceof DecimalType) {
463 temperature = normalizeCelsius(((DecimalType) command).toBigDecimal());
464 } else if (command instanceof QuantityType) {
465 @SuppressWarnings("unchecked")
466 QuantityType<Temperature> convertedCommand = ((QuantityType<Temperature>) command)
467 .toUnit(SIUnits.CELSIUS);
468 if (convertedCommand != null) {
469 temperature = normalizeCelsius(convertedCommand.toBigDecimal());
471 logger.warn("Unable to convert unit from '{}' to '{}'. Skipping command.",
472 ((QuantityType<?>) command).getUnit(), SIUnits.CELSIUS);
474 } else if (command instanceof IncreaseDecreaseType) {
475 temperature = currentDevice.getHkr().getTsoll();
476 if (IncreaseDecreaseType.INCREASE.equals(command)) {
477 temperature.add(BigDecimal.ONE);
479 temperature.subtract(BigDecimal.ONE);
481 } else if (command instanceof OnOffType) {
482 temperature = OnOffType.ON.equals(command) ? TEMP_FRITZ_ON : TEMP_FRITZ_OFF;
484 if (temperature != null) {
485 fritzBox.setSetTemp(ain, fromCelsius(temperature));
486 HeatingModel heatingModel = currentDevice.getHkr();
487 heatingModel.setTsoll(temperature);
488 updateState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
491 case CHANNEL_RADIATOR_MODE:
492 BigDecimal targetTemperature = null;
493 if (command instanceof StringType) {
494 switch (command.toString()) {
496 targetTemperature = TEMP_FRITZ_ON;
499 targetTemperature = TEMP_FRITZ_OFF;
502 targetTemperature = currentDevice.getHkr().getKomfort();
505 targetTemperature = currentDevice.getHkr().getAbsenk();
508 targetTemperature = TEMP_FRITZ_MAX;
511 case MODE_WINDOW_OPEN:
512 logger.debug("Command '{}' is a read-only command for channel {}.", command, channelId);
515 if (targetTemperature != null) {
516 fritzBox.setSetTemp(ain, targetTemperature);
517 currentDevice.getHkr().setTsoll(targetTemperature);
518 updateState(CHANNEL_SETTEMP, new QuantityType<>(toCelsius(targetTemperature), SIUnits.CELSIUS));
522 case CHANNEL_ROLLERSHUTTER:
523 if (command instanceof StopMoveType) {
524 StopMoveType rollershutterCommand = (StopMoveType) command;
525 if (StopMoveType.STOP.equals(rollershutterCommand)) {
526 fritzBox.setBlind(ain, BlindCommand.STOP);
528 logger.debug("Received unknown rollershutter StopMove command MOVE");
530 } else if (command instanceof UpDownType) {
531 UpDownType rollershutterCommand = (UpDownType) command;
532 if (UpDownType.UP.equals(rollershutterCommand)) {
533 fritzBox.setBlind(ain, BlindCommand.OPEN);
535 fritzBox.setBlind(ain, BlindCommand.CLOSE);
537 } else if (command instanceof PercentType) {
538 BigDecimal levelPercentage = ((PercentType) command).toBigDecimal();
539 fritzBox.setLevelPercentage(ain, levelPercentage);
541 logger.debug("Received unknown rollershutter command type '{}'", command.toString());
545 logger.debug("Received unknown channel {}", channelId);
551 * Handles a command for a given action.
556 protected void handleAction(String action, long duration) {
557 FritzAhaWebInterface fritzBox = getWebInterface();
558 if (fritzBox == null) {
559 logger.debug("Cannot handle action '{}' because connection is missing", action);
562 String ain = getIdentifier();
564 logger.debug("Cannot handle action '{}' because AIN is missing", action);
567 if (duration < 0 || 86400 < duration) {
568 throw new IllegalArgumentException("Duration must not be less than zero or greater than 86400");
572 fritzBox.setBoostMode(ain,
573 duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
575 case MODE_WINDOW_OPEN:
576 fritzBox.setWindowOpenMode(ain,
577 duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
580 logger.debug("Received unknown action '{}'", action);
586 * Provides the web interface object.
588 * @return The web interface object
590 private @Nullable FritzAhaWebInterface getWebInterface() {
591 Bridge bridge = getBridge();
592 if (bridge != null) {
593 BridgeHandler handler = bridge.getHandler();
594 if (handler instanceof AVMFritzBaseBridgeHandler) {
595 return ((AVMFritzBaseBridgeHandler) handler).getWebInterface();
602 * Handles a refresh command.
604 private void handleRefreshCommand() {
605 Bridge bridge = getBridge();
606 if (bridge != null) {
607 BridgeHandler handler = bridge.getHandler();
608 if (handler instanceof AVMFritzBaseBridgeHandler) {
609 ((AVMFritzBaseBridgeHandler) handler).handleRefreshCommand();
619 public @Nullable String getIdentifier() {