]> git.basschouten.com Git - openhab-addons.git/blob
c670378b44e473278bba1d74bf5f974168ffc7e3
[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.Set;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20 import java.util.function.Predicate;
21
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.openhab.binding.enocean.internal.config.EnOceanActuatorConfig;
24 import org.openhab.binding.enocean.internal.config.EnOceanChannelRockerSwitchConfigBase.SwitchMode;
25 import org.openhab.binding.enocean.internal.config.EnOceanChannelRockerSwitchListenerConfig;
26 import org.openhab.binding.enocean.internal.config.EnOceanChannelVirtualRockerSwitchConfig;
27 import org.openhab.binding.enocean.internal.eep.EEP;
28 import org.openhab.binding.enocean.internal.eep.EEPFactory;
29 import org.openhab.binding.enocean.internal.eep.EEPType;
30 import org.openhab.binding.enocean.internal.messages.BasePacket;
31 import org.openhab.core.library.types.OnOffType;
32 import org.openhab.core.library.types.StopMoveType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.library.types.UpDownType;
35 import org.openhab.core.thing.Channel;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.CommonTriggerEvents;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.ThingTypeUID;
42 import org.openhab.core.thing.link.ItemChannelLinkRegistry;
43 import org.openhab.core.thing.type.ChannelTypeUID;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.openhab.core.util.HexUtils;
47
48 /**
49  *
50  * @author Daniel Weber - Initial contribution
51  *         This class defines base functionality for sending eep messages. This class extends EnOceanBaseSensorHandler
52  *         class as most actuator things send status or response messages, too.
53  */
54 public class EnOceanClassicDeviceHandler extends EnOceanBaseActuatorHandler {
55
56     // List of thing types which support sending of eep messages
57     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_CLASSICDEVICE);
58
59     private StringType lastTriggerEvent = StringType.valueOf(CommonTriggerEvents.DIR1_PRESSED);
60     ScheduledFuture<?> releaseFuture = null;
61
62     public EnOceanClassicDeviceHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
63         super(thing, itemChannelLinkRegistry);
64     }
65
66     @Override
67     void initializeConfig() {
68         super.initializeConfig();
69         ((EnOceanActuatorConfig) config).broadcastMessages = true;
70         ((EnOceanActuatorConfig) config).enoceanId = EMPTYENOCEANID;
71     }
72
73     @Override
74     public long getEnOceanIdToListenTo() {
75         return 0;
76     }
77
78     @Override
79     public void channelLinked(@NonNull ChannelUID channelUID) {
80         super.channelLinked(channelUID);
81
82         // if linked channel is a listening channel => put listener
83         Channel channel = getThing().getChannel(channelUID);
84         addListener(channel);
85     }
86
87     @Override
88     public void thingUpdated(Thing thing) {
89         super.thingUpdated(thing);
90
91         // it seems that there does not exist a channel update callback
92         // => remove all listeners and add them again
93         getBridgeHandler().removePacketListener(this);
94
95         this.getThing().getChannels().forEach(c -> {
96             if (isLinked(c.getUID()) && !addListener(c)) {
97                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Wrong channel configuration");
98             }
99         });
100     }
101
102     @Override
103     public void channelUnlinked(@NonNull ChannelUID channelUID) {
104         super.channelUnlinked(channelUID);
105
106         // if unlinked channel is listening channel => remove listener
107         Channel channel = getThing().getChannel(channelUID);
108         removeListener(channel);
109     }
110
111     protected boolean addListener(Channel channel) {
112         if (channel == null) {
113             return true;
114         }
115
116         ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
117         String id = channelTypeUID == null ? "" : channelTypeUID.getId();
118
119         if (id.startsWith(CHANNEL_ROCKERSWITCHLISTENER_START)) {
120             EnOceanChannelRockerSwitchListenerConfig config = channel.getConfiguration()
121                     .as(EnOceanChannelRockerSwitchListenerConfig.class);
122             try {
123                 getBridgeHandler().addPacketListener(this, Long.parseLong(config.enoceanId, 16));
124                 return true;
125             } catch (NumberFormatException e) {
126             }
127
128             return false;
129         }
130         return true;
131     }
132
133     protected void removeListener(Channel channel) {
134         if (channel == null) {
135             return;
136         }
137
138         ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
139         String id = channelTypeUID == null ? "" : channelTypeUID.getId();
140
141         if (id.startsWith(CHANNEL_ROCKERSWITCHLISTENER_START)) {
142             EnOceanChannelRockerSwitchListenerConfig config = channel.getConfiguration()
143                     .as(EnOceanChannelRockerSwitchListenerConfig.class);
144             try {
145                 getBridgeHandler().removePacketListener(this, Long.parseLong(config.enoceanId, 16));
146             } catch (NumberFormatException e) {
147             }
148         }
149     }
150
151     @Override
152     protected Predicate<Channel> channelFilter(EEPType eepType, byte[] senderId) {
153         return c -> {
154             ChannelTypeUID channelTypeUID = c.getChannelTypeUID();
155             String id = channelTypeUID == null ? "" : channelTypeUID.getId();
156
157             return id.startsWith(CHANNEL_ROCKERSWITCHLISTENER_START)
158                     && c.getConfiguration().as(EnOceanChannelRockerSwitchListenerConfig.class).enoceanId
159                             .equalsIgnoreCase(HexUtils.bytesToHex(senderId));
160         };
161     }
162
163     @SuppressWarnings("unlikely-arg-type")
164     private StringType convertToReleasedCommand(StringType command) {
165         return command.equals(CommonTriggerEvents.DIR1_PRESSED) ? StringType.valueOf(CommonTriggerEvents.DIR1_RELEASED)
166                 : StringType.valueOf(CommonTriggerEvents.DIR2_RELEASED);
167     }
168
169     private StringType convertToPressedCommand(Command command, SwitchMode switchMode) {
170         if (command instanceof StringType) {
171             return (StringType) command;
172         } else if (command instanceof OnOffType) {
173             switch (switchMode) {
174                 case RockerSwitch:
175                     return (command == OnOffType.ON) ? StringType.valueOf(CommonTriggerEvents.DIR1_PRESSED)
176                             : StringType.valueOf(CommonTriggerEvents.DIR2_PRESSED);
177                 case ToggleDir1:
178                     return StringType.valueOf(CommonTriggerEvents.DIR1_PRESSED);
179                 case ToggleDir2:
180                     return StringType.valueOf(CommonTriggerEvents.DIR2_PRESSED);
181                 default:
182                     return null;
183             }
184         } else if (command instanceof UpDownType) {
185             switch (switchMode) {
186                 case RockerSwitch:
187                     return (command == UpDownType.UP) ? StringType.valueOf(CommonTriggerEvents.DIR1_PRESSED)
188                             : StringType.valueOf(CommonTriggerEvents.DIR2_PRESSED);
189                 case ToggleDir1:
190                     return StringType.valueOf(CommonTriggerEvents.DIR1_PRESSED);
191                 case ToggleDir2:
192                     return StringType.valueOf(CommonTriggerEvents.DIR2_PRESSED);
193                 default:
194                     return null;
195             }
196         } else if (command instanceof StopMoveType) {
197             if (command == StopMoveType.STOP) {
198                 return lastTriggerEvent;
199             }
200         }
201
202         return null;
203     }
204
205     @Override
206     public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) {
207         // We must have a valid sendingEEPType and sender id to send commands
208         if (sendingEEPType == null || senderId == null || command == RefreshType.REFRESH) {
209             return;
210         }
211
212         String channelId = channelUID.getId();
213         Channel channel = getThing().getChannel(channelUID);
214         if (channel == null) {
215             return;
216         }
217
218         ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
219         String channelTypeId = (channelTypeUID != null) ? channelTypeUID.getId() : "";
220         if (channelTypeId.contains("Listener")) {
221             return;
222         }
223
224         EnOceanChannelVirtualRockerSwitchConfig channelConfig = channel.getConfiguration()
225                 .as(EnOceanChannelVirtualRockerSwitchConfig.class);
226
227         StringType result = convertToPressedCommand(command, channelConfig.getSwitchMode());
228
229         if (result != null) {
230             lastTriggerEvent = result;
231
232             EEP eep = EEPFactory.createEEP(sendingEEPType);
233             if (eep.setSenderId(senderId).setDestinationId(destinationId).convertFromCommand(channelId, channelTypeId,
234                     result, id -> this.getCurrentState(id), channel.getConfiguration()).hasData()) {
235                 BasePacket press = eep.setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
236
237                 getBridgeHandler().sendMessage(press, null);
238
239                 if (channelConfig.duration > 0) {
240                     releaseFuture = scheduler.schedule(() -> {
241                         if (eep.convertFromCommand(channelId, channelTypeId, convertToReleasedCommand(lastTriggerEvent),
242                                 id -> this.getCurrentState(id), channel.getConfiguration()).hasData()) {
243                             BasePacket release = eep.getERP1Message();
244                             getBridgeHandler().sendMessage(release, null);
245                         }
246                     }, channelConfig.duration, TimeUnit.MILLISECONDS);
247                 }
248             }
249         }
250     }
251
252     @Override
253     public void handleRemoval() {
254         if (releaseFuture != null && !releaseFuture.isDone()) {
255             releaseFuture.cancel(true);
256         }
257
258         releaseFuture = null;
259         super.handleRemoval();
260     }
261 }