]> git.basschouten.com Git - openhab-addons.git/blob
9fc1c5eee4dca8a68d2f9fae3bb3dc511a354daf
[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.Arrays;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.LinkedList;
23 import java.util.Set;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
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;
62
63 /**
64  * The {@link EnOceanBridgeHandler} is responsible for sending ESP3Packages build by {@link EnOceanActuatorHandler} and
65  * transferring received ESP3Packages to {@link EnOceanSensorHandler}.
66  *
67  * @author Daniel Weber - Initial contribution
68  */
69 public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements TransceiverErrorListener {
70
71     private Logger logger = LoggerFactory.getLogger(EnOceanBridgeHandler.class);
72
73     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_BRIDGE));
74
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
77
78     private byte[] baseId = null;
79     private Thing[] sendingThings = new Thing[128];
80
81     private int nextSenderId = 0;
82     private SerialPortManager serialPortManager;
83
84     public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
85         super(bridge);
86         this.serialPortManager = serialPortManager;
87     }
88
89     @Override
90     public void handleCommand(ChannelUID channelUID, Command command) {
91         if (transceiver == null) {
92             updateStatus(ThingStatus.OFFLINE);
93             return;
94         }
95
96         switch (channelUID.getId()) {
97             case CHANNEL_REPEATERMODE:
98                 if (command instanceof RefreshType) {
99                     sendMessage(ESP3PacketFactory.CO_RD_REPEATER,
100                             new ResponseListenerIgnoringTimeouts<RDRepeaterResponse>() {
101                                 @Override
102                                 public void responseReceived(RDRepeaterResponse response) {
103                                     if (response.isValid() && response.isOK()) {
104                                         updateState(channelUID, response.getRepeaterLevel());
105                                     } else {
106                                         updateState(channelUID, new StringType(REPEATERMODE_OFF));
107                                     }
108                                 }
109                             });
110                 } else if (command instanceof StringType) {
111                     sendMessage(ESP3PacketFactory.CO_WR_REPEATER((StringType) command),
112                             new ResponseListenerIgnoringTimeouts<BaseResponse>() {
113                                 @Override
114                                 public void responseReceived(BaseResponse response) {
115                                     if (response.isOK()) {
116                                         updateState(channelUID, (StringType) command);
117                                     }
118                                 }
119                             });
120                 }
121                 break;
122
123             case CHANNEL_SETBASEID:
124                 if (command instanceof StringType) {
125                     try {
126                         byte[] id = HexUtils.hexToBytes(((StringType) command).toFullString());
127
128                         sendMessage(ESP3PacketFactory.CO_WR_IDBASE(id),
129                                 new ResponseListenerIgnoringTimeouts<BaseResponse>() {
130                                     @Override
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"));
141                                         }
142                                     }
143                                 });
144                     } catch (IllegalArgumentException e) {
145                         updateState(channelUID, new StringType("BaseId could not be parsed"));
146                     }
147                 }
148                 break;
149
150             default:
151                 break;
152         }
153     }
154
155     @Override
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");
161         } else {
162             Object devId = getConfig().get(NEXTSENDERID);
163             if (devId != null) {
164                 nextSenderId = ((BigDecimal) devId).intValue();
165             } else {
166                 nextSenderId = 0;
167             }
168
169             if (connectorTask == null || connectorTask.isDone()) {
170                 connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() {
171                     @Override
172                     public void run() {
173                         if (thing.getStatus() != ThingStatus.ONLINE) {
174                             initTransceiver();
175                         }
176                     }
177                 }, 0, 60, TimeUnit.SECONDS);
178             }
179         }
180     }
181
182     private synchronized void initTransceiver() {
183         try {
184             EnOceanBridgeConfig c = getThing().getConfiguration().as(EnOceanBridgeConfig.class);
185             if (transceiver != null) {
186                 transceiver.ShutDown();
187             }
188
189             switch (c.getESPVersion()) {
190                 case ESP2:
191                     transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager);
192                     break;
193                 case ESP3:
194                     transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager);
195                     break;
196                 default:
197                     break;
198             }
199
200             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "opening serial port...");
201             transceiver.Initialize();
202
203             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread...");
204             transceiver.StartReceiving(scheduler);
205
206             if (c.rs485) {
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");
212                     }
213                 } else {
214                     baseId = new byte[4];
215                 }
216
217                 updateProperty(PROPERTY_BASE_ID, HexUtils.bytesToHex(baseId));
218                 updateStatus(ThingStatus.ONLINE);
219             } else {
220                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
221                         "trying to get bridge base id...");
222
223                 logger.debug("request base id");
224                 transceiver.sendBasePacket(ESP3PacketFactory.CO_RD_IDBASE,
225                         new ResponseListenerIgnoringTimeouts<RDBaseIdResponse>() {
226                             @Override
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);
235
236                                     updateStatus(ThingStatus.ONLINE);
237                                 } else {
238                                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
239                                             "Could not get BaseId");
240                                 }
241                             }
242                         });
243             }
244
245             logger.debug("request version info");
246             transceiver.sendBasePacket(ESP3PacketFactory.CO_RD_VERSION,
247                     new ResponseListenerIgnoringTimeouts<RDVersionResponse>() {
248                         @Override
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());
255                             }
256                         }
257                     });
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");
264             return;
265         }
266     }
267
268     @Override
269     public synchronized void dispose() {
270         if (transceiver != null) {
271             transceiver.ShutDown();
272             transceiver = null;
273         }
274
275         if (connectorTask != null && !connectorTask.isDone()) {
276             connectorTask.cancel(true);
277             connectorTask = null;
278         }
279
280         super.dispose();
281     }
282
283     @Override
284     public Collection<ConfigStatusMessage> getConfigStatus() {
285         Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<>();
286
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)
292                     .build());
293         }
294
295         return configStatusMessages;
296     }
297
298     public byte[] getBaseId() {
299         return baseId.clone();
300     }
301
302     public int getNextSenderId(Thing sender) {
303         // TODO: change id to enoceanId
304         return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
305     }
306
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);
313             nextSenderId = 0;
314
315             return result;
316         }
317
318         for (byte i = 1; i < sendingThings.length; i++) {
319             if (sendingThings[i] == null || sendingThings[i].getConfiguration().as(EnOceanBaseConfig.class).enoceanId
320                     .equalsIgnoreCase(senderId)) {
321                 return i;
322             }
323         }
324
325         return -1;
326     }
327
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);
331     }
332
333     public void addSender(int id, Thing thing) {
334         sendingThings[id] = thing;
335     }
336
337     public void removeSender(int id) {
338         sendingThings[id] = null;
339     }
340
341     public <T extends Response> void sendMessage(BasePacket message, ResponseListener<T> responseListener) {
342         try {
343             transceiver.sendBasePacket(message, responseListener);
344         } catch (IOException e) {
345             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
346         }
347     }
348
349     public void addPacketListener(PacketListener listener) {
350         addPacketListener(listener, listener.getSenderIdToListenTo());
351     }
352
353     public void addPacketListener(PacketListener listener, long senderIdToListenTo) {
354         if (transceiver != null) {
355             transceiver.addPacketListener(listener, senderIdToListenTo);
356         }
357     }
358
359     public void removePacketListener(PacketListener listener) {
360         removePacketListener(listener, listener.getSenderIdToListenTo());
361     }
362
363     public void removePacketListener(PacketListener listener, long senderIdToListenTo) {
364         if (transceiver != null) {
365             transceiver.removePacketListener(listener, senderIdToListenTo);
366         }
367     }
368
369     public void startDiscovery(PacketListener teachInListener) {
370         transceiver.startDiscovery(teachInListener);
371     }
372
373     public void stopDiscovery() {
374         transceiver.stopDiscovery();
375     }
376
377     @Override
378     public void ErrorOccured(Throwable exception) {
379         transceiver.ShutDown();
380         transceiver = null;
381         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage());
382     }
383 }