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.AbstractMap.SimpleEntry;
18 import java.util.Collection;
19 import java.util.Hashtable;
20 import java.util.LinkedList;
21 import java.util.List;
23 import java.util.concurrent.atomic.AtomicBoolean;
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;
51 * @author Daniel Weber - Initial contribution
53 public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
55 private EnOceanBridgeHandler gateway = null;
56 protected Logger logger = LoggerFactory.getLogger(EnOceanBaseThingHandler.class);
58 protected String configurationErrorDescription;
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<>();
64 protected EnOceanBaseConfig config = null;
66 private ItemChannelLinkRegistry itemChannelLinkRegistry;
68 protected @NonNull ChannelUID prepareAnswer;
70 public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
72 this.itemChannelLinkRegistry = itemChannelLinkRegistry;
73 prepareAnswer = new ChannelUID(thing.getUID(), CHANNEL_STATUS_REQUEST_EVENT);
77 public void initialize() {
78 logger.debug("Initializing enocean base thing handler.");
79 this.gateway = null; // reset gateway in case we change the bridge
81 Bridge bridge = getBridge();
83 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "A bridge is required");
85 initializeThing(bridge.getStatus());
89 private void initializeThing(ThingStatus bridgeStatus) {
90 logger.debug("initializeThing thing {} bridge status {}", getThing().getUID(), bridgeStatus);
92 if (this.itemChannelLinkRegistry == null) {
93 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
94 "ItemChannelLinkRegistry could not be found");
96 if (getBridgeHandler() != null) {
97 if (bridgeStatus == ThingStatus.ONLINE) {
99 if (validateConfig()) {
100 updateStatus(ThingStatus.ONLINE);
102 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
103 configurationErrorDescription);
106 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
109 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "A bridge is required");
114 protected boolean validateEnoceanId(String id) {
115 if (id == null || id.isEmpty()) {
118 if (id.length() != 8) {
123 Integer.parseUnsignedInt(id, 16);
125 } catch (Exception e) {
131 abstract void initializeConfig();
133 abstract boolean validateConfig();
135 abstract Collection<EEPType> getEEPTypes();
137 protected void updateChannels() {
139 List<@NonNull Channel> channelList = new LinkedList<>(this.getThing().getChannels());
140 Collection<EEPType> eeps = getEEPTypes();
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))));
149 // Next create supported channels of each selected eep
150 eeps.stream().flatMap(eep -> eep.GetSupportedChannels().keySet().stream().map(id -> new SimpleEntry<>(id, eep)))
152 String channelId = entry.getKey();
153 EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId);
159 // if we do not need to auto create channel => skip
160 if (!cd.autoCreate) {
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()))) {
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)
177 channelList.add(channel);
178 channelListChanged.set(true);
180 if (!cd.isStateChannel) {
181 lastEvents.putIfAbsent(channelId, "");
185 if (channelListChanged.get()) {
186 ThingBuilder thingBuilder = editThing();
187 thingBuilder.withChannels(channelList);
188 updateThing(thingBuilder.build());
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) {
203 return UnDefType.UNDEF;
206 protected State getCurrentState(String channelId) {
207 return getCurrentState(getThing().getChannel(channelId));
210 protected synchronized EnOceanBridgeHandler getBridgeHandler() {
211 if (this.gateway == null) {
212 Bridge bridge = getBridge();
213 if (bridge == null) {
216 ThingHandler handler = bridge.getHandler();
217 if (handler instanceof EnOceanBridgeHandler) {
218 this.gateway = (EnOceanBridgeHandler) handler;
227 public Collection<ConfigStatusMessage> getConfigStatus() {
229 // Collection<ConfigStatusMessage> configStatusMessages;
231 // The senderId must be provided
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());
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());
247 * configStatusMessages = Collections.emptyList();
250 * return configStatusMessages;
253 return new LinkedList<>();
257 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
258 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
259 initializeThing(bridgeStatusInfo.getStatus());