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