2 * Copyright (c) 2010-2020 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.io.IOException;
18 import java.math.BigDecimal;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.LinkedList;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
27 import org.openhab.binding.enocean.internal.EnOceanConfigStatusMessage;
28 import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
29 import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig;
30 import org.openhab.binding.enocean.internal.messages.BasePacket;
31 import org.openhab.binding.enocean.internal.messages.BaseResponse;
32 import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory;
33 import org.openhab.binding.enocean.internal.messages.RDBaseIdResponse;
34 import org.openhab.binding.enocean.internal.messages.RDRepeaterResponse;
35 import org.openhab.binding.enocean.internal.messages.RDVersionResponse;
36 import org.openhab.binding.enocean.internal.messages.Response;
37 import org.openhab.binding.enocean.internal.messages.Response.ResponseType;
38 import org.openhab.binding.enocean.internal.transceiver.EnOceanESP2Transceiver;
39 import org.openhab.binding.enocean.internal.transceiver.EnOceanESP3Transceiver;
40 import org.openhab.binding.enocean.internal.transceiver.EnOceanTransceiver;
41 import org.openhab.binding.enocean.internal.transceiver.PacketListener;
42 import org.openhab.binding.enocean.internal.transceiver.ResponseListener;
43 import org.openhab.binding.enocean.internal.transceiver.ResponseListenerIgnoringTimeouts;
44 import org.openhab.binding.enocean.internal.transceiver.TransceiverErrorListener;
45 import org.openhab.core.config.core.Configuration;
46 import org.openhab.core.config.core.status.ConfigStatusMessage;
47 import org.openhab.core.io.transport.serial.PortInUseException;
48 import org.openhab.core.io.transport.serial.SerialPortManager;
49 import org.openhab.core.library.types.StringType;
50 import org.openhab.core.thing.Bridge;
51 import org.openhab.core.thing.ChannelUID;
52 import org.openhab.core.thing.Thing;
53 import org.openhab.core.thing.ThingStatus;
54 import org.openhab.core.thing.ThingStatusDetail;
55 import org.openhab.core.thing.ThingTypeUID;
56 import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
57 import org.openhab.core.types.Command;
58 import org.openhab.core.types.RefreshType;
59 import org.openhab.core.util.HexUtils;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
64 * The {@link EnOceanBridgeHandler} is responsible for sending ESP3Packages build by {@link EnOceanActuatorHandler} and
65 * transferring received ESP3Packages to {@link EnOceanSensorHandler}.
67 * @author Daniel Weber - Initial contribution
69 public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements TransceiverErrorListener {
71 private Logger logger = LoggerFactory.getLogger(EnOceanBridgeHandler.class);
73 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_BRIDGE));
75 private EnOceanTransceiver transceiver; // holds connection to serial/tcp port and sends/receives messages
76 private ScheduledFuture<?> connectorTask; // is used for reconnection if something goes wrong
78 private byte[] baseId = null;
79 private Thing[] sendingThings = new Thing[128];
81 private int nextSenderId = 0;
82 private SerialPortManager serialPortManager;
84 public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
86 this.serialPortManager = serialPortManager;
90 public void handleCommand(ChannelUID channelUID, Command command) {
91 if (transceiver == null) {
92 updateStatus(ThingStatus.OFFLINE);
96 switch (channelUID.getId()) {
97 case CHANNEL_REPEATERMODE:
98 if (command instanceof RefreshType) {
99 sendMessage(ESP3PacketFactory.CO_RD_REPEATER,
100 new ResponseListenerIgnoringTimeouts<RDRepeaterResponse>() {
102 public void responseReceived(RDRepeaterResponse response) {
103 if (response.isValid() && response.isOK()) {
104 updateState(channelUID, response.getRepeaterLevel());
106 updateState(channelUID, new StringType(REPEATERMODE_OFF));
110 } else if (command instanceof StringType) {
111 sendMessage(ESP3PacketFactory.CO_WR_REPEATER((StringType) command),
112 new ResponseListenerIgnoringTimeouts<BaseResponse>() {
114 public void responseReceived(BaseResponse response) {
115 if (response.isOK()) {
116 updateState(channelUID, (StringType) command);
123 case CHANNEL_SETBASEID:
124 if (command instanceof StringType) {
126 byte[] id = HexUtils.hexToBytes(((StringType) command).toFullString());
128 sendMessage(ESP3PacketFactory.CO_WR_IDBASE(id),
129 new ResponseListenerIgnoringTimeouts<BaseResponse>() {
131 public void responseReceived(BaseResponse response) {
132 if (response.isOK()) {
133 updateState(channelUID, new StringType("New Id successfully set"));
134 } else if (response.getResponseType() == ResponseType.RET_FLASH_HW_ERROR) {
135 updateState(channelUID,
136 new StringType("The write/erase/verify process failed"));
137 } else if (response.getResponseType() == ResponseType.RET_BASEID_OUT_OF_RANGE) {
138 updateState(channelUID, new StringType("Base id out of range"));
139 } else if (response.getResponseType() == ResponseType.RET_BASEID_MAX_REACHED) {
140 updateState(channelUID, new StringType("No more change possible"));
144 } catch (IllegalArgumentException e) {
145 updateState(channelUID, new StringType("BaseId could not be parsed"));
156 public void initialize() {
157 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "trying to connect to gateway...");
158 if (this.serialPortManager == null) {
159 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
160 "SerialPortManager could not be found");
162 Object devId = getConfig().get(NEXTSENDERID);
164 nextSenderId = ((BigDecimal) devId).intValue();
169 if (connectorTask == null || connectorTask.isDone()) {
170 connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() {
173 if (thing.getStatus() != ThingStatus.ONLINE) {
177 }, 0, 60, TimeUnit.SECONDS);
182 private synchronized void initTransceiver() {
184 EnOceanBridgeConfig c = getThing().getConfiguration().as(EnOceanBridgeConfig.class);
185 if (transceiver != null) {
186 transceiver.ShutDown();
189 switch (c.getESPVersion()) {
191 transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager);
194 transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager);
200 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "opening serial port...");
201 transceiver.Initialize();
203 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread...");
204 transceiver.StartReceiving(scheduler);
207 if (c.rs485BaseId != null && !c.rs485BaseId.isEmpty()) {
208 baseId = HexUtils.hexToBytes(c.rs485BaseId);
209 if (baseId.length != 4) {
210 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
211 "RS485 BaseId has the wrong format. It is expected to be an 8 digit hex code, for example 01000000");
214 baseId = new byte[4];
217 updateProperty(PROPERTY_BASE_ID, HexUtils.bytesToHex(baseId));
218 updateStatus(ThingStatus.ONLINE);
220 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
221 "trying to get bridge base id...");
223 logger.debug("request base id");
224 transceiver.sendBasePacket(ESP3PacketFactory.CO_RD_IDBASE,
225 new ResponseListenerIgnoringTimeouts<RDBaseIdResponse>() {
227 public void responseReceived(RDBaseIdResponse response) {
228 logger.debug("received response for base id");
229 if (response.isValid() && response.isOK()) {
230 baseId = response.getBaseId().clone();
231 updateProperty(PROPERTY_BASE_ID, HexUtils.bytesToHex(response.getBaseId()));
232 updateProperty(PROPERTY_REMAINING_WRITE_CYCLES_Base_ID,
233 Integer.toString(response.getRemainingWriteCycles()));
234 transceiver.setFilteredDeviceId(baseId);
236 updateStatus(ThingStatus.ONLINE);
238 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
239 "Could not get BaseId");
245 logger.debug("request version info");
246 transceiver.sendBasePacket(ESP3PacketFactory.CO_RD_VERSION,
247 new ResponseListenerIgnoringTimeouts<RDVersionResponse>() {
249 public void responseReceived(RDVersionResponse response) {
250 if (response.isValid() && response.isOK()) {
251 updateProperty(PROPERTY_APP_VERSION, response.getAPPVersion());
252 updateProperty(PROPERTY_API_VERSION, response.getAPIVersion());
253 updateProperty(PROPERTY_CHIP_ID, response.getChipID());
254 updateProperty(PROPERTY_DESCRIPTION, response.getDescription());
258 } catch (IOException e) {
259 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port could not be found");
260 } catch (PortInUseException e) {
261 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port already in use");
262 } catch (Exception e) {
263 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port could not be initialized");
269 public synchronized void dispose() {
270 if (transceiver != null) {
271 transceiver.ShutDown();
275 if (connectorTask != null && !connectorTask.isDone()) {
276 connectorTask.cancel(true);
277 connectorTask = null;
284 public Collection<ConfigStatusMessage> getConfigStatus() {
285 Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<>();
287 // The serial port must be provided
288 String path = (String) getThing().getConfiguration().get(PATH);
289 if (path == null || path.isEmpty()) {
290 configStatusMessages.add(ConfigStatusMessage.Builder.error(PATH)
291 .withMessageKeySuffix(EnOceanConfigStatusMessage.PORT_MISSING.getMessageKey()).withArguments(PATH)
295 return configStatusMessages;
298 public byte[] getBaseId() {
299 return baseId.clone();
302 public int getNextSenderId(Thing sender) {
303 // TODO: change id to enoceanId
304 return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
307 public int getNextSenderId(String senderId) {
308 if (nextSenderId != 0 && sendingThings[nextSenderId] == null) {
309 int result = nextSenderId;
310 Configuration config = getConfig();
311 config.put(NEXTSENDERID, null);
312 updateConfiguration(config);
318 for (byte i = 1; i < sendingThings.length; i++) {
319 if (sendingThings[i] == null || sendingThings[i].getConfiguration().as(EnOceanBaseConfig.class).enoceanId
320 .equalsIgnoreCase(senderId)) {
328 public boolean existsSender(int id, Thing sender) {
329 return sendingThings[id] != null && !sendingThings[id].getConfiguration().as(EnOceanBaseConfig.class).enoceanId
330 .equalsIgnoreCase(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
333 public void addSender(int id, Thing thing) {
334 sendingThings[id] = thing;
337 public void removeSender(int id) {
338 sendingThings[id] = null;
341 public <T extends Response> void sendMessage(BasePacket message, ResponseListener<T> responseListener) {
343 transceiver.sendBasePacket(message, responseListener);
344 } catch (IOException e) {
345 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
349 public void addPacketListener(PacketListener listener) {
350 addPacketListener(listener, listener.getSenderIdToListenTo());
353 public void addPacketListener(PacketListener listener, long senderIdToListenTo) {
354 if (transceiver != null) {
355 transceiver.addPacketListener(listener, senderIdToListenTo);
359 public void removePacketListener(PacketListener listener) {
360 removePacketListener(listener, listener.getSenderIdToListenTo());
363 public void removePacketListener(PacketListener listener, long senderIdToListenTo) {
364 if (transceiver != null) {
365 transceiver.removePacketListener(listener, senderIdToListenTo);
369 public void startDiscovery(PacketListener teachInListener) {
370 transceiver.startDiscovery(teachInListener);
373 public void stopDiscovery() {
374 transceiver.stopDiscovery();
378 public void ErrorOccured(Throwable exception) {
379 transceiver.ShutDown();
381 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage());