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