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;
19 import java.util.Comparator;
20 import java.util.Hashtable;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24 import java.util.function.Predicate;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
29 import org.openhab.binding.enocean.internal.eep.EEP;
30 import org.openhab.binding.enocean.internal.eep.EEPFactory;
31 import org.openhab.binding.enocean.internal.eep.EEPType;
32 import org.openhab.binding.enocean.internal.messages.BasePacket;
33 import org.openhab.binding.enocean.internal.messages.ERP1Message;
34 import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
35 import org.openhab.binding.enocean.internal.transceiver.PacketListener;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.thing.Channel;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.thing.link.ItemChannelLinkRegistry;
42 import org.openhab.core.thing.type.ChannelKind;
43 import org.openhab.core.thing.type.ChannelTypeUID;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47 import org.openhab.core.util.HexUtils;
50 * @author Daniel Weber - Initial contribution
51 * This class defines base functionality for receiving eep messages.
54 public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements PacketListener {
56 // List of all thing types which support receiving of eep messages
57 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ROOMOPERATINGPANEL,
58 THING_TYPE_MECHANICALHANDLE, THING_TYPE_CONTACT, THING_TYPE_TEMPERATURESENSOR,
59 THING_TYPE_TEMPERATUREHUMIDITYSENSOR, THING_TYPE_GASSENSOR, THING_TYPE_ROCKERSWITCH,
60 THING_TYPE_OCCUPANCYSENSOR, THING_TYPE_LIGHTTEMPERATUREOCCUPANCYSENSOR, THING_TYPE_LIGHTSENSOR,
61 THING_TYPE_PUSHBUTTON, THING_TYPE_AUTOMATEDMETERSENSOR, THING_TYPE_ENVIRONMENTALSENSOR,
62 THING_TYPE_MULTFUNCTIONSMOKEDETECTOR, THING_TYPE_WINDOWSASHHANDLESENSOR);
64 protected final Hashtable<RORG, EEPType> receivingEEPTypes = new Hashtable<>();
66 protected @Nullable ScheduledFuture<?> responseFuture = null;
68 public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
69 super(thing, itemChannelLinkRegistry);
73 void initializeConfig() {
74 config = getConfigAs(EnOceanBaseConfig.class);
79 Collection<EEPType> getEEPTypes() {
80 return Collections.unmodifiableCollection(receivingEEPTypes.values());
84 boolean validateConfig() {
85 receivingEEPTypes.clear();
87 config.receivingEEPId.forEach(receivingEEP -> {
88 EEPType receivingEEPType = EEPType.getType(receivingEEP);
89 EEPType existingKey = receivingEEPTypes.putIfAbsent(receivingEEPType.getRORG(), receivingEEPType);
90 if (existingKey != null) {
91 throw new IllegalArgumentException("Receiving more than one EEP of the same RORG is not supported");
94 if (config.receivingSIGEEP) {
95 receivingEEPTypes.put(EEPType.SigBatteryStatus.getRORG(), EEPType.SigBatteryStatus);
97 } catch (IllegalArgumentException e) {
98 String eMessage = e.getMessage();
99 configurationErrorDescription = eMessage != null ? eMessage
100 : "IllegalArgumentException without a message was thrown";
106 if (!validateEnoceanId(config.enoceanId)) {
107 configurationErrorDescription = "EnOceanId is not a valid EnOceanId";
111 if (!config.enoceanId.equals(EMPTYENOCEANID)) {
112 EnOceanBridgeHandler handler = getBridgeHandler();
113 if (handler != null) {
114 handler.addPacketListener(this);
122 public long getEnOceanIdToListenTo() {
123 return Long.parseLong(config.enoceanId, 16);
127 public void handleRemoval() {
128 EnOceanBridgeHandler handler = getBridgeHandler();
129 if (handler != null) {
130 handler.removePacketListener(this);
132 super.handleRemoval();
136 public void handleCommand(ChannelUID channelUID, Command command) {
137 // sensor things cannot send any messages, hence they are not allowed to handle any command
138 // The only possible command would be "Refresh"
141 protected Predicate<Channel> channelFilter(EEPType eepType, byte[] senderId) {
144 boolean result = eepType.isChannelSupported(c);
145 return (isLinked(c.getUID()) || c.getKind() == ChannelKind.TRIGGER) && result;
149 protected void sendRequestResponse() {
150 throw new UnsupportedOperationException("Sensor cannot send responses");
154 public void packetReceived(BasePacket packet) {
155 ERP1Message msg = (ERP1Message) packet;
157 EEPType localReceivingType = receivingEEPTypes.get(msg.getRORG());
158 if (localReceivingType == null) {
162 EEP eep = EEPFactory.buildEEP(localReceivingType, (ERP1Message) packet);
163 logger.debug("ESP Packet payload {} for {} received", HexUtils.bytesToHex(packet.getPayload()),
164 HexUtils.bytesToHex(msg.getSenderId()));
167 byte[] senderId = msg.getSenderId();
169 // try to interpret received message for all linked or trigger channels
170 getThing().getChannels().stream().filter(channelFilter(localReceivingType, senderId))
171 .sorted(Comparator.comparing(Channel::getKind)) // handle state channels first
172 .forEachOrdered(channel -> {
174 ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
175 String channelTypeId = (channelTypeUID != null) ? channelTypeUID.getId() : "";
177 String channelId = channel.getUID().getId();
178 Configuration channelConfig = channel.getConfiguration();
180 switch (channel.getKind()) {
182 State result = eep.convertToState(channelId, channelTypeId, channelConfig,
183 this::getCurrentState);
185 // if message can be interpreted (result != UnDefType.UNDEF) => update item state
186 if (result != UnDefType.UNDEF) {
187 updateState(channelId, result);
191 String lastEvent = lastEvents.get(channelId);
192 String event = eep.convertToEvent(channelId, channelTypeId, lastEvent, channelConfig);
194 triggerChannel(channel.getUID(), event);
195 lastEvents.put(channelId, event);
201 if (localReceivingType.getRequstesResponse()) {
202 // fire trigger for receive
203 triggerChannel(prepareAnswer, "requestAnswer");
204 // Send response after 100ms
205 ScheduledFuture<?> responseFuture = this.responseFuture;
206 if (responseFuture == null || responseFuture.isDone()) {
207 this.responseFuture = scheduler.schedule(this::sendRequestResponse, 100, TimeUnit.MILLISECONDS);