]> git.basschouten.com Git - openhab-addons.git/blob
e26b3663f46e595d851b79802e2513a66d90d198
[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.Comparator;
20 import java.util.Hashtable;
21 import java.util.Set;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24 import java.util.function.Predicate;
25
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;
48
49 /**
50  * @author Daniel Weber - Initial contribution
51  *         This class defines base functionality for receiving eep messages.
52  */
53 @NonNullByDefault
54 public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements PacketListener {
55
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);
63
64     protected final Hashtable<RORG, EEPType> receivingEEPTypes = new Hashtable<>();
65
66     protected @Nullable ScheduledFuture<?> responseFuture = null;
67
68     public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
69         super(thing, itemChannelLinkRegistry);
70     }
71
72     @Override
73     void initializeConfig() {
74         config = getConfigAs(EnOceanBaseConfig.class);
75     }
76
77     @Override
78     @Nullable
79     Collection<EEPType> getEEPTypes() {
80         return Collections.unmodifiableCollection(receivingEEPTypes.values());
81     }
82
83     @Override
84     boolean validateConfig() {
85         receivingEEPTypes.clear();
86         try {
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");
92                 }
93             });
94             if (config.receivingSIGEEP) {
95                 receivingEEPTypes.put(EEPType.SigBatteryStatus.getRORG(), EEPType.SigBatteryStatus);
96             }
97         } catch (IllegalArgumentException e) {
98             String eMessage = e.getMessage();
99             configurationErrorDescription = eMessage != null ? eMessage
100                     : "IllegalArgumentException without a message was thrown";
101             return false;
102         }
103
104         updateChannels();
105
106         if (!validateEnoceanId(config.enoceanId)) {
107             configurationErrorDescription = "EnOceanId is not a valid EnOceanId";
108             return false;
109         }
110
111         if (!config.enoceanId.equals(EMPTYENOCEANID)) {
112             EnOceanBridgeHandler handler = getBridgeHandler();
113             if (handler != null) {
114                 handler.addPacketListener(this);
115             }
116         }
117
118         return true;
119     }
120
121     @Override
122     public long getEnOceanIdToListenTo() {
123         return Long.parseLong(config.enoceanId, 16);
124     }
125
126     @Override
127     public void handleRemoval() {
128         EnOceanBridgeHandler handler = getBridgeHandler();
129         if (handler != null) {
130             handler.removePacketListener(this);
131         }
132         super.handleRemoval();
133     }
134
135     @Override
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"
139     }
140
141     protected Predicate<Channel> channelFilter(EEPType eepType, byte[] senderId) {
142         return c -> {
143
144             boolean result = eepType.isChannelSupported(c);
145             return (isLinked(c.getUID()) || c.getKind() == ChannelKind.TRIGGER) && result;
146         };
147     }
148
149     protected void sendRequestResponse() {
150         throw new UnsupportedOperationException("Sensor cannot send responses");
151     }
152
153     @Override
154     public void packetReceived(BasePacket packet) {
155         ERP1Message msg = (ERP1Message) packet;
156
157         EEPType localReceivingType = receivingEEPTypes.get(msg.getRORG());
158         if (localReceivingType == null) {
159             return;
160         }
161
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()));
165
166         if (eep.isValid()) {
167             byte[] senderId = msg.getSenderId();
168
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 -> {
173
174                         ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
175                         String channelTypeId = (channelTypeUID != null) ? channelTypeUID.getId() : "";
176
177                         String channelId = channel.getUID().getId();
178                         Configuration channelConfig = channel.getConfiguration();
179
180                         switch (channel.getKind()) {
181                             case STATE:
182                                 State result = eep.convertToState(channelId, channelTypeId, channelConfig,
183                                         this::getCurrentState);
184
185                                 // if message can be interpreted (result != UnDefType.UNDEF) => update item state
186                                 if (result != UnDefType.UNDEF) {
187                                     updateState(channelId, result);
188                                 }
189                                 break;
190                             case TRIGGER:
191                                 String lastEvent = lastEvents.get(channelId);
192                                 if (lastEvent != null) {
193                                     String event = eep.convertToEvent(channelId, channelTypeId, lastEvent,
194                                             channelConfig);
195                                     if (event != null) {
196                                         triggerChannel(channel.getUID(), event);
197                                         lastEvents.put(channelId, event);
198                                     }
199                                 }
200                                 break;
201                         }
202                     });
203
204             if (localReceivingType.getRequstesResponse()) {
205                 // fire trigger for receive
206                 triggerChannel(prepareAnswer, "requestAnswer");
207                 // Send response after 100ms
208                 ScheduledFuture<?> localResponseFuture = responseFuture;
209                 if (localResponseFuture == null || localResponseFuture.isDone()) {
210                     localResponseFuture = scheduler.schedule(this::sendRequestResponse, 100, TimeUnit.MILLISECONDS);
211                 }
212             }
213         }
214     }
215 }