]> git.basschouten.com Git - openhab-addons.git/blob
00debd0235e5159ffa02d8fac0b897985ffc7ba8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.*;
16 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.*;
17
18 import java.math.BigDecimal;
19 import java.time.Instant;
20 import java.time.ZoneId;
21 import java.time.ZonedDateTime;
22 import java.util.Map;
23
24 import javax.measure.quantity.Temperature;
25
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.DeviceModel;
33 import org.openhab.binding.avmfritz.internal.dto.HeatingModel;
34 import org.openhab.binding.avmfritz.internal.dto.HeatingModel.NextChangeModel;
35 import org.openhab.binding.avmfritz.internal.dto.HumidityModel;
36 import org.openhab.binding.avmfritz.internal.dto.LevelcontrolModel;
37 import org.openhab.binding.avmfritz.internal.dto.PowerMeterModel;
38 import org.openhab.binding.avmfritz.internal.dto.SimpleOnOffModel;
39 import org.openhab.binding.avmfritz.internal.dto.SwitchModel;
40 import org.openhab.binding.avmfritz.internal.dto.TemperatureModel;
41 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
42 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
43 import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaSetBlindTargetCallback.BlindCommand;
44 import org.openhab.core.config.core.Configuration;
45 import org.openhab.core.library.types.DateTimeType;
46 import org.openhab.core.library.types.DecimalType;
47 import org.openhab.core.library.types.IncreaseDecreaseType;
48 import org.openhab.core.library.types.OnOffType;
49 import org.openhab.core.library.types.OpenClosedType;
50 import org.openhab.core.library.types.PercentType;
51 import org.openhab.core.library.types.QuantityType;
52 import org.openhab.core.library.types.StopMoveType;
53 import org.openhab.core.library.types.StringType;
54 import org.openhab.core.library.types.UpDownType;
55 import org.openhab.core.library.unit.SIUnits;
56 import org.openhab.core.library.unit.Units;
57 import org.openhab.core.thing.Bridge;
58 import org.openhab.core.thing.Channel;
59 import org.openhab.core.thing.ChannelUID;
60 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
61 import org.openhab.core.thing.Thing;
62 import org.openhab.core.thing.ThingStatus;
63 import org.openhab.core.thing.ThingStatusDetail;
64 import org.openhab.core.thing.ThingUID;
65 import org.openhab.core.thing.binding.BaseThingHandler;
66 import org.openhab.core.thing.binding.BridgeHandler;
67 import org.openhab.core.thing.binding.ThingHandlerCallback;
68 import org.openhab.core.thing.type.ChannelTypeUID;
69 import org.openhab.core.types.Command;
70 import org.openhab.core.types.RefreshType;
71 import org.openhab.core.types.State;
72 import org.openhab.core.types.UnDefType;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76 /**
77  * Abstract handler for a FRITZ! thing. Handles commands, which are sent to one of the channels.
78  *
79  * @author Robert Bausdorf - Initial contribution
80  * @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
81  * @author Christoph Weitkamp - Added support for groups
82  * @author Ulrich Mertin - Added support for HAN-FUN blinds
83  */
84 @NonNullByDefault
85 public abstract class AVMFritzBaseThingHandler extends BaseThingHandler implements FritzAhaStatusListener {
86
87     private final Logger logger = LoggerFactory.getLogger(AVMFritzBaseThingHandler.class);
88
89     /**
90      * keeps track of the current state for handling of increase/decrease
91      */
92     private @Nullable AVMFritzBaseModel state;
93     private @Nullable String identifier;
94
95     /**
96      * Constructor
97      *
98      * @param thing Thing object representing a FRITZ! device
99      */
100     public AVMFritzBaseThingHandler(Thing thing) {
101         super(thing);
102     }
103
104     @Override
105     public void initialize() {
106         final AVMFritzDeviceConfiguration config = getConfigAs(AVMFritzDeviceConfiguration.class);
107         final String newIdentifier = config.ain;
108         if (newIdentifier == null || newIdentifier.isBlank()) {
109             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110                     "The 'ain' parameter must be configured.");
111         } else {
112             this.identifier = newIdentifier;
113             updateStatus(ThingStatus.UNKNOWN);
114         }
115     }
116
117     @Override
118     public void onDeviceAdded(AVMFritzBaseModel device) {
119         // nothing to do
120     }
121
122     @Override
123     public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
124         if (thing.getUID().equals(thingUID)) {
125             logger.debug("Update thing '{}' with device model: {}", thingUID, device);
126             if (device.getPresent() == 1) {
127                 updateStatus(ThingStatus.ONLINE);
128             } else {
129                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device not present");
130             }
131             state = device;
132
133             updateProperties(device, editProperties());
134
135             if (device.isPowermeter()) {
136                 updatePowermeter(device.getPowermeter());
137             }
138             if (device.isSwitchableOutlet()) {
139                 updateSwitchableOutlet(device.getSwitch());
140             }
141             if (device.isHeatingThermostat()) {
142                 updateHeatingThermostat(device.getHkr());
143             }
144             if (device.isHANFUNUnit() && device.isHANFUNOnOff()) {
145                 updateSimpleOnOffUnit(device.getSimpleOnOffUnit());
146             }
147             if (device instanceof DeviceModel) {
148                 DeviceModel deviceModel = (DeviceModel) device;
149                 if (deviceModel.isTemperatureSensor()) {
150                     updateTemperatureSensor(deviceModel.getTemperature());
151                 }
152                 if (deviceModel.isHumiditySensor()) {
153                     updateHumiditySensor(deviceModel.getHumidity());
154                 }
155                 if (deviceModel.isHANFUNAlarmSensor()) {
156                     updateHANFUNAlarmSensor(deviceModel.getAlert());
157                 }
158                 if (deviceModel.isHANFUNBlinds()) {
159                     updateLevelcontrol(deviceModel.getLevelcontrol());
160                 }
161             }
162         }
163     }
164
165     private void updateHANFUNAlarmSensor(@Nullable AlertModel alertModel) {
166         if (alertModel != null) {
167             updateThingChannelState(CHANNEL_CONTACT_STATE,
168                     AlertModel.ON.equals(alertModel.getState()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
169         }
170     }
171
172     protected void updateTemperatureSensor(@Nullable TemperatureModel temperatureModel) {
173         if (temperatureModel != null) {
174             updateThingChannelState(CHANNEL_TEMPERATURE,
175                     new QuantityType<>(temperatureModel.getCelsius(), SIUnits.CELSIUS));
176             updateThingChannelConfiguration(CHANNEL_TEMPERATURE, CONFIG_CHANNEL_TEMP_OFFSET,
177                     temperatureModel.getOffset());
178         }
179     }
180
181     protected void updateHumiditySensor(@Nullable HumidityModel humidityModel) {
182         if (humidityModel != null) {
183             updateThingChannelState(CHANNEL_HUMIDITY,
184                     new QuantityType<>(humidityModel.getRelativeHumidity(), Units.PERCENT));
185         }
186     }
187
188     protected void updateLevelcontrol(@Nullable LevelcontrolModel levelcontrolModel) {
189         if (levelcontrolModel != null) {
190             updateThingChannelState(CHANNEL_ROLLERSHUTTER, new PercentType(levelcontrolModel.getLevelPercentage()));
191         }
192     }
193
194     private void updateHeatingThermostat(@Nullable HeatingModel heatingModel) {
195         if (heatingModel != null) {
196             updateThingChannelState(CHANNEL_MODE, new StringType(heatingModel.getMode()));
197             updateThingChannelState(CHANNEL_LOCKED,
198                     BigDecimal.ZERO.equals(heatingModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
199             updateThingChannelState(CHANNEL_DEVICE_LOCKED,
200                     BigDecimal.ZERO.equals(heatingModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
201             updateThingChannelState(CHANNEL_ACTUALTEMP,
202                     new QuantityType<>(toCelsius(heatingModel.getTist()), SIUnits.CELSIUS));
203             updateThingChannelState(CHANNEL_SETTEMP,
204                     new QuantityType<>(toCelsius(heatingModel.getTsoll()), SIUnits.CELSIUS));
205             updateThingChannelState(CHANNEL_ECOTEMP,
206                     new QuantityType<>(toCelsius(heatingModel.getAbsenk()), SIUnits.CELSIUS));
207             updateThingChannelState(CHANNEL_COMFORTTEMP,
208                     new QuantityType<>(toCelsius(heatingModel.getKomfort()), SIUnits.CELSIUS));
209             updateThingChannelState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
210             NextChangeModel nextChange = heatingModel.getNextchange();
211             if (nextChange != null) {
212                 int endPeriod = nextChange.getEndperiod();
213                 updateThingChannelState(CHANNEL_NEXT_CHANGE, endPeriod == 0 ? UnDefType.UNDEF
214                         : new DateTimeType(
215                                 ZonedDateTime.ofInstant(Instant.ofEpochSecond(endPeriod), ZoneId.systemDefault())));
216                 BigDecimal nextTemperature = nextChange.getTchange();
217                 updateThingChannelState(CHANNEL_NEXTTEMP, TEMP_FRITZ_UNDEFINED.equals(nextTemperature) ? UnDefType.UNDEF
218                         : new QuantityType<>(toCelsius(nextTemperature), SIUnits.CELSIUS));
219             }
220             updateBattery(heatingModel);
221         }
222     }
223
224     protected void updateBattery(BatteryModel batteryModel) {
225         BigDecimal batteryLevel = batteryModel.getBattery();
226         updateThingChannelState(CHANNEL_BATTERY,
227                 batteryLevel == null ? UnDefType.UNDEF : new DecimalType(batteryLevel));
228         BigDecimal lowBattery = batteryModel.getBatterylow();
229         if (lowBattery == null) {
230             updateThingChannelState(CHANNEL_BATTERY_LOW, UnDefType.UNDEF);
231         } else {
232             updateThingChannelState(CHANNEL_BATTERY_LOW, OnOffType.from(BatteryModel.BATTERY_ON.equals(lowBattery)));
233         }
234     }
235
236     private void updateSimpleOnOffUnit(@Nullable SimpleOnOffModel simpleOnOffUnit) {
237         if (simpleOnOffUnit != null) {
238             updateThingChannelState(CHANNEL_ON_OFF, OnOffType.from(simpleOnOffUnit.state));
239         }
240     }
241
242     private void updateSwitchableOutlet(@Nullable SwitchModel switchModel) {
243         if (switchModel != null) {
244             updateThingChannelState(CHANNEL_MODE, new StringType(switchModel.getMode()));
245             updateThingChannelState(CHANNEL_LOCKED,
246                     BigDecimal.ZERO.equals(switchModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
247             updateThingChannelState(CHANNEL_DEVICE_LOCKED,
248                     BigDecimal.ZERO.equals(switchModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
249             BigDecimal state = switchModel.getState();
250             if (state == null) {
251                 updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
252             } else {
253                 updateThingChannelState(CHANNEL_OUTLET, OnOffType.from(SwitchModel.ON.equals(state)));
254             }
255         }
256     }
257
258     private void updatePowermeter(@Nullable PowerMeterModel powerMeterModel) {
259         if (powerMeterModel != null) {
260             updateThingChannelState(CHANNEL_ENERGY, new QuantityType<>(powerMeterModel.getEnergy(), Units.WATT_HOUR));
261             updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), Units.WATT));
262             updateThingChannelState(CHANNEL_VOLTAGE, new QuantityType<>(powerMeterModel.getVoltage(), Units.VOLT));
263         }
264     }
265
266     /**
267      * Updates thing properties.
268      *
269      * @param device the {@link AVMFritzBaseModel}
270      * @param editProperties map of existing properties
271      */
272     protected void updateProperties(AVMFritzBaseModel device, Map<String, String> editProperties) {
273         editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
274         updateProperties(editProperties);
275     }
276
277     /**
278      * Updates thing channels and creates dynamic channels if missing.
279      *
280      * @param channelId ID of the channel to be updated.
281      * @param state State to be set.
282      */
283     protected void updateThingChannelState(String channelId, State state) {
284         Channel channel = thing.getChannel(channelId);
285         if (channel != null) {
286             updateState(channel.getUID(), state);
287         } else {
288             logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
289             createChannel(channelId);
290         }
291     }
292
293     /**
294      * Creates a {@link ChannelTypeUID} from the given channel id.
295      *
296      * @param channelId ID of the channel type UID to be created.
297      * @return the channel type UID
298      */
299     private ChannelTypeUID createChannelTypeUID(String channelId) {
300         int pos = channelId.indexOf(ChannelUID.CHANNEL_GROUP_SEPARATOR);
301         String id = pos > -1 ? channelId.substring(pos + 1) : channelId;
302         return CHANNEL_BATTERY.equals(id) ? DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID()
303                 : new ChannelTypeUID(BINDING_ID, id);
304     }
305
306     /**
307      * Creates new channels for the thing.
308      *
309      * @param channelId ID of the channel to be created.
310      */
311     private void createChannel(String channelId) {
312         ThingHandlerCallback callback = getCallback();
313         if (callback != null) {
314             ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
315             ChannelTypeUID channelTypeUID = createChannelTypeUID(channelId);
316             Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
317             updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
318         }
319     }
320
321     /**
322      * Updates thing channel configurations.
323      *
324      * @param channelId ID of the channel which configuration to be updated.
325      * @param configId ID of the configuration to be updated.
326      * @param value Value to be set.
327      */
328     protected void updateThingChannelConfiguration(String channelId, String configId, Object value) {
329         Channel channel = thing.getChannel(channelId);
330         if (channel != null) {
331             Configuration editConfig = channel.getConfiguration();
332             editConfig.put(configId, value);
333         }
334     }
335
336     @Override
337     public void onDeviceGone(ThingUID thingUID) {
338         if (thing.getUID().equals(thingUID)) {
339             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
340         }
341     }
342
343     @Override
344     public void handleCommand(ChannelUID channelUID, Command command) {
345         String channelId = channelUID.getIdWithoutGroup();
346         logger.debug("Handle command '{}' for channel {}", command, channelId);
347         if (command == RefreshType.REFRESH) {
348             handleRefreshCommand();
349             return;
350         }
351         FritzAhaWebInterface fritzBox = getWebInterface();
352         if (fritzBox == null) {
353             logger.debug("Cannot handle command '{}' because connection is missing", command);
354             return;
355         }
356         String ain = getIdentifier();
357         if (ain == null) {
358             logger.debug("Cannot handle command '{}' because AIN is missing", command);
359             return;
360         }
361         switch (channelId) {
362             case CHANNEL_MODE:
363             case CHANNEL_LOCKED:
364             case CHANNEL_DEVICE_LOCKED:
365             case CHANNEL_TEMPERATURE:
366             case CHANNEL_HUMIDITY:
367             case CHANNEL_ENERGY:
368             case CHANNEL_POWER:
369             case CHANNEL_VOLTAGE:
370             case CHANNEL_ACTUALTEMP:
371             case CHANNEL_ECOTEMP:
372             case CHANNEL_COMFORTTEMP:
373             case CHANNEL_NEXT_CHANGE:
374             case CHANNEL_NEXTTEMP:
375             case CHANNEL_BATTERY:
376             case CHANNEL_BATTERY_LOW:
377             case CHANNEL_CONTACT_STATE:
378             case CHANNEL_LAST_CHANGE:
379                 logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
380                 break;
381             case CHANNEL_OUTLET:
382             case CHANNEL_ON_OFF:
383                 if (command instanceof OnOffType) {
384                     fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
385                 }
386                 break;
387             case CHANNEL_SETTEMP:
388                 BigDecimal temperature = null;
389                 if (command instanceof DecimalType) {
390                     temperature = normalizeCelsius(((DecimalType) command).toBigDecimal());
391                 } else if (command instanceof QuantityType) {
392                     @SuppressWarnings("unchecked")
393                     QuantityType<Temperature> convertedCommand = ((QuantityType<Temperature>) command)
394                             .toUnit(SIUnits.CELSIUS);
395                     if (convertedCommand != null) {
396                         temperature = normalizeCelsius(convertedCommand.toBigDecimal());
397                     } else {
398                         logger.warn("Unable to convert unit from '{}' to '{}'. Skipping command.",
399                                 ((QuantityType<?>) command).getUnit(), SIUnits.CELSIUS);
400                     }
401                 } else if (command instanceof IncreaseDecreaseType) {
402                     temperature = state.getHkr().getTsoll();
403                     if (IncreaseDecreaseType.INCREASE.equals(command)) {
404                         temperature.add(BigDecimal.ONE);
405                     } else {
406                         temperature.subtract(BigDecimal.ONE);
407                     }
408                 } else if (command instanceof OnOffType) {
409                     temperature = OnOffType.ON.equals(command) ? TEMP_FRITZ_ON : TEMP_FRITZ_OFF;
410                 }
411                 if (temperature != null) {
412                     fritzBox.setSetTemp(ain, fromCelsius(temperature));
413                     HeatingModel heatingModel = state.getHkr();
414                     heatingModel.setTsoll(temperature);
415                     updateState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
416                 }
417                 break;
418             case CHANNEL_RADIATOR_MODE:
419                 BigDecimal targetTemperature = null;
420                 if (command instanceof StringType) {
421                     switch (command.toString()) {
422                         case MODE_ON:
423                             targetTemperature = TEMP_FRITZ_ON;
424                             break;
425                         case MODE_OFF:
426                             targetTemperature = TEMP_FRITZ_OFF;
427                             break;
428                         case MODE_COMFORT:
429                             targetTemperature = state.getHkr().getKomfort();
430                             break;
431                         case MODE_ECO:
432                             targetTemperature = state.getHkr().getAbsenk();
433                             break;
434                         case MODE_BOOST:
435                             targetTemperature = TEMP_FRITZ_MAX;
436                             break;
437                         case MODE_UNKNOWN:
438                         case MODE_WINDOW_OPEN:
439                             logger.debug("Command '{}' is a read-only command for channel {}.", command, channelId);
440                             break;
441                     }
442                     if (targetTemperature != null) {
443                         fritzBox.setSetTemp(ain, targetTemperature);
444                         state.getHkr().setTsoll(targetTemperature);
445                         updateState(CHANNEL_SETTEMP, new QuantityType<>(toCelsius(targetTemperature), SIUnits.CELSIUS));
446                     }
447                 }
448                 break;
449             case CHANNEL_ROLLERSHUTTER:
450                 if (command instanceof StopMoveType) {
451                     StopMoveType rollershutterCommand = (StopMoveType) command;
452                     if (StopMoveType.STOP.equals(rollershutterCommand)) {
453                         fritzBox.setBlind(ain, BlindCommand.STOP);
454                     } else {
455                         logger.debug("Received unknown rollershutter StopMove command MOVE");
456                     }
457                 } else if (command instanceof UpDownType) {
458                     UpDownType rollershutterCommand = (UpDownType) command;
459                     if (UpDownType.UP.equals(rollershutterCommand)) {
460                         fritzBox.setBlind(ain, BlindCommand.OPEN);
461                     } else {
462                         fritzBox.setBlind(ain, BlindCommand.CLOSE);
463                     }
464                 } else if (command instanceof PercentType) {
465                     PercentType rollershutterCommand = (PercentType) command;
466                     BigDecimal levelpercentage = rollershutterCommand.toBigDecimal();
467                     fritzBox.setLevelpercentage(ain, levelpercentage);
468                 } else {
469                     logger.debug("Received unknown rollershutter command type '{}'", command.toString());
470                 }
471                 break;
472             default:
473                 logger.debug("Received unknown channel {}", channelId);
474                 break;
475         }
476     }
477
478     /**
479      * Handles a command for a given action.
480      *
481      * @param action
482      * @param duration
483      */
484     protected void handleAction(String action, long duration) {
485         FritzAhaWebInterface fritzBox = getWebInterface();
486         if (fritzBox == null) {
487             logger.debug("Cannot handle action '{}' because connection is missing", action);
488             return;
489         }
490         String ain = getIdentifier();
491         if (ain == null) {
492             logger.debug("Cannot handle action '{}' because AIN is missing", action);
493             return;
494         }
495         if (duration < 0 || 86400 < duration) {
496             throw new IllegalArgumentException("Duration must not be less than zero or greater than 86400");
497         }
498         switch (action) {
499             case MODE_BOOST:
500                 fritzBox.setBoostMode(ain,
501                         duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
502                 break;
503             case MODE_WINDOW_OPEN:
504                 fritzBox.setWindowOpenMode(ain,
505                         duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
506                 break;
507             default:
508                 logger.debug("Received unknown action '{}'", action);
509                 break;
510         }
511     }
512
513     /**
514      * Provides the web interface object.
515      *
516      * @return The web interface object
517      */
518     private @Nullable FritzAhaWebInterface getWebInterface() {
519         Bridge bridge = getBridge();
520         if (bridge != null) {
521             BridgeHandler handler = bridge.getHandler();
522             if (handler instanceof AVMFritzBaseBridgeHandler) {
523                 return ((AVMFritzBaseBridgeHandler) handler).getWebInterface();
524             }
525         }
526         return null;
527     }
528
529     /**
530      * Handles a refresh command.
531      */
532     private void handleRefreshCommand() {
533         Bridge bridge = getBridge();
534         if (bridge != null) {
535             BridgeHandler handler = bridge.getHandler();
536             if (handler instanceof AVMFritzBaseBridgeHandler) {
537                 ((AVMFritzBaseBridgeHandler) handler).handleRefreshCommand();
538             }
539         }
540     }
541
542     /**
543      * Returns the AIN.
544      *
545      * @return the AIN
546      */
547     public @Nullable String getIdentifier() {
548         return identifier;
549     }
550 }