]> git.basschouten.com Git - openhab-addons.git/blob
6d4a305e8958522726af39f8568d625d51ff9850
[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.Optional;
21 import java.util.function.Predicate;
22
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;
58
59 /**
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.
62  *
63  * @author Robert Bausdorf - Initial contribution
64  * @author Christoph Weitkamp - Added support for groups
65  */
66 @NonNullByDefault
67 public class Powerline546EHandler extends AVMFritzBaseBridgeHandler implements FritzAhaStatusListener {
68
69     private final Logger logger = LoggerFactory.getLogger(Powerline546EHandler.class);
70
71     /**
72      * keeps track of the current state for handling of increase/decrease
73      */
74     private @Nullable AVMFritzBaseModel state;
75     private @Nullable AVMFritzDeviceConfiguration config;
76
77     /**
78      * Constructor
79      *
80      * @param bridge Bridge object representing a FRITZ!Powerline 546E
81      */
82     public Powerline546EHandler(Bridge bridge, HttpClient httpClient,
83             AVMFritzDynamicCommandDescriptionProvider commandDescriptionProvider) {
84         super(bridge, httpClient, commandDescriptionProvider);
85     }
86
87     @Override
88     public void initialize() {
89         config = getConfigAs(AVMFritzDeviceConfiguration.class);
90
91         registerStatusListener(this);
92
93         super.initialize();
94     }
95
96     @Override
97     public void dispose() {
98         unregisterStatusListener(this);
99
100         super.dispose();
101     }
102
103     @Override
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));
113         } else {
114             listeners.stream().forEach(listener -> listener.onDeviceGone(thing.getUID()));
115         }
116         super.onDeviceListAdded(devicelist);
117     }
118
119     @Override
120     public void onDeviceAdded(AVMFritzBaseModel device) {
121         // nothing to do
122     }
123
124     @Override
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);
130             }
131
132             logger.debug("Update self '{}' with device model: {}", thingUID, device);
133             if (device.getPresent() == 1) {
134                 updateStatus(ThingStatus.ONLINE);
135             } else {
136                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device not present");
137             }
138             state = device;
139
140             updateProperties(device);
141
142             if (device.isPowermeter()) {
143                 updatePowermeter(device.getPowermeter());
144             }
145             if (device.isSwitchableOutlet()) {
146                 updateSwitchableOutlet(device.getSwitch());
147             }
148         }
149     }
150
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();
159             if (state == null) {
160                 updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
161             } else {
162                 updateThingChannelState(CHANNEL_OUTLET, SwitchModel.ON.equals(state) ? OnOffType.ON : OnOffType.OFF);
163             }
164         }
165     }
166
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));
174         }
175     }
176
177     /**
178      * Updates thing properties.
179      *
180      * @param device the {@link AVMFritzBaseModel}
181      */
182     private void updateProperties(AVMFritzBaseModel device) {
183         Map<String, String> editProperties = editProperties();
184         editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
185         updateProperties(editProperties);
186     }
187
188     /**
189      * Updates thing configuration.
190      *
191      * @param device the {@link AVMFritzBaseModel}
192      */
193     private void updateConfiguration(AVMFritzBaseModel device) {
194         Configuration editConfig = editConfiguration();
195         editConfig.put(CONFIG_AIN, device.getIdentifier());
196         updateConfiguration(editConfig);
197     }
198
199     /**
200      * Updates thing channels and creates dynamic channels if missing.
201      *
202      * @param channelId ID of the channel to be updated.
203      * @param state State to be set.
204      */
205     private void updateThingChannelState(String channelId, State state) {
206         Channel channel = thing.getChannel(channelId);
207         if (channel != null) {
208             updateState(channel.getUID(), state);
209         } else {
210             logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
211             createChannel(channelId);
212         }
213     }
214
215     /**
216      * Creates new channels for the thing.
217      *
218      * @param channelId ID of the channel to be created.
219      */
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());
229         }
230     }
231
232     @Override
233     public void onDeviceGone(ThingUID thingUID) {
234         if (thing.getUID().equals(thingUID)) {
235             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
236         }
237     }
238
239     /**
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 "_".
244      *
245      * @param device Discovered device model
246      * @return ThingUID without illegal characters.
247      */
248     @Override
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;
252
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);
257         } else {
258             return super.getThingUID(device);
259         }
260     }
261
262     @Override
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();
268             return;
269         }
270         FritzAhaWebInterface fritzBox = getWebInterface();
271         if (fritzBox == null) {
272             logger.debug("Cannot handle command '{}' because connection is missing", command);
273             return;
274         }
275         String ain = getIdentifier();
276         if (ain == null) {
277             logger.debug("Cannot handle command '{}' because AIN is missing", command);
278             return;
279         }
280         switch (channelId) {
281             case CHANNEL_MODE:
282             case CHANNEL_LOCKED:
283             case CHANNEL_DEVICE_LOCKED:
284             case CHANNEL_ENERGY:
285             case CHANNEL_POWER:
286             case CHANNEL_VOLTAGE:
287                 logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
288                 break;
289             case CHANNEL_APPLY_TEMPLATE:
290                 if (command instanceof StringType) {
291                     fritzBox.applyTemplate(command.toString());
292                 }
293                 break;
294             case CHANNEL_OUTLET:
295                 fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
296                 if (command instanceof OnOffType) {
297                     if (state != null) {
298                         state.getSwitch().setState(OnOffType.ON.equals(command) ? SwitchModel.ON : SwitchModel.OFF);
299                     }
300                 }
301                 break;
302             default:
303                 super.handleCommand(channelUID, command);
304                 break;
305         }
306     }
307
308     /**
309      * Returns the AIN.
310      *
311      * @return the AIN
312      */
313     public @Nullable String getIdentifier() {
314         AVMFritzDeviceConfiguration localConfig = config;
315         return localConfig != null ? localConfig.ain : null;
316     }
317 }