2 * Copyright (c) 2010-2020 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.Arrays;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
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.thing.Channel;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.link.ItemChannelLinkRegistry;
39 import org.openhab.core.thing.type.ChannelTypeUID;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.openhab.core.util.HexUtils;
46 * @author Daniel Weber - Initial contribution
47 * This class defines base functionality for sending eep messages. This class extends EnOceanBaseSensorHandler
48 * class as most actuator things send status or response messages, too.
50 public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
52 // List of thing types which support sending of eep messages
53 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_CENTRALCOMMAND,
54 THING_TYPE_MEASUREMENTSWITCH, THING_TYPE_GENERICTHING, THING_TYPE_ROLLERSHUTTER, THING_TYPE_THERMOSTAT));
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
59 protected EEPType sendingEEPType = null;
61 private ScheduledFuture<?> refreshJob; // used for polling current status of thing
63 public EnOceanBaseActuatorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
64 super(thing, itemChannelLinkRegistry);
69 * @param senderIdOffset to be validated
70 * @return true if senderIdOffset is between ]0;128[ and is not used yet
72 private boolean validateSenderIdOffset(int senderIdOffset) {
73 if (senderIdOffset == -1) {
77 if (senderIdOffset > 0 && senderIdOffset < 128) {
78 EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
79 if (bridgeHandler != null) {
80 return !bridgeHandler.existsSender(senderIdOffset, this.thing);
88 void initializeConfig() {
89 config = getConfigAs(EnOceanActuatorConfig.class);
92 protected EnOceanActuatorConfig getConfiguration() {
93 return (EnOceanActuatorConfig) config;
97 Collection<EEPType> getEEPTypes() {
98 Collection<EEPType> r = super.getEEPTypes();
99 if (sendingEEPType == null) {
103 return Collections.unmodifiableCollection(Stream
104 .concat(r.stream(), Collections.singletonList(sendingEEPType).stream()).collect(Collectors.toList()));
108 boolean validateConfig() {
109 EnOceanActuatorConfig config = getConfiguration();
110 if (config == null) {
111 configurationErrorDescription = "Configuration is not valid";
115 if (config.sendingEEPId == null || config.sendingEEPId.isEmpty()) {
116 configurationErrorDescription = "Sending EEP must be provided";
121 sendingEEPType = EEPType.getType(getConfiguration().sendingEEPId);
122 } catch (IllegalArgumentException e) {
123 configurationErrorDescription = "Sending EEP is not supported";
127 if (super.validateConfig()) {
129 if (sendingEEPType.getSupportsRefresh()) {
130 if (getConfiguration().pollingInterval > 0) {
131 refreshJob = scheduler.scheduleWithFixedDelay(() -> {
134 } catch (Exception e) {
136 }, 30, getConfiguration().pollingInterval, TimeUnit.SECONDS);
140 if (getConfiguration().broadcastMessages) {
141 destinationId = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
143 destinationId = HexUtils.hexToBytes(config.enoceanId);
145 } catch (Exception e) {
146 configurationErrorDescription = "Configuration is not valid";
150 if (validateSenderIdOffset(getConfiguration().senderIdOffset)) {
151 return initializeIdForSending();
153 configurationErrorDescription = "Sender Id is not valid for bridge";
160 private boolean initializeIdForSending() {
161 // Generic things are treated as actuator things, however to support also generic sensors one can define a
162 // senderIdOffset of -1
163 // TODO: seperate generic actuators from generic sensors?
164 String thingTypeId = this.getThing().getThingTypeUID().getId();
165 String genericThingTypeId = THING_TYPE_GENERICTHING.getId();
167 if (getConfiguration().senderIdOffset == -1 && thingTypeId.equals(genericThingTypeId)) {
171 EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
172 if (bridgeHandler == null) {
176 // if senderIdOffset is not set (=> defaults to -1) or set to -1, the next free senderIdOffset is determined
177 if (getConfiguration().senderIdOffset == -1) {
178 Configuration updateConfig = editConfiguration();
179 getConfiguration().senderIdOffset = bridgeHandler.getNextSenderId(thing);
180 if (getConfiguration().senderIdOffset == -1) {
181 configurationErrorDescription = "Could not get a free sender Id from Bridge";
184 updateConfig.put(PARAMETER_SENDERIDOFFSET, getConfiguration().senderIdOffset);
185 updateConfiguration(updateConfig);
188 byte[] baseId = bridgeHandler.getBaseId();
189 baseId[3] = (byte) ((baseId[3] & 0xFF) + getConfiguration().senderIdOffset);
190 this.senderId = baseId;
192 this.updateProperty(PROPERTY_ENOCEAN_ID, HexUtils.bytesToHex(this.senderId));
193 bridgeHandler.addSender(getConfiguration().senderIdOffset, thing);
198 private void refreshStates() {
199 logger.debug("polling channels");
200 if (thing.getStatus().equals(ThingStatus.ONLINE)) {
201 for (Channel channel : this.getThing().getChannels()) {
202 handleCommand(channel.getUID(), RefreshType.REFRESH);
208 public void handleCommand(ChannelUID channelUID, Command command) {
209 // We must have a valid sendingEEPType and sender id to send commands
210 if (sendingEEPType == null || senderId == null) {
214 // check if the channel is linked otherwise do nothing
215 String channelId = channelUID.getId();
216 Channel channel = getThing().getChannel(channelUID);
217 if (channel == null || !isLinked(channelUID)) {
221 ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
222 String channelTypeId = (channelTypeUID != null) ? channelTypeUID.getId() : "";
224 // check if we do support refreshs
225 if (command == RefreshType.REFRESH) {
226 if (!sendingEEPType.getSupportsRefresh()) {
230 // receiving status cannot be refreshed
231 switch (channelTypeId) {
233 case CHANNEL_REPEATCOUNT:
234 case CHANNEL_LASTRECEIVED:
240 Configuration channelConfig = channel.getConfiguration();
242 EEP eep = EEPFactory.createEEP(sendingEEPType);
243 if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig)
245 BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId)
246 .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
248 getBridgeHandler().sendMessage(msg, null);
251 } catch (IllegalArgumentException e) {
252 logger.warn("Exception while sending telegram!", e);
257 public void handleRemoval() {
258 if (getConfiguration().senderIdOffset > 0) {
259 EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
260 if (bridgeHandler != null) {
261 bridgeHandler.removeSender(getConfiguration().senderIdOffset);
265 super.handleRemoval();
269 public void dispose() {
270 if (refreshJob != null && !refreshJob.isCancelled()) {
271 refreshJob.cancel(true);