2 * Copyright (c) 2010-2021 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 java.util.AbstractMap.SimpleEntry;
16 import java.util.Collection;
17 import java.util.Hashtable;
18 import java.util.LinkedList;
19 import java.util.List;
21 import java.util.concurrent.atomic.AtomicBoolean;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.openhab.binding.enocean.internal.EnOceanChannelDescription;
25 import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
26 import org.openhab.binding.enocean.internal.eep.EEPType;
27 import org.openhab.core.config.core.status.ConfigStatusMessage;
28 import org.openhab.core.items.Item;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.Channel;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.ThingStatusInfo;
36 import org.openhab.core.thing.binding.ConfigStatusThingHandler;
37 import org.openhab.core.thing.binding.ThingHandler;
38 import org.openhab.core.thing.binding.builder.ChannelBuilder;
39 import org.openhab.core.thing.binding.builder.ThingBuilder;
40 import org.openhab.core.thing.link.ItemChannelLinkRegistry;
41 import org.openhab.core.thing.type.ChannelKind;
42 import org.openhab.core.types.State;
43 import org.openhab.core.types.UnDefType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
49 * @author Daniel Weber - Initial contribution
51 public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
53 private EnOceanBridgeHandler gateway = null;
54 protected Logger logger = LoggerFactory.getLogger(EnOceanBaseThingHandler.class);
56 protected String configurationErrorDescription;
58 // There is no data structure which holds the last triggered event, so we have to implement it by ourself
59 // This is especially needed for press and release events
60 protected Hashtable<String, String> lastEvents = new Hashtable<>();
62 protected EnOceanBaseConfig config = null;
64 private ItemChannelLinkRegistry itemChannelLinkRegistry;
66 public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
68 this.itemChannelLinkRegistry = itemChannelLinkRegistry;
71 @SuppressWarnings("null")
73 public void initialize() {
74 logger.debug("Initializing enocean base thing handler.");
75 this.gateway = null; // reset gateway in case we change the bridge
77 initializeThing((getBridge() == null) ? null : getBridge().getStatus());
80 private void initializeThing(ThingStatus bridgeStatus) {
81 logger.debug("initializeThing thing {} bridge status {}", getThing().getUID(), bridgeStatus);
83 if (this.itemChannelLinkRegistry == null) {
84 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
85 "ItemChannelLinkRegistry could not be found");
87 if (getBridgeHandler() != null) {
88 if (bridgeStatus == ThingStatus.ONLINE) {
90 if (validateConfig()) {
91 updateStatus(ThingStatus.ONLINE);
93 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
94 configurationErrorDescription);
97 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
100 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "A bridge is required");
105 protected boolean validateEnoceanId(String id) {
106 if (id == null || id.isEmpty()) {
109 if (id.length() != 8) {
114 Integer.parseUnsignedInt(id, 16);
116 } catch (Exception e) {
122 abstract void initializeConfig();
124 abstract boolean validateConfig();
126 abstract Collection<EEPType> getEEPTypes();
128 protected void updateChannels() {
130 List<@NonNull Channel> channelList = new LinkedList<>(this.getThing().getChannels());
131 Collection<EEPType> eeps = getEEPTypes();
136 // First remove channels which are no longer supported by current selected eeps of thing
137 AtomicBoolean channelListChanged = new AtomicBoolean(
138 channelList.removeIf(channel -> !eeps.stream().anyMatch(eep -> eep.isChannelSupported(channel))));
140 // Next create supported channels of each selected eep
141 eeps.stream().flatMap(eep -> eep.GetSupportedChannels().keySet().stream().map(id -> new SimpleEntry<>(id, eep)))
143 String channelId = entry.getKey();
144 EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId);
146 // if we do not need to auto create channel => skip
147 if (!cd.autoCreate) {
151 // if we already created a channel with the same type and id => skip
152 if (channelList.stream().anyMatch(channel -> cd.channelTypeUID.equals(channel.getChannelTypeUID())
153 && channelId.equals(channel.getUID().getId()))) {
157 // create channel and add it to the channelList
158 Channel channel = ChannelBuilder
159 .create(new ChannelUID(this.getThing().getUID(), channelId), cd.acceptedItemType)
160 .withConfiguration(entry.getValue().getChannelConfig(channelId)).withType(cd.channelTypeUID)
161 .withKind(cd.isStateChannel ? ChannelKind.STATE : ChannelKind.TRIGGER).withLabel(cd.label)
164 channelList.add(channel);
165 channelListChanged.set(true);
167 if (!cd.isStateChannel) {
168 lastEvents.putIfAbsent(channelId, "");
172 if (channelListChanged.get()) {
173 ThingBuilder thingBuilder = editThing();
174 thingBuilder.withChannels(channelList);
175 updateThing(thingBuilder.build());
179 protected State getCurrentState(Channel channel) {
180 if (channel != null) {
181 Set<Item> items = itemChannelLinkRegistry.getLinkedItems(channel.getUID());
182 for (Item item : items) {
183 State state = item.getState();
184 if (state != UnDefType.NULL && state != UnDefType.UNDEF) {
190 return UnDefType.UNDEF;
193 protected State getCurrentState(String channelId) {
194 return getCurrentState(getThing().getChannel(channelId));
197 protected synchronized EnOceanBridgeHandler getBridgeHandler() {
198 if (this.gateway == null) {
199 Bridge bridge = getBridge();
200 if (bridge == null) {
203 ThingHandler handler = bridge.getHandler();
204 if (handler instanceof EnOceanBridgeHandler) {
205 this.gateway = (EnOceanBridgeHandler) handler;
214 public Collection<ConfigStatusMessage> getConfigStatus() {
216 // Collection<ConfigStatusMessage> configStatusMessages;
218 // The senderId must be provided
220 * final EnOceanActuatorConfig config = getConfigAs(EnOceanActuatorConfig.class);
221 * final String senderId = config.senderIdOffset;
222 * if (senderId == null || senderId.isEmpty()) {
223 * configStatusMessages = Collections.singletonList(ConfigStatusMessage.Builder.error(SENDERID)
224 * .withMessageKeySuffix(EnOceanConfigStatusMessage.SENDERID_MISSING.getMessageKey())
225 * .withArguments(SENDERID).build());
228 * Integer.parseUnsignedInt(senderId, 16);
229 * } catch (Exception e) {
230 * configStatusMessages = Collections.singletonList(ConfigStatusMessage.Builder.error(SENDERID)
231 * .withMessageKeySuffix(EnOceanConfigStatusMessage.SENDERID_MALFORMED.getMessageKey())
232 * .withArguments(SENDERID).build());
234 * configStatusMessages = Collections.emptyList();
237 * return configStatusMessages;
240 return new LinkedList<>();
244 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
245 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
246 initializeThing(bridgeStatusInfo.getStatus());