]> git.basschouten.com Git - openhab-addons.git/blob
d4c0ceb14ee681b1bf7890b4973abfab60781170
[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.phc.internal.handler;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.TooManyListenersException;
23 import java.util.concurrent.BlockingQueue;
24 import java.util.concurrent.LinkedBlockingQueue;
25 import java.util.concurrent.ScheduledThreadPoolExecutor;
26
27 import org.apache.commons.lang3.StringUtils;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.phc.internal.PHCBindingConstants;
31 import org.openhab.binding.phc.internal.PHCHelper;
32 import org.openhab.core.io.transport.serial.PortInUseException;
33 import org.openhab.core.io.transport.serial.SerialPort;
34 import org.openhab.core.io.transport.serial.SerialPortEvent;
35 import org.openhab.core.io.transport.serial.SerialPortEventListener;
36 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
37 import org.openhab.core.io.transport.serial.SerialPortManager;
38 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.PercentType;
41 import org.openhab.core.library.types.StopMoveType;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingUID;
48 import org.openhab.core.thing.binding.BaseBridgeHandler;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.util.HexUtils;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * The {@link PHCBridgeHandler} is responsible for handling the serial Communication to and from the PHC Modules.
56  *
57  * @author Jonas Hohaus - Initial contribution
58  */
59 @NonNullByDefault
60 public class PHCBridgeHandler extends BaseBridgeHandler implements SerialPortEventListener {
61
62     private final Logger logger = LoggerFactory.getLogger(PHCBridgeHandler.class);
63
64     private static final int BAUD = 19200;
65     private static final int SEND_RETRY_COUNT = 20; // max count to send the same message
66     private static final int SEND_RETRY_TIME_MILLIS = 60; // time to wait for an acknowledge before send the message
67                                                           // again in milliseconds
68
69     private @Nullable InputStream serialIn;
70     private @Nullable OutputStream serialOut;
71     private @Nullable SerialPort commPort;
72     private final SerialPortManager serialPortManager;
73
74     private final Map<Byte, Boolean> toggleMap = new HashMap<>();
75     private final InternalBuffer buffer = new InternalBuffer();
76     private final BlockingQueue<QueueObject> receiveQueue = new LinkedBlockingQueue<>();
77     private final BlockingQueue<QueueObject> sendQueue = new LinkedBlockingQueue<>();
78     private final ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(3);
79
80     private final byte emLedOutputState[] = new byte[32];
81     private final byte amOutputState[] = new byte[32];
82     private final byte dmOutputState[] = new byte[32];
83
84     private final List<Byte> modules = new ArrayList<>();
85
86     public PHCBridgeHandler(Bridge phcBridge, SerialPortManager serialPortManager) {
87         super(phcBridge);
88         this.serialPortManager = serialPortManager;
89     }
90
91     @Override
92     public void initialize() {
93         String port = ((String) getConfig().get(PHCBindingConstants.PORT));
94
95         // find the given port
96         SerialPortIdentifier portId = serialPortManager.getIdentifier(port);
97
98         if (portId == null) {
99             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
100                     "Serial port '" + port + "' could not be found.");
101             return;
102         }
103
104         try {
105             // initialize serial port
106             SerialPort serialPort = portId.open(this.getClass().getName(), 2000); // owner, timeout
107             serialIn = serialPort.getInputStream();
108             // set port parameters
109             serialPort.setSerialPortParams(BAUD, SerialPort.DATABITS_8, SerialPort.STOPBITS_2, SerialPort.PARITY_NONE);
110             serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
111
112             serialPort.addEventListener(this);
113             // activate the DATA_AVAILABLE notifier
114             serialPort.notifyOnDataAvailable(true);
115
116             // get the output stream
117             serialOut = serialPort.getOutputStream();
118
119             commPort = serialPort;
120
121             sendPorBroadcast();
122
123             byte[] b = { 0x01 };
124             for (int j = 0; j <= 0x1F; j++) {
125                 serialWrite(buildMessage((byte) j, 0, b, false));
126             }
127             updateStatus(ThingStatus.ONLINE);
128
129             // receive messages
130             threadPoolExecutor.execute(new Runnable() {
131
132                 @Override
133                 public void run() {
134                     processReceivedBytes();
135                 }
136             });
137
138             // process received messages
139             threadPoolExecutor.execute(new Runnable() {
140
141                 @Override
142                 public void run() {
143                     processReceiveQueue();
144                 }
145             });
146
147             // sendig commands to the modules
148             threadPoolExecutor.execute(new Runnable() {
149
150                 @Override
151                 public void run() {
152                     processSendQueue();
153                 }
154             });
155         } catch (PortInUseException | TooManyListenersException e) {
156             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
157                     "Could not open serial port " + port + ": " + e.getMessage());
158         } catch (UnsupportedCommOperationException e) {
159             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
160                     "Could not configure serial port " + port + ": " + e.getMessage());
161         } catch (IOException e) {
162             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
163                     "Failed to get input or output stream for serialPort: " + e.getMessage());
164             logger.debug("Failed to get inputstream for serialPort", e);
165         }
166     }
167
168     /**
169      * Reads the data on serial port and puts it into the internal buffer.
170      */
171     @Override
172     public void serialEvent(SerialPortEvent event) {
173         if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE && serialIn != null) {
174             try {
175                 byte[] bytes = new byte[serialIn.available()];
176                 serialIn.read(bytes);
177
178                 buffer.offer(bytes);
179
180                 if (logger.isTraceEnabled()) {
181                     logger.trace("buffer offered {}", HexUtils.bytesToHex(bytes, " "));
182                 }
183             } catch (IOException e) {
184                 logger.warn("Error on reading input stream to internal buffer", e);
185             }
186         }
187     }
188
189     /**
190      * process internal incoming buffer (recognize on read messages)
191      */
192     private void processReceivedBytes() {
193         int faultCounter = 0;
194
195         try {
196             byte module = buffer.get();
197
198             while (true) {
199                 // Recognition of messages from byte buffer.
200                 // not a known module address
201                 if (!modules.contains(module)) {
202                     module = buffer.get();
203                     continue;
204                 }
205
206                 if (logger.isDebugEnabled()) {
207                     logger.debug("get module: {}", new String(HexUtils.byteToHex(module)));
208                 }
209
210                 byte sizeToggle = buffer.get();
211
212                 // read length of command and check if makes sense
213                 if ((sizeToggle < 1 || sizeToggle > 3) && ((sizeToggle & 0xFF) < 0x81 || (sizeToggle & 0xFF) > 0x83)) {
214                     if (logger.isDebugEnabled()) {
215                         logger.debug("get invalid sizeToggle: {}", new String(HexUtils.byteToHex(sizeToggle)));
216                     }
217
218                     module = sizeToggle;
219                     continue;
220                 }
221
222                 // read toggle, size and command
223                 int size = (sizeToggle & 0x7F);
224                 boolean toggle = (sizeToggle & 0x80) == 0x80;
225
226                 logger.debug("get toggle: {}", toggle);
227
228                 byte[] command = new byte[size];
229
230                 for (int i = 0; i < size; i++) {
231                     command[i] = buffer.get();
232                 }
233
234                 // log command
235                 if (logger.isTraceEnabled()) {
236                     logger.trace("command read: {}", PHCHelper.bytesToBinaryString(command));
237                 }
238
239                 // read crc
240                 byte crcByte1 = buffer.get();
241                 byte crcByte2 = buffer.get();
242
243                 short crc = (short) (crcByte1 & 0xFF);
244                 crc |= (crcByte2 << 8);
245
246                 // calculate checkCrc
247                 short checkCrc = calcCrc(module, sizeToggle, command);
248
249                 // check crc
250                 if (crc != checkCrc) {
251                     logger.debug("CRC not correct (crc from message, calculated crc): {}, {}", crc, checkCrc);
252
253                     faultCounter = handleCrcFault(faultCounter);
254
255                     module = buffer.get();
256                     continue;
257                 }
258
259                 if (logger.isTraceEnabled()) {
260                     logger.trace("get crc: {}", HexUtils.bytesToHex(new byte[] { crcByte1, crcByte2 }, " "));
261                 }
262
263                 faultCounter = 0;
264
265                 processReceivedMsg(module, toggle, command);
266                 module = buffer.get();
267             }
268         } catch (InterruptedException e) {
269             Thread.currentThread().interrupt();
270         }
271     }
272
273     private int handleCrcFault(int faultCounter) throws InterruptedException {
274         if (faultCounter > 0) {
275             // Normally in this case we read the message repeatedly offset to the real -> skip one to 6 bytes
276             for (int i = 0; i < faultCounter; i++) {
277                 if (buffer.hasNext()) {
278                     buffer.get();
279                 }
280             }
281         }
282
283         int resCounter = faultCounter + 1;
284         if (resCounter > 6) {
285             resCounter = 0;
286         }
287         return resCounter;
288     }
289
290     private void processReceivedMsg(byte module, boolean toggle, byte[] command) {
291         // Acknowledgement received (command first byte 0)
292         if (command[0] == 0) {
293             String moduleType;
294             byte channel = 0; // only needed for dim
295             if ((module & 0xE0) == 0x40) {
296                 moduleType = PHCBindingConstants.CHANNELS_AM;
297             } else if ((module & 0xE0) == 0xA0) {
298                 moduleType = PHCBindingConstants.CHANNELS_DIM;
299                 channel = (byte) ((command[0] >>> 5) & 0x0F);
300             } else {
301                 moduleType = PHCBindingConstants.CHANNELS_EM_LED;
302             }
303
304             setModuleOutputState(moduleType, (byte) (module & 0x1F), command[1], channel);
305             toggleMap.put(module, !toggle);
306
307             // initialization (first byte FF)
308         } else if (command[0] == (byte) 0xFF) {
309             if ((module & 0xE0) == 0x00) { // EM
310                 sendEmConfig(module);
311             } else if ((module & 0xE0) == 0x40 || (module & 0xE0) == 0xA0) { // AM, JRM and DIM
312                 sendAmConfig(module);
313             }
314
315             logger.debug("initialization: {}", module);
316
317             // ignored - ping (first byte 01)
318         } else if (command[0] == 0x01) {
319             logger.debug("first byte 0x01 -> ignored");
320
321             // EM command / update
322         } else {
323             if (((module & 0xE0) == 0x00)) {
324                 sendEmAcknowledge(module, toggle);
325                 logger.debug("send acknowledge (modul, toggle) {} {}", module, toggle);
326
327                 byte channel = (byte) ((command[0] >>> 4) & 0x0F);
328
329                 OnOffType onOff = OnOffType.OFF;
330
331                 if ((command[0] & 0x0F) == 2) {
332                     onOff = OnOffType.ON;
333                 }
334
335                 QueueObject qo = new QueueObject(PHCBindingConstants.CHANNELS_EM, module, channel, onOff);
336
337                 // put recognized message into queue
338                 if (!receiveQueue.contains(qo)) {
339                     receiveQueue.offer(qo);
340                 }
341
342                 // ignore if message not from EM module
343             } else if (logger.isDebugEnabled()) {
344                 logger.debug("Incoming message (module, toggle, command) not from EM module: {} {} {}",
345                         new String(HexUtils.byteToHex(module)), toggle, PHCHelper.bytesToBinaryString(command));
346             }
347         }
348     }
349
350     /**
351      * process receive queue
352      */
353     private void processReceiveQueue() {
354         while (true) {
355             try {
356                 QueueObject qo = receiveQueue.take();
357
358                 logger.debug("Consume Receive QueueObject: {}", qo);
359                 handleIncomingCommand(qo.getModuleAddress(), qo.getChannel(), (OnOffType) qo.getCommand());
360             } catch (InterruptedException e) {
361                 Thread.currentThread().interrupt();
362             }
363         }
364     }
365
366     /**
367      * process send queue
368      */
369     private void processSendQueue() {
370         while (true) {
371             try {
372                 QueueObject qo = sendQueue.take();
373
374                 sendQueueObject(qo);
375             } catch (InterruptedException e1) {
376                 Thread.currentThread().interrupt();
377             }
378         }
379     }
380
381     private void sendQueueObject(QueueObject qo) {
382         int sendCount = 0;
383         // Send the command to the module until a response is received. Max. SEND_RETRY_COUNT repeats.
384         do {
385             switch (qo.getModuleType()) {
386                 case PHCBindingConstants.CHANNELS_AM:
387                     sendAm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand());
388                     break;
389                 case PHCBindingConstants.CHANNELS_EM_LED:
390                     sendEm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand());
391                     break;
392                 case PHCBindingConstants.CHANNELS_JRM:
393                     sendJrm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand(), qo.getTime());
394                     break;
395                 case PHCBindingConstants.CHANNELS_DIM:
396                     sendDim(qo.getModuleAddress(), qo.getChannel(), qo.getCommand(), qo.getTime());
397                     break;
398             }
399
400             sendCount++;
401             try {
402                 Thread.sleep(SEND_RETRY_TIME_MILLIS);
403             } catch (InterruptedException e) {
404                 Thread.currentThread().interrupt();
405             }
406         } while (!isChannelOutputState(qo.getModuleType(), qo.getModuleAddress(), qo.getChannel(), qo.getCommand())
407                 && sendCount < SEND_RETRY_COUNT);
408
409         if (PHCBindingConstants.CHANNELS_JRM.equals(qo.getModuleType())) {
410             // there aren't state per channel for JRM modules
411             amOutputState[qo.getModuleAddress() & 0x1F] = -1;
412         } else if (PHCBindingConstants.CHANNELS_DIM.equals(qo.getModuleType())) {
413             // state ist the same for every dim level except zero/off -> inizialize state
414             // with 0x0F after sending an command.
415             dmOutputState[qo.getModuleAddress() & 0x1F] |= (0x0F << (qo.getChannel() * 4));
416         }
417
418         if (sendCount >= SEND_RETRY_COUNT) {
419             // change the toggle: if no acknowledge received it may be wrong.
420             byte module = qo.getModuleAddress();
421             if (PHCBindingConstants.CHANNELS_AM.equals(qo.getModuleType())
422                     || PHCBindingConstants.CHANNELS_JRM.equals(qo.getModuleType())) {
423                 module |= 0x40;
424             } else if (PHCBindingConstants.CHANNELS_DIM.equals(qo.getModuleType())) {
425                 module |= 0xA0;
426             }
427             toggleMap.put(module, !getToggle(module));
428
429             if (logger.isDebugEnabled()) {
430                 logger.debug("No acknowledge from the module {} received.", qo.getModuleAddress());
431             }
432         }
433     }
434
435     private void setModuleOutputState(String moduleType, byte moduleAddress, byte state, byte channel) {
436         if (PHCBindingConstants.CHANNELS_EM_LED.equals(moduleType)) {
437             emLedOutputState[moduleAddress] = state;
438         } else if (PHCBindingConstants.CHANNELS_AM.equals(moduleType)) {
439             amOutputState[moduleAddress & 0x1F] = state;
440         } else if (PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
441             dmOutputState[moduleAddress & 0x1F] = (byte) (state << channel * 4);
442         }
443     }
444
445     private boolean isChannelOutputState(String moduleType, byte moduleAddress, byte channel, Command cmd) {
446         int state = OnOffType.OFF.equals(cmd) ? 0 : 1;
447
448         if (PHCBindingConstants.CHANNELS_EM_LED.equals(moduleType)) {
449             return ((emLedOutputState[moduleAddress & 0x1F] >>> channel) & 0x01) == state;
450         } else if (PHCBindingConstants.CHANNELS_AM.equals(moduleType)) {
451             return ((amOutputState[moduleAddress & 0x1F] >>> channel) & 0x01) == state;
452         } else if (PHCBindingConstants.CHANNELS_JRM.equals(moduleType)) {
453             return (amOutputState[moduleAddress & 0x1F] != -1);
454         } else if (PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
455             return ((dmOutputState[moduleAddress & 0x1F] >>> channel * 4) & 0x0F) != 0x0F;
456         } else {
457             return false;
458         }
459     }
460
461     private boolean getToggle(byte moduleAddress) {
462         if (!toggleMap.containsKey(moduleAddress)) {
463             toggleMap.put(moduleAddress, false);
464         }
465
466         return toggleMap.get(moduleAddress);
467     }
468
469     /**
470      * Put the given command into the queue to send.
471      *
472      * @param moduleType
473      * @param moduleAddress
474      * @param channel
475      * @param command
476      * @param upDownTime
477      */
478     public void send(@Nullable String moduleType, int moduleAddress, String channel, Command command,
479             short upDownTime) {
480         if (PHCBindingConstants.CHANNELS_JRM.equals(moduleType)
481                 || PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
482             sendQueue.offer(new QueueObject(moduleType, moduleAddress, channel, command, upDownTime));
483         } else {
484             sendQueue.offer(new QueueObject(moduleType, moduleAddress, channel, command));
485         }
486     }
487
488     private void sendAm(byte moduleAddress, byte channel, Command command) {
489         byte module = (byte) (moduleAddress | 0x40);
490
491         byte[] cmd = { (byte) (channel << 5) };
492
493         if (OnOffType.ON.equals(command)) {
494             cmd[0] |= 2;
495         } else {
496             cmd[0] |= 3;
497         }
498         serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
499     }
500
501     private void sendEm(byte moduleAddress, byte channel, Command command) {
502         byte[] cmd = { (byte) (channel << 4) };
503
504         if (OnOffType.ON.equals(command)) {
505             cmd[0] |= 2;
506         } else {
507             cmd[0] |= 3;
508         }
509         serialWrite(buildMessage(moduleAddress, channel, cmd, getToggle(moduleAddress)));
510     }
511
512     private void sendJrm(byte moduleAddress, byte channel, Command command, short upDownTime) {
513         // The up and the down message needs two additional bytes for the time.
514         int size = (command == StopMoveType.STOP) ? 2 : 4;
515         byte[] cmd = new byte[size];
516         if (channel == 0) {
517             channel = 4;
518         }
519
520         byte module = (byte) (moduleAddress | 0x40);
521
522         cmd[0] = (byte) (channel << 5);
523         cmd[1] = 0x3F;
524
525         switch (command.toString()) {
526             case "UP":
527                 cmd[0] |= 5;
528                 cmd[2] = (byte) (upDownTime & 0xFF);// Time 1/10 sec. LSB
529                 cmd[3] = (byte) ((upDownTime >> 8) & 0xFF); // 1/10 sec. MSB
530                 break;
531             case "DOWN":
532                 cmd[0] |= 6;
533                 cmd[2] = (byte) (upDownTime & 0xFF);// Time 1/10 sec. LSB
534                 cmd[3] = (byte) ((upDownTime >> 8) & 0xFF); // 1/10 sec. MSB
535                 break;
536             case "STOP":
537                 cmd[0] |= 2;
538                 break;
539         }
540
541         serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
542     }
543
544     private void sendDim(byte moduleAddress, byte channel, Command command, short dimTime) {
545         byte module = (byte) (moduleAddress | 0xA0);
546         byte[] cmd = new byte[(command instanceof PercentType && !(((PercentType) command).byteValue() == 0)) ? 3 : 1];
547
548         cmd[0] = (byte) (channel << 5);
549
550         if (command instanceof OnOffType) {
551             if (OnOffType.ON.equals(command)) {
552                 cmd[0] |= 3;
553             } else if (OnOffType.OFF.equals(command)) {
554                 cmd[0] |= 4;
555             }
556         } else {
557             if (((PercentType) command).byteValue() == 0) {
558                 cmd[0] |= 4;
559             } else {
560                 cmd[0] |= 22;
561                 cmd[1] = (byte) (((PercentType) command).byteValue() * 2.55);
562                 cmd[2] = (byte) dimTime;
563             }
564         }
565         serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
566     }
567
568     private void sendPorBroadcast() {
569         byte[] msg = buildMessage((byte) 0xFF, 0, new byte[] { 0 }, false);
570         for (int i = 0; i < 20; i++) {
571             serialWrite(msg);
572
573         }
574     }
575
576     private void sendAmConfig(byte moduleAddress) {
577         byte[] cmd = new byte[3];
578
579         cmd[0] = (byte) 0xFE;
580         cmd[1] = 0;
581         cmd[2] = (byte) 0xFF;
582
583         serialWrite(buildMessage(moduleAddress, 0, cmd, false));
584     }
585
586     private void sendEmConfig(byte moduleAddress) {
587         byte[] cmd = new byte[52];
588         int pos = 0;
589
590         cmd[pos++] = (byte) 0xFE;
591         cmd[pos++] = (byte) 0x00; // POR
592
593         cmd[pos++] = 0x00;
594         cmd[pos++] = 0x00;
595
596         for (int i = 0; i < 16; i++) { // 16 inputs
597             cmd[pos++] = (byte) ((i << 4) | 0x02);
598             cmd[pos++] = (byte) ((i << 4) | 0x03);
599             cmd[pos++] = (byte) ((i << 4) | 0x05);
600         }
601
602         serialWrite(buildMessage(moduleAddress, 0, cmd, false));
603     }
604
605     private void sendEmAcknowledge(byte module, boolean toggle) {
606         byte[] msg = buildMessage(module, 0, new byte[] { 0 }, toggle);
607         for (int i = 0; i < 3; i++) { // send three times stops the module faster from sending messages if the first
608                                       // response is not recognized.
609             serialWrite(msg);
610         }
611     }
612
613     /**
614      * Build a serial message from the given parameters.
615      *
616      * @param modulAddr
617      * @param channel
618      * @param cmd
619      * @param toggle
620      * @return
621      */
622     private byte[] buildMessage(byte modulAddr, int channel, byte[] cmd, boolean toggle) {
623         int len = cmd.length;
624         byte[] buffer = new byte[len + 4];
625
626         buffer[0] = modulAddr;
627         buffer[1] = (byte) (toggle ? (len | 0x80) : len); // 0x80: 1000 0000
628
629         System.arraycopy(cmd, 0, buffer, 2, len);
630
631         short crc = calcCrc(modulAddr, buffer[1], cmd);
632
633         buffer[2 + len] = (byte) (crc & 0xFF);
634         buffer[3 + len] = (byte) ((crc >> 8) & 0xFF);
635
636         return buffer;
637     }
638
639     /**
640      * Calculate the 16 bit crc of the message.
641      *
642      * @param module
643      * @param sizeToggle
644      * @param cmd
645      * @return
646      */
647     private short calcCrc(byte module, byte sizeToggle, byte[] cmd) {
648         short crc = (short) 0xFFFF;
649
650         crc = crc16Update(crc, module);
651         crc = crc16Update(crc, sizeToggle);
652
653         for (byte b : cmd) {
654             crc = crc16Update(crc, b);
655         }
656
657         crc ^= 0xFFFF;
658         return crc;
659     }
660
661     /**
662      * Update the 16 bit crc of the message.
663      *
664      * @param crc
665      * @param data
666      * @return
667      */
668     private short crc16Update(short crc, byte messagePart) {
669         byte data = (byte) (messagePart ^ (crc & 0xFF));
670         data ^= data << 4;
671         short data16 = data;
672
673         return (short) (((data16 << 8) | (((crc >> 8) & 0xFF) & 0xFF)) ^ ((data >> 4) & 0xF)
674                 ^ ((data16 << 3) & 0b11111111111));
675     }
676
677     /**
678      * Send the incoming command to the appropriate handler and channel.
679      *
680      * @param moduleAddress
681      * @param channel
682      * @param cmd
683      * @param rcvCrc
684      */
685     private void handleIncomingCommand(byte moduleAddress, int channel, OnOffType onOff) {
686         ThingUID uid = PHCHelper.getThingUIDreverse(PHCBindingConstants.THING_TYPE_EM, moduleAddress);
687         Thing thing = getThing().getThing(uid);
688         String channelId = "em#" + StringUtils.leftPad(Integer.toString(channel), 2, '0');
689
690         if (thing != null && thing.getHandler() != null) {
691             logger.debug("Input: {}, {}, {}", thing.getUID(), channelId, onOff);
692
693             PHCHandler handler = (PHCHandler) thing.getHandler();
694             if (handler != null) {
695                 handler.handleIncoming(channelId, onOff);
696             } else {
697                 logger.debug("No Handler for Thing {} available.", thing.getUID());
698             }
699
700         } else {
701             logger.debug("No Thing with UID {} available.", uid.getAsString());
702         }
703     }
704
705     private void serialWrite(byte[] msg) {
706         if (serialOut != null) {
707             try {
708                 // write to serial port
709                 serialOut.write(msg);
710                 serialOut.flush();
711             } catch (IOException e) {
712                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
713                         "Error writing '" + msg + "' to serial port : " + e.getMessage());
714             }
715
716             if (logger.isTraceEnabled()) {
717                 logger.trace("send: {}", PHCHelper.bytesToBinaryString(msg));
718             }
719         }
720     }
721
722     /**
723      * Adds the given address to the module list.
724      *
725      * @param module
726      */
727     public void addModule(byte module) {
728         modules.add(module);
729     }
730
731     @Override
732     public void handleCommand(ChannelUID channelUID, Command command) {
733         // unnecessary
734     }
735
736     @Override
737     public void dispose() {
738         threadPoolExecutor.shutdownNow();
739         if (commPort != null) {
740             commPort.close();
741             commPort = null;
742         }
743     }
744 }