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.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;
52 * @author Daniel Weber - Initial contribution
55 public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
57 private @Nullable EnOceanBridgeHandler gateway = null;
58 protected Logger logger = LoggerFactory.getLogger(EnOceanBaseThingHandler.class);
60 protected String configurationErrorDescription = "";
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<>();
66 protected EnOceanBaseConfig config = new EnOceanBaseConfig();
68 private ItemChannelLinkRegistry itemChannelLinkRegistry;
70 protected ChannelUID prepareAnswer;
72 public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
74 this.itemChannelLinkRegistry = itemChannelLinkRegistry;
75 prepareAnswer = new ChannelUID(thing.getUID(), CHANNEL_STATUS_REQUEST_EVENT);
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();
84 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "A bridge is required");
86 initializeThing(bridge.getStatus());
90 private void initializeThing(ThingStatus bridgeStatus) {
91 logger.debug("initializeThing thing {} bridge status {}", getThing().getUID(), bridgeStatus);
93 if (getBridgeHandler() != null) {
94 if (bridgeStatus == ThingStatus.ONLINE) {
96 if (validateConfig()) {
97 updateStatus(ThingStatus.ONLINE);
99 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
100 configurationErrorDescription);
103 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
106 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "A bridge is required");
110 protected boolean validateEnoceanId(@Nullable String id) {
111 if (id == null || id.isEmpty()) {
114 if (id.length() != 8) {
119 Integer.parseUnsignedInt(id, 16);
121 } catch (Exception e) {
127 abstract void initializeConfig();
129 abstract boolean validateConfig();
131 abstract @Nullable Collection<EEPType> getEEPTypes();
133 protected void updateChannels() {
134 List<Channel> channelList = new LinkedList<>(this.getThing().getChannels());
135 Collection<EEPType> eeps = getEEPTypes();
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))));
144 // Next create supported channels of each selected eep
145 eeps.stream().flatMap(eep -> eep.getSupportedChannels().keySet().stream().map(id -> new SimpleEntry<>(id, eep)))
147 String channelId = entry.getKey();
148 EnOceanChannelDescription cd = entry.getValue().getSupportedChannels().get(channelId);
154 // if we do not need to auto create channel => skip
155 if (!cd.autoCreate) {
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()))) {
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)
172 channelList.add(channel);
173 channelListChanged.set(true);
175 if (!cd.isStateChannel) {
176 lastEvents.putIfAbsent(channelId, "");
180 if (channelListChanged.get()) {
181 ThingBuilder thingBuilder = editThing();
182 thingBuilder.withChannels(channelList);
183 updateThing(thingBuilder.build());
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) {
198 return UnDefType.UNDEF;
201 protected State getCurrentState(String channelId) {
202 return getCurrentState(getThing().getChannel(channelId));
205 protected synchronized @Nullable EnOceanBridgeHandler getBridgeHandler() {
206 if (this.gateway == null) {
207 Bridge bridge = getBridge();
208 if (bridge == null) {
211 ThingHandler handler = bridge.getHandler();
212 if (handler instanceof EnOceanBridgeHandler bridgeHandler) {
213 this.gateway = bridgeHandler;
222 public Collection<ConfigStatusMessage> getConfigStatus() {
224 // Collection<ConfigStatusMessage> configStatusMessages;
226 // The senderId must be provided
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());
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());
242 * configStatusMessages = Collections.emptyList();
245 * return configStatusMessages;
248 return new LinkedList<>();
252 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
253 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
254 initializeThing(bridgeStatusInfo.getStatus());