2 * Copyright (c) 2010-2020 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.*;
17 import java.math.BigDecimal;
18 import java.util.List;
20 import java.util.Optional;
21 import java.util.function.Predicate;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
27 import org.openhab.binding.avmfritz.internal.AVMFritzDynamicCommandDescriptionProvider;
28 import org.openhab.binding.avmfritz.internal.config.AVMFritzBoxConfiguration;
29 import org.openhab.binding.avmfritz.internal.config.AVMFritzDeviceConfiguration;
30 import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
31 import org.openhab.binding.avmfritz.internal.dto.PowerMeterModel;
32 import org.openhab.binding.avmfritz.internal.dto.SwitchModel;
33 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
34 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
35 import org.openhab.core.config.core.Configuration;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.OpenClosedType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.SmartHomeUnits;
41 import org.openhab.core.thing.Bridge;
42 import org.openhab.core.thing.Channel;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.ThingUID;
50 import org.openhab.core.thing.binding.ThingHandlerCallback;
51 import org.openhab.core.thing.type.ChannelTypeUID;
52 import org.openhab.core.types.Command;
53 import org.openhab.core.types.RefreshType;
54 import org.openhab.core.types.State;
55 import org.openhab.core.types.UnDefType;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
60 * Handler for a FRITZ!Powerline 546E device. Handles polling of values from AHA devices and commands, which are sent to
61 * one of the channels.
63 * @author Robert Bausdorf - Initial contribution
64 * @author Christoph Weitkamp - Added support for groups
67 public class Powerline546EHandler extends AVMFritzBaseBridgeHandler implements FritzAhaStatusListener {
69 private final Logger logger = LoggerFactory.getLogger(Powerline546EHandler.class);
72 * keeps track of the current state for handling of increase/decrease
74 private @Nullable AVMFritzBaseModel state;
75 private @Nullable AVMFritzDeviceConfiguration config;
80 * @param bridge Bridge object representing a FRITZ!Powerline 546E
82 public Powerline546EHandler(Bridge bridge, HttpClient httpClient,
83 AVMFritzDynamicCommandDescriptionProvider commandDescriptionProvider) {
84 super(bridge, httpClient, commandDescriptionProvider);
88 public void initialize() {
89 config = getConfigAs(AVMFritzDeviceConfiguration.class);
91 registerStatusListener(this);
97 public void dispose() {
98 unregisterStatusListener(this);
104 public void onDeviceListAdded(List<AVMFritzBaseModel> devicelist) {
105 final String identifier = getIdentifier();
106 final Predicate<AVMFritzBaseModel> predicate = identifier == null ? it -> thing.getUID().equals(getThingUID(it))
107 : it -> identifier.equals(it.getIdentifier());
108 final Optional<AVMFritzBaseModel> optionalDevice = devicelist.stream().filter(predicate).findFirst();
109 if (optionalDevice.isPresent()) {
110 final AVMFritzBaseModel device = optionalDevice.get();
111 devicelist.remove(device);
112 listeners.stream().forEach(listener -> listener.onDeviceUpdated(thing.getUID(), device));
114 listeners.stream().forEach(listener -> listener.onDeviceGone(thing.getUID()));
116 super.onDeviceListAdded(devicelist);
120 public void onDeviceAdded(AVMFritzBaseModel device) {
125 public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
126 if (thing.getUID().equals(thingUID)) {
127 // save AIN to config for FRITZ!Powerline 546E stand-alone
128 if (config == null) {
129 updateConfiguration(device);
132 logger.debug("Update self '{}' with device model: {}", thingUID, device);
133 if (device.getPresent() == 1) {
134 updateStatus(ThingStatus.ONLINE);
136 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device not present");
140 updateProperties(device);
142 if (device.isPowermeter()) {
143 updatePowermeter(device.getPowermeter());
145 if (device.isSwitchableOutlet()) {
146 updateSwitchableOutlet(device.getSwitch());
151 private void updateSwitchableOutlet(@Nullable SwitchModel switchModel) {
152 if (switchModel != null) {
153 updateThingChannelState(CHANNEL_MODE, new StringType(switchModel.getMode()));
154 updateThingChannelState(CHANNEL_LOCKED,
155 BigDecimal.ZERO.equals(switchModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
156 updateThingChannelState(CHANNEL_DEVICE_LOCKED,
157 BigDecimal.ZERO.equals(switchModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
158 BigDecimal state = switchModel.getState();
160 updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
162 updateThingChannelState(CHANNEL_OUTLET, SwitchModel.ON.equals(state) ? OnOffType.ON : OnOffType.OFF);
167 private void updatePowermeter(@Nullable PowerMeterModel powerMeterModel) {
168 if (powerMeterModel != null) {
169 updateThingChannelState(CHANNEL_ENERGY,
170 new QuantityType<>(powerMeterModel.getEnergy(), SmartHomeUnits.WATT_HOUR));
171 updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), SmartHomeUnits.WATT));
172 updateThingChannelState(CHANNEL_VOLTAGE,
173 new QuantityType<>(powerMeterModel.getVoltage(), SmartHomeUnits.VOLT));
178 * Updates thing properties.
180 * @param device the {@link AVMFritzBaseModel}
182 private void updateProperties(AVMFritzBaseModel device) {
183 Map<String, String> editProperties = editProperties();
184 editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
185 updateProperties(editProperties);
189 * Updates thing configuration.
191 * @param device the {@link AVMFritzBaseModel}
193 private void updateConfiguration(AVMFritzBaseModel device) {
194 Configuration editConfig = editConfiguration();
195 editConfig.put(CONFIG_AIN, device.getIdentifier());
196 updateConfiguration(editConfig);
200 * Updates thing channels and creates dynamic channels if missing.
202 * @param channelId ID of the channel to be updated.
203 * @param state State to be set.
205 private void updateThingChannelState(String channelId, State state) {
206 Channel channel = thing.getChannel(channelId);
207 if (channel != null) {
208 updateState(channel.getUID(), state);
210 logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
211 createChannel(channelId);
216 * Creates new channels for the thing.
218 * @param channelId ID of the channel to be created.
220 private void createChannel(String channelId) {
221 ThingHandlerCallback callback = getCallback();
222 if (callback != null) {
223 ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
224 ChannelTypeUID channelTypeUID = CHANNEL_BATTERY.equals(channelId)
225 ? DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID()
226 : new ChannelTypeUID(BINDING_ID, channelId);
227 Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
228 updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
233 public void onDeviceGone(ThingUID thingUID) {
234 if (thing.getUID().equals(thingUID)) {
235 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
240 * Builds a {@link ThingUID} from a device model. The UID is build from the
241 * {@link AVMFritzBindingConstants#BINDING_ID} and value of
242 * {@link AVMFritzBaseModel#getProductName()} in which all characters NOT matching
243 * the regex [^a-zA-Z0-9_] are replaced by "_".
245 * @param device Discovered device model
246 * @return ThingUID without illegal characters.
249 public @Nullable ThingUID getThingUID(AVMFritzBaseModel device) {
250 ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, getThingTypeId(device).concat("_Solo"));
251 String ipAddress = getConfigAs(AVMFritzBoxConfiguration.class).ipAddress;
253 if (PL546E_STANDALONE_THING_TYPE.equals(thingTypeUID)) {
254 String thingName = "fritz.powerline".equals(ipAddress) ? ipAddress
255 : ipAddress.replaceAll(INVALID_PATTERN, "_");
256 return new ThingUID(thingTypeUID, thingName);
258 return super.getThingUID(device);
263 public void handleCommand(ChannelUID channelUID, Command command) {
264 String channelId = channelUID.getIdWithoutGroup();
265 logger.debug("Handle command '{}' for channel {}", command, channelId);
266 if (command == RefreshType.REFRESH) {
267 handleRefreshCommand();
270 FritzAhaWebInterface fritzBox = getWebInterface();
271 if (fritzBox == null) {
272 logger.debug("Cannot handle command '{}' because connection is missing", command);
275 String ain = getIdentifier();
277 logger.debug("Cannot handle command '{}' because AIN is missing", command);
283 case CHANNEL_DEVICE_LOCKED:
286 case CHANNEL_VOLTAGE:
287 logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
289 case CHANNEL_APPLY_TEMPLATE:
290 if (command instanceof StringType) {
291 fritzBox.applyTemplate(command.toString());
295 fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
296 if (command instanceof OnOffType) {
298 state.getSwitch().setState(OnOffType.ON.equals(command) ? SwitchModel.ON : SwitchModel.OFF);
303 super.handleCommand(channelUID, command);
313 public @Nullable String getIdentifier() {
314 AVMFritzDeviceConfiguration localConfig = config;
315 return localConfig != null ? localConfig.ain : null;