]> git.basschouten.com Git - openhab-addons.git/blob
590769153791d619303f73610ff121a0db5fef4e
[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.enocean.internal.handler;
14
15 import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
16
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.Set;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
24
25 import org.openhab.binding.enocean.internal.config.EnOceanActuatorConfig;
26 import org.openhab.binding.enocean.internal.eep.EEP;
27 import org.openhab.binding.enocean.internal.eep.EEPFactory;
28 import org.openhab.binding.enocean.internal.eep.EEPType;
29 import org.openhab.binding.enocean.internal.messages.BasePacket;
30 import org.openhab.core.config.core.Configuration;
31 import org.openhab.core.thing.Channel;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingTypeUID;
36 import org.openhab.core.thing.link.ItemChannelLinkRegistry;
37 import org.openhab.core.thing.type.ChannelTypeUID;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.util.HexUtils;
41
42 /**
43  *
44  * @author Daniel Weber - Initial contribution
45  *         This class defines base functionality for sending eep messages. This class extends EnOceanBaseSensorHandler
46  *         class as most actuator things send status or response messages, too.
47  */
48 public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
49
50     // List of thing types which support sending of eep messages
51     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_CENTRALCOMMAND,
52             THING_TYPE_MEASUREMENTSWITCH, THING_TYPE_GENERICTHING, THING_TYPE_ROLLERSHUTTER, THING_TYPE_THERMOSTAT,
53             THING_TYPE_HEATRECOVERYVENTILATION);
54
55     protected byte[] senderId; // base id of bridge + senderIdOffset, used for sending msg
56     protected byte[] destinationId; // in case of broadcast FFFFFFFF otherwise the enocean id of the device
57
58     protected EEPType sendingEEPType = null;
59
60     private ScheduledFuture<?> refreshJob; // used for polling current status of thing
61
62     public EnOceanBaseActuatorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
63         super(thing, itemChannelLinkRegistry);
64     }
65
66     /**
67      *
68      * @param senderIdOffset to be validated
69      * @return true if senderIdOffset is between ]0;128[ and is not used yet
70      */
71     private boolean validateSenderIdOffset(int senderIdOffset) {
72         if (senderIdOffset == -1) {
73             return true;
74         }
75
76         if (senderIdOffset > 0 && senderIdOffset < 128) {
77             EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
78             if (bridgeHandler != null) {
79                 return !bridgeHandler.existsSender(senderIdOffset, this.thing);
80             }
81         }
82
83         return false;
84     }
85
86     @Override
87     void initializeConfig() {
88         config = getConfigAs(EnOceanActuatorConfig.class);
89     }
90
91     protected EnOceanActuatorConfig getConfiguration() {
92         return (EnOceanActuatorConfig) config;
93     }
94
95     @Override
96     Collection<EEPType> getEEPTypes() {
97         Collection<EEPType> r = super.getEEPTypes();
98         if (sendingEEPType == null) {
99             return r;
100         }
101
102         return Collections.unmodifiableCollection(Stream
103                 .concat(r.stream(), Collections.singletonList(sendingEEPType).stream()).collect(Collectors.toList()));
104     }
105
106     @Override
107     boolean validateConfig() {
108         EnOceanActuatorConfig config = getConfiguration();
109         if (config == null) {
110             configurationErrorDescription = "Configuration is not valid";
111             return false;
112         }
113
114         if (config.sendingEEPId == null || config.sendingEEPId.isEmpty()) {
115             configurationErrorDescription = "Sending EEP must be provided";
116             return false;
117         }
118
119         try {
120             sendingEEPType = EEPType.getType(getConfiguration().sendingEEPId);
121         } catch (IllegalArgumentException e) {
122             configurationErrorDescription = "Sending EEP is not supported";
123             return false;
124         }
125
126         if (super.validateConfig()) {
127             try {
128                 if (sendingEEPType.getSupportsRefresh()) {
129                     if (getConfiguration().pollingInterval > 0) {
130                         refreshJob = scheduler.scheduleWithFixedDelay(() -> {
131                             try {
132                                 refreshStates();
133                             } catch (Exception e) {
134                             }
135                         }, 30, getConfiguration().pollingInterval, TimeUnit.SECONDS);
136                     }
137                 }
138
139                 if (getConfiguration().broadcastMessages) {
140                     destinationId = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
141                 } else {
142                     destinationId = HexUtils.hexToBytes(config.enoceanId);
143                 }
144             } catch (Exception e) {
145                 configurationErrorDescription = "Configuration is not valid";
146                 return false;
147             }
148
149             if (validateSenderIdOffset(getConfiguration().senderIdOffset)) {
150                 return initializeIdForSending();
151             } else {
152                 configurationErrorDescription = "Sender Id is not valid for bridge";
153             }
154         }
155
156         return false;
157     }
158
159     private boolean initializeIdForSending() {
160         // Generic things are treated as actuator things, however to support also generic sensors one can define a
161         // senderIdOffset of -1
162         // TODO: seperate generic actuators from generic sensors?
163         String thingTypeId = this.getThing().getThingTypeUID().getId();
164         String genericThingTypeId = THING_TYPE_GENERICTHING.getId();
165
166         if (getConfiguration().senderIdOffset == -1 && thingTypeId.equals(genericThingTypeId)) {
167             return true;
168         }
169
170         EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
171         if (bridgeHandler == null) {
172             return false;
173         }
174
175         // if senderIdOffset is not set (=> defaults to -1) or set to -1, the next free senderIdOffset is determined
176         if (getConfiguration().senderIdOffset == -1) {
177             Configuration updateConfig = editConfiguration();
178             getConfiguration().senderIdOffset = bridgeHandler.getNextSenderId(thing);
179             if (getConfiguration().senderIdOffset == -1) {
180                 configurationErrorDescription = "Could not get a free sender Id from Bridge";
181                 return false;
182             }
183             updateConfig.put(PARAMETER_SENDERIDOFFSET, getConfiguration().senderIdOffset);
184             updateConfiguration(updateConfig);
185         }
186
187         byte[] baseId = bridgeHandler.getBaseId();
188         baseId[3] = (byte) ((baseId[3] & 0xFF) + getConfiguration().senderIdOffset);
189         this.senderId = baseId;
190
191         this.updateProperty(PROPERTY_ENOCEAN_ID, HexUtils.bytesToHex(this.senderId));
192         bridgeHandler.addSender(getConfiguration().senderIdOffset, thing);
193
194         return true;
195     }
196
197     private void refreshStates() {
198         logger.debug("polling channels");
199         if (thing.getStatus().equals(ThingStatus.ONLINE)) {
200             for (Channel channel : this.getThing().getChannels()) {
201                 handleCommand(channel.getUID(), RefreshType.REFRESH);
202             }
203         }
204     }
205
206     @Override
207     public void handleCommand(ChannelUID channelUID, Command command) {
208         // We must have a valid sendingEEPType and sender id to send commands
209         if (sendingEEPType == null || senderId == null) {
210             return;
211         }
212
213         // check if the channel is linked otherwise do nothing
214         String channelId = channelUID.getId();
215         Channel channel = getThing().getChannel(channelUID);
216         if (channel == null || !isLinked(channelUID)) {
217             return;
218         }
219
220         ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
221         String channelTypeId = (channelTypeUID != null) ? channelTypeUID.getId() : "";
222
223         // check if we do support refreshs
224         if (command == RefreshType.REFRESH) {
225             if (!sendingEEPType.getSupportsRefresh()) {
226                 return;
227             }
228
229             // receiving status cannot be refreshed
230             switch (channelTypeId) {
231                 case CHANNEL_RSSI:
232                 case CHANNEL_REPEATCOUNT:
233                 case CHANNEL_LASTRECEIVED:
234                     return;
235             }
236         }
237
238         try {
239             Configuration channelConfig = channel.getConfiguration();
240
241             EEP eep = EEPFactory.createEEP(sendingEEPType);
242             if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig)
243                     .hasData()) {
244                 BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId)
245                         .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
246
247                 getBridgeHandler().sendMessage(msg, null);
248             }
249
250         } catch (IllegalArgumentException e) {
251             logger.warn("Exception while sending telegram!", e);
252         }
253     }
254
255     @Override
256     public void handleRemoval() {
257         if (getConfiguration().senderIdOffset > 0) {
258             EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
259             if (bridgeHandler != null) {
260                 bridgeHandler.removeSender(getConfiguration().senderIdOffset);
261             }
262         }
263
264         super.handleRemoval();
265     }
266
267     @Override
268     public void dispose() {
269         if (refreshJob != null && !refreshJob.isCancelled()) {
270             refreshJob.cancel(true);
271             refreshJob = null;
272         }
273     }
274 }