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