2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.enocean.internal.handler;
15 import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
17 import java.util.Collection;
18 import java.util.Collections;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
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;
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.
52 public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
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);
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
63 protected @Nullable EEPType sendingEEPType = null;
65 private @Nullable ScheduledFuture<?> refreshJob; // used for polling current status of thing
67 public EnOceanBaseActuatorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
68 super(thing, itemChannelLinkRegistry);
73 * @param senderIdOffset to be validated
74 * @return true if senderIdOffset is between ]0;128[ and is not used yet
76 private boolean validateSenderIdOffset(@Nullable Integer senderIdOffset) {
77 if (senderIdOffset == null) {
81 if (senderIdOffset > 0 && senderIdOffset < 128) {
82 EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
83 if (bridgeHandler != null) {
84 return !bridgeHandler.existsSender(senderIdOffset, this.thing);
92 void initializeConfig() {
93 config = getConfigAs(EnOceanActuatorConfig.class);
96 protected EnOceanActuatorConfig getConfiguration() {
97 return (EnOceanActuatorConfig) config;
102 Collection<EEPType> getEEPTypes() {
103 Collection<EEPType> r = super.getEEPTypes();
105 if (sendingEEPType == null) {
109 r = Collections.emptyList();
111 return Collections.unmodifiableCollection(Stream
112 .concat(r.stream(), Collections.singletonList(sendingEEPType).stream()).collect(Collectors.toList()));
116 boolean validateConfig() {
117 EnOceanActuatorConfig config = getConfiguration();
119 if (config.sendingEEPId.isEmpty()) {
120 configurationErrorDescription = "Sending EEP must be provided";
124 EEPType localEEPType = null;
126 localEEPType = EEPType.getType(getConfiguration().sendingEEPId);
127 sendingEEPType = localEEPType;
128 } catch (IllegalArgumentException e) {
129 configurationErrorDescription = "Sending EEP is not supported";
133 if (super.validateConfig()) {
135 if (localEEPType.getSupportsRefresh()) {
136 if (getConfiguration().pollingInterval > 0) {
137 refreshJob = scheduler.scheduleWithFixedDelay(() -> {
140 } catch (Exception e) {
142 }, 30, getConfiguration().pollingInterval, TimeUnit.SECONDS);
146 if (getConfiguration().broadcastMessages) {
147 destinationId = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
149 destinationId = HexUtils.hexToBytes(config.enoceanId);
151 } catch (Exception e) {
152 configurationErrorDescription = "Configuration is not valid";
156 if (validateSenderIdOffset(getConfiguration().senderIdOffset)) {
157 return initializeIdForSending();
159 configurationErrorDescription = "Sender Id is not valid for bridge";
166 private boolean initializeIdForSending() {
167 EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
168 if (bridgeHandler == null) {
172 // Generic things are treated as actuator things, however to support also generic sensors one can omit
174 // TODO: seperate generic actuators from generic sensors?
175 Integer senderOffset = getConfiguration().senderIdOffset;
177 if ((senderOffset == null && THING_TYPE_GENERICTHING.equals(this.getThing().getThingTypeUID()))) {
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";
190 updateConfig.put(PARAMETER_SENDERIDOFFSET, senderOffset);
191 updateConfiguration(updateConfig);
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);
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);
212 protected void sendRequestResponse() {
213 sendMessage(VIRTUALCHANNEL_SEND_COMMAND, VIRTUALCHANNEL_SEND_COMMAND, OnOffType.ON, null);
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");
223 EEP eep = EEPFactory.createEEP(sendType);
224 if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig)
226 BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId)
227 .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
229 logger.warn("cannot send an empty message");
232 EnOceanBridgeHandler handler = getBridgeHandler();
233 if (handler != null) {
234 handler.sendMessage(msg, null);
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) {
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)) {
254 ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
255 String channelTypeId = (channelTypeUID != null) ? channelTypeUID.getId() : "";
257 // check if we do support refreshs
258 if (command == RefreshType.REFRESH) {
259 if (!localsendingType.getSupportsRefresh()) {
263 // receiving status cannot be refreshed
264 switch (channelTypeId) {
266 case CHANNEL_REPEATCOUNT:
267 case CHANNEL_LASTRECEIVED:
273 Configuration channelConfig = channel.getConfiguration();
274 sendMessage(channelId, channelTypeId, command, channelConfig);
275 } catch (IllegalArgumentException e) {
276 logger.warn("Exception while sending telegram!", e);
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);
289 if (bridgeHandler.isSmackClient(this.thing)) {
290 logger.warn("Removing smack client (ThingId: {}) without teach out!", this.thing.getUID().getId());
294 super.handleRemoval();
298 public void dispose() {
299 ScheduledFuture<?> localRefreshJob = refreshJob;
300 if (localRefreshJob != null && !localRefreshJob.isCancelled()) {
301 localRefreshJob.cancel(true);