]> git.basschouten.com Git - openhab-addons.git/blob
089ee9b8123fa59a56d5510e984ff0c4b02d8a86
[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.SmartHomeUnits;
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,
163                     new QuantityType<>(powerMeterModel.getEnergy(), SmartHomeUnits.WATT_HOUR));
164             updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), SmartHomeUnits.WATT));
165             updateThingChannelState(CHANNEL_VOLTAGE,
166                     new QuantityType<>(powerMeterModel.getVoltage(), SmartHomeUnits.VOLT));
167         }
168     }
169
170     /**
171      * Updates thing properties.
172      *
173      * @param device the {@link AVMFritzBaseModel}
174      */
175     private void updateProperties(AVMFritzBaseModel device) {
176         Map<String, String> editProperties = editProperties();
177         editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
178         updateProperties(editProperties);
179     }
180
181     /**
182      * Updates thing channels and creates dynamic channels if missing.
183      *
184      * @param channelId ID of the channel to be updated.
185      * @param state State to be set.
186      */
187     private void updateThingChannelState(String channelId, State state) {
188         Channel channel = thing.getChannel(channelId);
189         if (channel != null) {
190             updateState(channel.getUID(), state);
191         } else {
192             logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
193             createChannel(channelId);
194         }
195     }
196
197     /**
198      * Creates new channels for the thing.
199      *
200      * @param channelId ID of the channel to be created.
201      */
202     private void createChannel(String channelId) {
203         ThingHandlerCallback callback = getCallback();
204         if (callback != null) {
205             ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
206             ChannelTypeUID channelTypeUID = CHANNEL_BATTERY.equals(channelId)
207                     ? DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID()
208                     : new ChannelTypeUID(BINDING_ID, channelId);
209             Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
210             updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
211         }
212     }
213
214     @Override
215     public void onDeviceGone(ThingUID thingUID) {
216         if (thing.getUID().equals(thingUID)) {
217             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
218         }
219     }
220
221     /**
222      * Builds a {@link ThingUID} from a device model. The UID is build from the
223      * {@link AVMFritzBindingConstants#BINDING_ID} and value of
224      * {@link AVMFritzBaseModel#getProductName()} in which all characters NOT matching
225      * the regex [^a-zA-Z0-9_] are replaced by "_".
226      *
227      * @param device Discovered device model
228      * @return ThingUID without illegal characters.
229      */
230     @Override
231     public @Nullable ThingUID getThingUID(AVMFritzBaseModel device) {
232         ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, getThingTypeId(device).concat("_Solo"));
233         String ipAddress = getConfigAs(AVMFritzBoxConfiguration.class).ipAddress;
234
235         if (PL546E_STANDALONE_THING_TYPE.equals(thingTypeUID)) {
236             String thingName = "fritz.powerline".equals(ipAddress) ? ipAddress
237                     : ipAddress.replaceAll(INVALID_PATTERN, "_");
238             return new ThingUID(thingTypeUID, thingName);
239         } else {
240             return super.getThingUID(device);
241         }
242     }
243
244     @Override
245     public void handleCommand(ChannelUID channelUID, Command command) {
246         String channelId = channelUID.getIdWithoutGroup();
247         logger.debug("Handle command '{}' for channel {}", command, channelId);
248         if (command == RefreshType.REFRESH) {
249             handleRefreshCommand();
250             return;
251         }
252         FritzAhaWebInterface fritzBox = getWebInterface();
253         if (fritzBox == null) {
254             logger.debug("Cannot handle command '{}' because connection is missing", command);
255             return;
256         }
257         String ain = getIdentifier();
258         if (ain == null) {
259             logger.debug("Cannot handle command '{}' because AIN is missing", command);
260             return;
261         }
262         switch (channelId) {
263             case CHANNEL_MODE:
264             case CHANNEL_LOCKED:
265             case CHANNEL_DEVICE_LOCKED:
266             case CHANNEL_ENERGY:
267             case CHANNEL_POWER:
268             case CHANNEL_VOLTAGE:
269                 logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
270                 break;
271             case CHANNEL_APPLY_TEMPLATE:
272                 if (command instanceof StringType) {
273                     fritzBox.applyTemplate(command.toString());
274                 }
275                 break;
276             case CHANNEL_OUTLET:
277                 fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
278                 if (command instanceof OnOffType) {
279                     if (state != null) {
280                         state.getSwitch().setState(OnOffType.ON.equals(command) ? SwitchModel.ON : SwitchModel.OFF);
281                     }
282                 }
283                 break;
284             default:
285                 super.handleCommand(channelUID, command);
286                 break;
287         }
288     }
289
290     /**
291      * Returns the AIN.
292      *
293      * @return the AIN
294      */
295     public @Nullable String getIdentifier() {
296         return identifier;
297     }
298 }