]> git.basschouten.com Git - openhab-addons.git/blob
e308d4c6af447a7a64f60d96d52793a85c7a50f9
[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.AbstractMap.SimpleEntry;
18 import java.util.Collection;
19 import java.util.Hashtable;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.concurrent.atomic.AtomicBoolean;
24
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.openhab.binding.enocean.internal.EnOceanChannelDescription;
27 import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
28 import org.openhab.binding.enocean.internal.eep.EEPType;
29 import org.openhab.core.config.core.status.ConfigStatusMessage;
30 import org.openhab.core.items.Item;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.ThingStatusInfo;
38 import org.openhab.core.thing.binding.ConfigStatusThingHandler;
39 import org.openhab.core.thing.binding.ThingHandler;
40 import org.openhab.core.thing.binding.builder.ChannelBuilder;
41 import org.openhab.core.thing.binding.builder.ThingBuilder;
42 import org.openhab.core.thing.link.ItemChannelLinkRegistry;
43 import org.openhab.core.thing.type.ChannelKind;
44 import org.openhab.core.types.State;
45 import org.openhab.core.types.UnDefType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  *
51  * @author Daniel Weber - Initial contribution
52  */
53 public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
54
55     private EnOceanBridgeHandler gateway = null;
56     protected Logger logger = LoggerFactory.getLogger(EnOceanBaseThingHandler.class);
57
58     protected String configurationErrorDescription;
59
60     // There is no data structure which holds the last triggered event, so we have to implement it by ourself
61     // This is especially needed for press and release events
62     protected Hashtable<String, String> lastEvents = new Hashtable<>();
63
64     protected EnOceanBaseConfig config = null;
65
66     private ItemChannelLinkRegistry itemChannelLinkRegistry;
67
68     protected @NonNull ChannelUID prepareAnswer;
69
70     public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
71         super(thing);
72         this.itemChannelLinkRegistry = itemChannelLinkRegistry;
73         prepareAnswer = new ChannelUID(thing.getUID(), CHANNEL_STATUS_REQUEST_EVENT);
74     }
75
76     @Override
77     public void initialize() {
78         logger.debug("Initializing enocean base thing handler.");
79         this.gateway = null; // reset gateway in case we change the bridge
80         this.config = null;
81         Bridge bridge = getBridge();
82         if (bridge == null) {
83             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "A bridge is required");
84         } else {
85             initializeThing(bridge.getStatus());
86         }
87     }
88
89     private void initializeThing(ThingStatus bridgeStatus) {
90         logger.debug("initializeThing thing {} bridge status {}", getThing().getUID(), bridgeStatus);
91
92         if (this.itemChannelLinkRegistry == null) {
93             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
94                     "ItemChannelLinkRegistry could not be found");
95         } else {
96             if (getBridgeHandler() != null) {
97                 if (bridgeStatus == ThingStatus.ONLINE) {
98                     initializeConfig();
99                     if (validateConfig()) {
100                         updateStatus(ThingStatus.ONLINE);
101                     } else {
102                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
103                                 configurationErrorDescription);
104                     }
105                 } else {
106                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
107                 }
108             } else {
109                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "A bridge is required");
110             }
111         }
112     }
113
114     protected boolean validateEnoceanId(String id) {
115         if (id == null || id.isEmpty()) {
116             return false;
117         } else {
118             if (id.length() != 8) {
119                 return false;
120             }
121
122             try {
123                 Integer.parseUnsignedInt(id, 16);
124                 return true;
125             } catch (Exception e) {
126                 return false;
127             }
128         }
129     }
130
131     abstract void initializeConfig();
132
133     abstract boolean validateConfig();
134
135     abstract Collection<EEPType> getEEPTypes();
136
137     protected void updateChannels() {
138         @NonNull
139         List<@NonNull Channel> channelList = new LinkedList<>(this.getThing().getChannels());
140         Collection<EEPType> eeps = getEEPTypes();
141         if (eeps == null) {
142             return;
143         }
144
145         // First remove channels which are no longer supported by current selected eeps of thing
146         AtomicBoolean channelListChanged = new AtomicBoolean(
147                 channelList.removeIf(channel -> !eeps.stream().anyMatch(eep -> eep.isChannelSupported(channel))));
148
149         // Next create supported channels of each selected eep
150         eeps.stream().flatMap(eep -> eep.GetSupportedChannels().keySet().stream().map(id -> new SimpleEntry<>(id, eep)))
151                 .forEach(entry -> {
152                     String channelId = entry.getKey();
153                     EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId);
154
155                     if (cd == null) {
156                         return;
157                     }
158
159                     // if we do not need to auto create channel => skip
160                     if (!cd.autoCreate) {
161                         return;
162                     }
163
164                     // if we already created a channel with the same type and id => skip
165                     if (channelList.stream().anyMatch(channel -> cd.channelTypeUID.equals(channel.getChannelTypeUID())
166                             && channelId.equals(channel.getUID().getId()))) {
167                         return;
168                     }
169
170                     // create channel and add it to the channelList
171                     Channel channel = ChannelBuilder
172                             .create(new ChannelUID(this.getThing().getUID(), channelId), cd.acceptedItemType)
173                             .withConfiguration(entry.getValue().getChannelConfig(channelId)).withType(cd.channelTypeUID)
174                             .withKind(cd.isStateChannel ? ChannelKind.STATE : ChannelKind.TRIGGER).withLabel(cd.label)
175                             .build();
176
177                     channelList.add(channel);
178                     channelListChanged.set(true);
179
180                     if (!cd.isStateChannel) {
181                         lastEvents.putIfAbsent(channelId, "");
182                     }
183                 });
184
185         if (channelListChanged.get()) {
186             ThingBuilder thingBuilder = editThing();
187             thingBuilder.withChannels(channelList);
188             updateThing(thingBuilder.build());
189         }
190     }
191
192     protected State getCurrentState(Channel channel) {
193         if (channel != null) {
194             Set<Item> items = itemChannelLinkRegistry.getLinkedItems(channel.getUID());
195             for (Item item : items) {
196                 State state = item.getState();
197                 if (state != UnDefType.NULL && state != UnDefType.UNDEF) {
198                     return state;
199                 }
200             }
201         }
202
203         return UnDefType.UNDEF;
204     }
205
206     protected State getCurrentState(String channelId) {
207         return getCurrentState(getThing().getChannel(channelId));
208     }
209
210     protected synchronized EnOceanBridgeHandler getBridgeHandler() {
211         if (this.gateway == null) {
212             Bridge bridge = getBridge();
213             if (bridge == null) {
214                 return null;
215             }
216             ThingHandler handler = bridge.getHandler();
217             if (handler instanceof EnOceanBridgeHandler) {
218                 this.gateway = (EnOceanBridgeHandler) handler;
219             } else {
220                 return null;
221             }
222         }
223         return this.gateway;
224     }
225
226     @Override
227     public Collection<ConfigStatusMessage> getConfigStatus() {
228         // TODO
229         // Collection<ConfigStatusMessage> configStatusMessages;
230
231         // The senderId must be provided
232         /*
233          * final EnOceanActuatorConfig config = getConfigAs(EnOceanActuatorConfig.class);
234          * final String senderId = config.senderIdOffset;
235          * if (senderId == null || senderId.isEmpty()) {
236          * configStatusMessages = Collections.singletonList(ConfigStatusMessage.Builder.error(SENDERID)
237          * .withMessageKeySuffix(EnOceanConfigStatusMessage.SENDERID_MISSING.getMessageKey())
238          * .withArguments(SENDERID).build());
239          * } else {
240          * try {
241          * Integer.parseUnsignedInt(senderId, 16);
242          * } catch (Exception e) {
243          * configStatusMessages = Collections.singletonList(ConfigStatusMessage.Builder.error(SENDERID)
244          * .withMessageKeySuffix(EnOceanConfigStatusMessage.SENDERID_MALFORMED.getMessageKey())
245          * .withArguments(SENDERID).build());
246          * }
247          * configStatusMessages = Collections.emptyList();
248          * }
249          *
250          * return configStatusMessages;
251          */
252
253         return new LinkedList<>();
254     }
255
256     @Override
257     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
258         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
259             initializeThing(bridgeStatusInfo.getStatus());
260         }
261     }
262 }