]> git.basschouten.com Git - openhab-addons.git/blob
c888f6a60be2d0ad2a7c93f4441b929ded7f64ce
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.io.IOException;
18 import java.math.BigDecimal;
19 import java.util.Collection;
20 import java.util.LinkedList;
21 import java.util.Set;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import org.openhab.binding.enocean.internal.EnOceanConfigStatusMessage;
26 import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
27 import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig;
28 import org.openhab.binding.enocean.internal.messages.BasePacket;
29 import org.openhab.binding.enocean.internal.messages.BaseResponse;
30 import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory;
31 import org.openhab.binding.enocean.internal.messages.RDBaseIdResponse;
32 import org.openhab.binding.enocean.internal.messages.RDRepeaterResponse;
33 import org.openhab.binding.enocean.internal.messages.RDVersionResponse;
34 import org.openhab.binding.enocean.internal.messages.Response;
35 import org.openhab.binding.enocean.internal.messages.Response.ResponseType;
36 import org.openhab.binding.enocean.internal.transceiver.EnOceanESP2Transceiver;
37 import org.openhab.binding.enocean.internal.transceiver.EnOceanESP3Transceiver;
38 import org.openhab.binding.enocean.internal.transceiver.EnOceanTransceiver;
39 import org.openhab.binding.enocean.internal.transceiver.PacketListener;
40 import org.openhab.binding.enocean.internal.transceiver.ResponseListener;
41 import org.openhab.binding.enocean.internal.transceiver.ResponseListenerIgnoringTimeouts;
42 import org.openhab.binding.enocean.internal.transceiver.TransceiverErrorListener;
43 import org.openhab.core.config.core.Configuration;
44 import org.openhab.core.config.core.status.ConfigStatusMessage;
45 import org.openhab.core.io.transport.serial.PortInUseException;
46 import org.openhab.core.io.transport.serial.SerialPortManager;
47 import org.openhab.core.library.types.StringType;
48 import org.openhab.core.thing.Bridge;
49 import org.openhab.core.thing.ChannelUID;
50 import org.openhab.core.thing.Thing;
51 import org.openhab.core.thing.ThingStatus;
52 import org.openhab.core.thing.ThingStatusDetail;
53 import org.openhab.core.thing.ThingTypeUID;
54 import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
55 import org.openhab.core.types.Command;
56 import org.openhab.core.types.RefreshType;
57 import org.openhab.core.util.HexUtils;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 /**
62  * The {@link EnOceanBridgeHandler} is responsible for sending ESP3Packages build by {@link EnOceanActuatorHandler} and
63  * transferring received ESP3Packages to {@link EnOceanSensorHandler}.
64  *
65  * @author Daniel Weber - Initial contribution
66  */
67 public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements TransceiverErrorListener {
68
69     private Logger logger = LoggerFactory.getLogger(EnOceanBridgeHandler.class);
70
71     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE);
72
73     private EnOceanTransceiver transceiver; // holds connection to serial/tcp port and sends/receives messages
74     private ScheduledFuture<?> connectorTask; // is used for reconnection if something goes wrong
75
76     private byte[] baseId = null;
77     private Thing[] sendingThings = new Thing[128];
78
79     private int nextSenderId = 0;
80     private SerialPortManager serialPortManager;
81
82     public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
83         super(bridge);
84         this.serialPortManager = serialPortManager;
85     }
86
87     @Override
88     public void handleCommand(ChannelUID channelUID, Command command) {
89         if (transceiver == null) {
90             updateStatus(ThingStatus.OFFLINE);
91             return;
92         }
93
94         switch (channelUID.getId()) {
95             case CHANNEL_REPEATERMODE:
96                 if (command instanceof RefreshType) {
97                     sendMessage(ESP3PacketFactory.CO_RD_REPEATER,
98                             new ResponseListenerIgnoringTimeouts<RDRepeaterResponse>() {
99                                 @Override
100                                 public void responseReceived(RDRepeaterResponse response) {
101                                     if (response.isValid() && response.isOK()) {
102                                         updateState(channelUID, response.getRepeaterLevel());
103                                     } else {
104                                         updateState(channelUID, new StringType(REPEATERMODE_OFF));
105                                     }
106                                 }
107                             });
108                 } else if (command instanceof StringType) {
109                     sendMessage(ESP3PacketFactory.CO_WR_REPEATER((StringType) command),
110                             new ResponseListenerIgnoringTimeouts<BaseResponse>() {
111                                 @Override
112                                 public void responseReceived(BaseResponse response) {
113                                     if (response.isOK()) {
114                                         updateState(channelUID, (StringType) command);
115                                     }
116                                 }
117                             });
118                 }
119                 break;
120
121             case CHANNEL_SETBASEID:
122                 if (command instanceof StringType) {
123                     try {
124                         byte[] id = HexUtils.hexToBytes(((StringType) command).toFullString());
125
126                         sendMessage(ESP3PacketFactory.CO_WR_IDBASE(id),
127                                 new ResponseListenerIgnoringTimeouts<BaseResponse>() {
128                                     @Override
129                                     public void responseReceived(BaseResponse response) {
130                                         if (response.isOK()) {
131                                             updateState(channelUID, new StringType("New Id successfully set"));
132                                         } else if (response.getResponseType() == ResponseType.RET_FLASH_HW_ERROR) {
133                                             updateState(channelUID,
134                                                     new StringType("The write/erase/verify process failed"));
135                                         } else if (response.getResponseType() == ResponseType.RET_BASEID_OUT_OF_RANGE) {
136                                             updateState(channelUID, new StringType("Base id out of range"));
137                                         } else if (response.getResponseType() == ResponseType.RET_BASEID_MAX_REACHED) {
138                                             updateState(channelUID, new StringType("No more change possible"));
139                                         }
140                                     }
141                                 });
142                     } catch (IllegalArgumentException e) {
143                         updateState(channelUID, new StringType("BaseId could not be parsed"));
144                     }
145                 }
146                 break;
147
148             default:
149                 break;
150         }
151     }
152
153     @Override
154     public void initialize() {
155         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "trying to connect to gateway...");
156         if (this.serialPortManager == null) {
157             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
158                     "SerialPortManager could not be found");
159         } else {
160             Object devId = getConfig().get(NEXTSENDERID);
161             if (devId != null) {
162                 nextSenderId = ((BigDecimal) devId).intValue();
163             } else {
164                 nextSenderId = 0;
165             }
166
167             if (connectorTask == null || connectorTask.isDone()) {
168                 connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() {
169                     @Override
170                     public void run() {
171                         if (thing.getStatus() != ThingStatus.ONLINE) {
172                             initTransceiver();
173                         }
174                     }
175                 }, 0, 60, TimeUnit.SECONDS);
176             }
177         }
178     }
179
180     private synchronized void initTransceiver() {
181         try {
182             EnOceanBridgeConfig c = getThing().getConfiguration().as(EnOceanBridgeConfig.class);
183             if (transceiver != null) {
184                 transceiver.ShutDown();
185             }
186
187             switch (c.getESPVersion()) {
188                 case ESP2:
189                     transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager);
190                     break;
191                 case ESP3:
192                     transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager);
193                     break;
194                 default:
195                     break;
196             }
197
198             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "opening serial port...");
199             transceiver.Initialize();
200
201             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread...");
202             transceiver.StartReceiving(scheduler);
203
204             if (c.rs485) {
205                 if (c.rs485BaseId != null && !c.rs485BaseId.isEmpty()) {
206                     baseId = HexUtils.hexToBytes(c.rs485BaseId);
207                     if (baseId.length != 4) {
208                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
209                                 "RS485 BaseId has the wrong format. It is expected to be an 8 digit hex code, for example 01000000");
210                     }
211                 } else {
212                     baseId = new byte[4];
213                 }
214
215                 updateProperty(PROPERTY_BASE_ID, HexUtils.bytesToHex(baseId));
216                 updateStatus(ThingStatus.ONLINE);
217             } else {
218                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
219                         "trying to get bridge base id...");
220
221                 logger.debug("request base id");
222                 transceiver.sendBasePacket(ESP3PacketFactory.CO_RD_IDBASE,
223                         new ResponseListenerIgnoringTimeouts<RDBaseIdResponse>() {
224                             @Override
225                             public void responseReceived(RDBaseIdResponse response) {
226                                 logger.debug("received response for base id");
227                                 if (response.isValid() && response.isOK()) {
228                                     baseId = response.getBaseId().clone();
229                                     updateProperty(PROPERTY_BASE_ID, HexUtils.bytesToHex(response.getBaseId()));
230                                     updateProperty(PROPERTY_REMAINING_WRITE_CYCLES_Base_ID,
231                                             Integer.toString(response.getRemainingWriteCycles()));
232                                     transceiver.setFilteredDeviceId(baseId);
233
234                                     updateStatus(ThingStatus.ONLINE);
235                                 } else {
236                                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
237                                             "Could not get BaseId");
238                                 }
239                             }
240                         });
241             }
242
243             logger.debug("request version info");
244             transceiver.sendBasePacket(ESP3PacketFactory.CO_RD_VERSION,
245                     new ResponseListenerIgnoringTimeouts<RDVersionResponse>() {
246                         @Override
247                         public void responseReceived(RDVersionResponse response) {
248                             if (response.isValid() && response.isOK()) {
249                                 updateProperty(PROPERTY_APP_VERSION, response.getAPPVersion());
250                                 updateProperty(PROPERTY_API_VERSION, response.getAPIVersion());
251                                 updateProperty(PROPERTY_CHIP_ID, response.getChipID());
252                                 updateProperty(PROPERTY_DESCRIPTION, response.getDescription());
253                             }
254                         }
255                     });
256         } catch (IOException e) {
257             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port could not be found");
258         } catch (PortInUseException e) {
259             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port already in use");
260         } catch (Exception e) {
261             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Port could not be initialized");
262             return;
263         }
264     }
265
266     @Override
267     public synchronized void dispose() {
268         if (transceiver != null) {
269             transceiver.ShutDown();
270             transceiver = null;
271         }
272
273         if (connectorTask != null && !connectorTask.isDone()) {
274             connectorTask.cancel(true);
275             connectorTask = null;
276         }
277
278         super.dispose();
279     }
280
281     @Override
282     public Collection<ConfigStatusMessage> getConfigStatus() {
283         Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<>();
284
285         // The serial port must be provided
286         String path = (String) getThing().getConfiguration().get(PATH);
287         if (path == null || path.isEmpty()) {
288             configStatusMessages.add(ConfigStatusMessage.Builder.error(PATH)
289                     .withMessageKeySuffix(EnOceanConfigStatusMessage.PORT_MISSING.getMessageKey()).withArguments(PATH)
290                     .build());
291         }
292
293         return configStatusMessages;
294     }
295
296     public byte[] getBaseId() {
297         return baseId.clone();
298     }
299
300     public int getNextSenderId(Thing sender) {
301         // TODO: change id to enoceanId
302         return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
303     }
304
305     public int getNextSenderId(String senderId) {
306         if (nextSenderId != 0 && sendingThings[nextSenderId] == null) {
307             int result = nextSenderId;
308             Configuration config = getConfig();
309             config.put(NEXTSENDERID, null);
310             updateConfiguration(config);
311             nextSenderId = 0;
312
313             return result;
314         }
315
316         for (byte i = 1; i < sendingThings.length; i++) {
317             if (sendingThings[i] == null || sendingThings[i].getConfiguration().as(EnOceanBaseConfig.class).enoceanId
318                     .equalsIgnoreCase(senderId)) {
319                 return i;
320             }
321         }
322
323         return -1;
324     }
325
326     public boolean existsSender(int id, Thing sender) {
327         return sendingThings[id] != null && !sendingThings[id].getConfiguration().as(EnOceanBaseConfig.class).enoceanId
328                 .equalsIgnoreCase(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
329     }
330
331     public void addSender(int id, Thing thing) {
332         sendingThings[id] = thing;
333     }
334
335     public void removeSender(int id) {
336         sendingThings[id] = null;
337     }
338
339     public <T extends Response> void sendMessage(BasePacket message, ResponseListener<T> responseListener) {
340         try {
341             transceiver.sendBasePacket(message, responseListener);
342         } catch (IOException e) {
343             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
344         }
345     }
346
347     public void addPacketListener(PacketListener listener) {
348         addPacketListener(listener, listener.getSenderIdToListenTo());
349     }
350
351     public void addPacketListener(PacketListener listener, long senderIdToListenTo) {
352         if (transceiver != null) {
353             transceiver.addPacketListener(listener, senderIdToListenTo);
354         }
355     }
356
357     public void removePacketListener(PacketListener listener) {
358         removePacketListener(listener, listener.getSenderIdToListenTo());
359     }
360
361     public void removePacketListener(PacketListener listener, long senderIdToListenTo) {
362         if (transceiver != null) {
363             transceiver.removePacketListener(listener, senderIdToListenTo);
364         }
365     }
366
367     public void startDiscovery(PacketListener teachInListener) {
368         transceiver.startDiscovery(teachInListener);
369     }
370
371     public void stopDiscovery() {
372         transceiver.stopDiscovery();
373     }
374
375     @Override
376     public void ErrorOccured(Throwable exception) {
377         transceiver.ShutDown();
378         transceiver = null;
379         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage());
380     }
381 }