]> git.basschouten.com Git - openhab-addons.git/blob
3be58560c8343b9977cc91edb94e487e98c78dae
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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
17 import java.math.BigDecimal;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.function.Predicate;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants;
26 import org.openhab.binding.avmfritz.internal.AVMFritzDynamicCommandDescriptionProvider;
27 import org.openhab.binding.avmfritz.internal.config.AVMFritzBoxConfiguration;
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.PowerMeterModel;
31 import org.openhab.binding.avmfritz.internal.dto.SwitchModel;
32 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
33 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.OpenClosedType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.library.unit.Units;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.Channel;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.ThingTypeUID;
47 import org.openhab.core.thing.ThingUID;
48 import org.openhab.core.thing.binding.ThingHandlerCallback;
49 import org.openhab.core.thing.type.ChannelTypeUID;
50 import org.openhab.core.types.Command;
51 import org.openhab.core.types.RefreshType;
52 import org.openhab.core.types.State;
53 import org.openhab.core.types.UnDefType;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * Handler for a FRITZ!Powerline 546E device. Handles polling of values from AHA devices and commands, which are sent to
59  * one of the channels.
60  *
61  * @author Robert Bausdorf - Initial contribution
62  * @author Christoph Weitkamp - Added support for groups
63  */
64 @NonNullByDefault
65 public class Powerline546EHandler extends AVMFritzBaseBridgeHandler implements FritzAhaStatusListener {
66
67     private final Logger logger = LoggerFactory.getLogger(Powerline546EHandler.class);
68
69     /**
70      * keeps track of the current state for handling of increase/decrease
71      */
72     private @Nullable AVMFritzBaseModel state;
73     private @Nullable String identifier;
74
75     /**
76      * Constructor
77      *
78      * @param bridge Bridge object representing a FRITZ!Powerline 546E
79      */
80     public Powerline546EHandler(Bridge bridge, HttpClient httpClient,
81             AVMFritzDynamicCommandDescriptionProvider commandDescriptionProvider) {
82         super(bridge, httpClient, commandDescriptionProvider);
83     }
84
85     @Override
86     public void initialize() {
87         final AVMFritzDeviceConfiguration config = getConfigAs(AVMFritzDeviceConfiguration.class);
88         final String newIdentifier = config.ain;
89         if (newIdentifier != null && !newIdentifier.isBlank()) {
90             this.identifier = newIdentifier;
91         }
92
93         super.initialize();
94     }
95
96     @SuppressWarnings({ "null", "unused" })
97     @Override
98     public void onDeviceListAdded(List<AVMFritzBaseModel> devicelist) {
99         final String ain = getIdentifier();
100         final Predicate<AVMFritzBaseModel> predicate = ain == null ? it -> thing.getUID().equals(getThingUID(it))
101                 : it -> ain.equals(it.getIdentifier());
102         final AVMFritzBaseModel device = devicelist.stream().filter(predicate).findFirst().orElse(null);
103         if (device != null) {
104             devicelist.remove(device);
105             onDeviceUpdated(thing.getUID(), device);
106         } else {
107             onDeviceGone(thing.getUID());
108         }
109         super.onDeviceListAdded(devicelist);
110     }
111
112     @Override
113     public void onDeviceAdded(AVMFritzBaseModel device) {
114         // nothing to do
115     }
116
117     @Override
118     public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
119         if (thing.getUID().equals(thingUID)) {
120             // save AIN to config for FRITZ!Powerline 546E stand-alone
121             if (this.identifier == null) {
122                 this.identifier = device.getIdentifier();
123             }
124
125             logger.debug("Update self '{}' 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);
134
135             if (device.isPowermeter()) {
136                 updatePowermeter(device.getPowermeter());
137             }
138             if (device.isSwitchableOutlet()) {
139                 updateSwitchableOutlet(device.getSwitch());
140             }
141         }
142     }
143
144     private void updateSwitchableOutlet(@Nullable SwitchModel switchModel) {
145         if (switchModel != null) {
146             updateThingChannelState(CHANNEL_MODE, new StringType(switchModel.getMode()));
147             updateThingChannelState(CHANNEL_LOCKED,
148                     BigDecimal.ZERO.equals(switchModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
149             updateThingChannelState(CHANNEL_DEVICE_LOCKED,
150                     BigDecimal.ZERO.equals(switchModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
151             BigDecimal state = switchModel.getState();
152             if (state == null) {
153                 updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
154             } else {
155                 updateThingChannelState(CHANNEL_OUTLET, SwitchModel.ON.equals(state) ? OnOffType.ON : OnOffType.OFF);
156             }
157         }
158     }
159
160     private void updatePowermeter(@Nullable PowerMeterModel powerMeterModel) {
161         if (powerMeterModel != null) {
162             updateThingChannelState(CHANNEL_ENERGY, new QuantityType<>(powerMeterModel.getEnergy(), Units.WATT_HOUR));
163             updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), Units.WATT));
164             updateThingChannelState(CHANNEL_VOLTAGE, new QuantityType<>(powerMeterModel.getVoltage(), Units.VOLT));
165         }
166     }
167
168     /**
169      * Updates thing properties.
170      *
171      * @param device the {@link AVMFritzBaseModel}
172      */
173     private void updateProperties(AVMFritzBaseModel device) {
174         Map<String, String> editProperties = editProperties();
175         editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
176         updateProperties(editProperties);
177     }
178
179     /**
180      * Updates thing channels and creates dynamic channels if missing.
181      *
182      * @param channelId ID of the channel to be updated.
183      * @param state State to be set.
184      */
185     private void updateThingChannelState(String channelId, State state) {
186         Channel channel = thing.getChannel(channelId);
187         if (channel != null) {
188             updateState(channel.getUID(), state);
189         } else {
190             logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
191             createChannel(channelId);
192         }
193     }
194
195     /**
196      * Creates new channels for the thing.
197      *
198      * @param channelId ID of the channel to be created.
199      */
200     private void createChannel(String channelId) {
201         ThingHandlerCallback callback = getCallback();
202         if (callback != null) {
203             ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
204             ChannelTypeUID channelTypeUID = CHANNEL_BATTERY.equals(channelId)
205                     ? DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID()
206                     : new ChannelTypeUID(BINDING_ID, channelId);
207             Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
208             updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
209         }
210     }
211
212     @Override
213     public void onDeviceGone(ThingUID thingUID) {
214         if (thing.getUID().equals(thingUID)) {
215             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
216         }
217     }
218
219     /**
220      * Builds a {@link ThingUID} from a device model. The UID is build from the
221      * {@link AVMFritzBindingConstants#BINDING_ID} and value of
222      * {@link AVMFritzBaseModel#getProductName()} in which all characters NOT matching
223      * the regex [^a-zA-Z0-9_] are replaced by "_".
224      *
225      * @param device Discovered device model
226      * @return ThingUID without illegal characters.
227      */
228     @Override
229     public @Nullable ThingUID getThingUID(AVMFritzBaseModel device) {
230         ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, getThingTypeId(device).concat("_Solo"));
231         String ipAddress = getConfigAs(AVMFritzBoxConfiguration.class).ipAddress;
232
233         if (PL546E_STANDALONE_THING_TYPE.equals(thingTypeUID)) {
234             String thingName = "fritz.powerline".equals(ipAddress) ? ipAddress
235                     : ipAddress.replaceAll(INVALID_PATTERN, "_");
236             return new ThingUID(thingTypeUID, thingName);
237         } else {
238             return super.getThingUID(device);
239         }
240     }
241
242     @Override
243     public void handleCommand(ChannelUID channelUID, Command command) {
244         String channelId = channelUID.getIdWithoutGroup();
245         logger.debug("Handle command '{}' for channel {}", command, channelId);
246         if (command == RefreshType.REFRESH) {
247             handleRefreshCommand();
248             return;
249         }
250         FritzAhaWebInterface fritzBox = getWebInterface();
251         if (fritzBox == null) {
252             logger.debug("Cannot handle command '{}' because connection is missing", command);
253             return;
254         }
255         String ain = getIdentifier();
256         if (ain == null) {
257             logger.debug("Cannot handle command '{}' because AIN is missing", command);
258             return;
259         }
260         switch (channelId) {
261             case CHANNEL_MODE:
262             case CHANNEL_LOCKED:
263             case CHANNEL_DEVICE_LOCKED:
264             case CHANNEL_ENERGY:
265             case CHANNEL_POWER:
266             case CHANNEL_VOLTAGE:
267                 logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
268                 break;
269             case CHANNEL_APPLY_TEMPLATE:
270                 if (command instanceof StringType) {
271                     fritzBox.applyTemplate(command.toString());
272                 }
273                 break;
274             case CHANNEL_OUTLET:
275                 fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
276                 if (command instanceof OnOffType) {
277                     if (state != null) {
278                         state.getSwitch().setState(OnOffType.ON.equals(command) ? SwitchModel.ON : SwitchModel.OFF);
279                     }
280                 }
281                 break;
282             default:
283                 super.handleCommand(channelUID, command);
284                 break;
285         }
286     }
287
288     /**
289      * Returns the AIN.
290      *
291      * @return the AIN
292      */
293     public @Nullable String getIdentifier() {
294         return identifier;
295     }
296 }