]> git.basschouten.com Git - openhab-addons.git/blob
534e3a2208a54cbf268a277303ae84a95626f080
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.phc.internal.PHCBindingConstants;
30 import org.openhab.binding.phc.internal.PHCHelper;
31 import org.openhab.binding.phc.internal.util.StringUtils;
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                 int size = (sizeToggle & 0x7F);
214
215                 if (!isSizeToggleValid(sizeToggle, module)) {
216                     if (logger.isDebugEnabled()) {
217                         logger.debug("get invalid sizeToggle: {}", new String(HexUtils.byteToHex(sizeToggle)));
218                     }
219
220                     module = sizeToggle;
221                     continue;
222                 }
223
224                 // read toggle, size and command
225                 boolean toggle = (sizeToggle & 0x80) == 0x80;
226
227                 logger.debug("get toggle: {}", toggle);
228
229                 byte[] command = new byte[size];
230
231                 for (int i = 0; i < size; i++) {
232                     command[i] = buffer.get();
233                 }
234
235                 // log command
236                 if (logger.isTraceEnabled()) {
237                     logger.trace("command read: {}", PHCHelper.bytesToBinaryString(command));
238                 }
239
240                 // read crc
241                 byte crcByte1 = buffer.get();
242                 byte crcByte2 = buffer.get();
243
244                 short crc = (short) (crcByte1 & 0xFF);
245                 crc |= (crcByte2 << 8);
246
247                 // calculate checkCrc
248                 short checkCrc = calcCrc(module, sizeToggle, command);
249
250                 // check crc
251                 if (crc != checkCrc) {
252                     logger.debug("CRC not correct (crc from message, calculated crc): {}, {}", crc, checkCrc);
253
254                     faultCounter = handleCrcFault(faultCounter);
255
256                     module = buffer.get();
257                     continue;
258                 }
259
260                 if (logger.isTraceEnabled()) {
261                     logger.trace("get crc: {}", HexUtils.bytesToHex(new byte[] { crcByte1, crcByte2 }, " "));
262                 }
263
264                 faultCounter = 0;
265
266                 processReceivedMsg(module, toggle, command);
267                 module = buffer.get();
268             }
269         } catch (InterruptedException e) {
270             Thread.currentThread().interrupt();
271         }
272     }
273
274     private boolean isSizeToggleValid(byte sizeToggle, byte module) {
275         int unsigned = sizeToggle & 0xFF;
276
277         if (unsigned > 0 && unsigned < 4) {
278             return true;
279         } else if (unsigned > 0x80 && unsigned < 0x84) {
280             return true;
281         } else if ((module & 0xE0) == 0x00) {
282             if (unsigned > 0 && unsigned < 16) {
283                 return true;
284             } else if (unsigned > 0x80 && unsigned < 0x90) {
285                 return true;
286             }
287         }
288
289         return false;
290     }
291
292     private int handleCrcFault(int faultCounter) throws InterruptedException {
293         if (faultCounter > 0) {
294             // Normally in this case we read the message repeatedly offset to the real -> skip one to 6 bytes
295             for (int i = 0; i < faultCounter; i++) {
296                 if (buffer.hasNext()) {
297                     buffer.get();
298                 }
299             }
300         }
301
302         int resCounter = faultCounter + 1;
303         if (resCounter > 6) {
304             resCounter = 0;
305         }
306         return resCounter;
307     }
308
309     private void processReceivedMsg(byte module, boolean toggle, byte[] command) {
310         // Acknowledgement received (command first byte 0)
311         if (command[0] == 0) {
312             String moduleType;
313             byte channel = 0; // only needed for dim
314             if ((module & 0xE0) == 0x40) {
315                 moduleType = PHCBindingConstants.CHANNELS_AM;
316             } else if ((module & 0xE0) == 0xA0) {
317                 moduleType = PHCBindingConstants.CHANNELS_DIM;
318                 channel = (byte) ((command[0] >>> 5) & 0x0F);
319             } else {
320                 moduleType = PHCBindingConstants.CHANNELS_EM_LED;
321             }
322
323             setModuleOutputState(moduleType, (byte) (module & 0x1F), command[1], channel);
324             toggleMap.put(module, !toggle);
325
326             // initialization (first byte FF)
327         } else if (command[0] == (byte) 0xFF) {
328             if ((module & 0xE0) == 0x00) { // EM
329                 sendEmConfig(module);
330             } else if ((module & 0xE0) == 0x40 || (module & 0xE0) == 0xA0) { // AM, JRM and DIM
331                 sendAmConfig(module);
332             }
333
334             logger.debug("initialization: {}", module);
335
336             // ignored - ping (first byte 01)
337         } else if (command[0] == 0x01) {
338             logger.debug("first byte 0x01 -> ignored");
339
340             // EM command / update
341         } else {
342             if ((module & 0xE0) == 0x00) {
343                 sendEmAcknowledge(module, toggle);
344                 logger.debug("send acknowledge (modul, toggle) {} {}", module, toggle);
345
346                 for (byte cmdByte : command) {
347                     byte channel = (byte) ((cmdByte >>> 4) & 0x0F);
348
349                     OnOffType onOff = OnOffType.OFF;
350
351                     byte cmd = (byte) (cmdByte & 0x0F);
352                     if (cmd % 2 == 0) {
353                         if (cmd == 2) {
354                             onOff = OnOffType.ON;
355                         } else {
356                             logger.debug("Command {} isn't implemented for EM", cmd);
357                             continue;
358                         }
359                     }
360
361                     QueueObject qo = new QueueObject(PHCBindingConstants.CHANNELS_EM, module, channel, onOff);
362
363                     // put recognized message into queue
364                     if (!receiveQueue.contains(qo)) {
365                         receiveQueue.offer(qo);
366                     }
367                 }
368
369                 // ignore if message not from EM module
370             } else if (logger.isDebugEnabled()) {
371                 logger.debug("Incoming message (module, toggle, command) not from EM module: {} {} {}",
372                         new String(HexUtils.byteToHex(module)), toggle, PHCHelper.bytesToBinaryString(command));
373             }
374         }
375     }
376
377     /**
378      * process receive queue
379      */
380     private void processReceiveQueue() {
381         while (true) {
382             try {
383                 QueueObject qo = receiveQueue.take();
384
385                 logger.debug("Consume Receive QueueObject: {}", qo);
386                 handleIncomingCommand(qo.getModuleAddress(), qo.getChannel(), (OnOffType) qo.getCommand());
387             } catch (InterruptedException e) {
388                 Thread.currentThread().interrupt();
389             }
390         }
391     }
392
393     /**
394      * process send queue
395      */
396     private void processSendQueue() {
397         while (true) {
398             try {
399                 QueueObject qo = sendQueue.take();
400
401                 sendQueueObject(qo);
402             } catch (InterruptedException e1) {
403                 Thread.currentThread().interrupt();
404             }
405         }
406     }
407
408     private void sendQueueObject(QueueObject qo) {
409         int sendCount = 0;
410         // Send the command to the module until a response is received. Max. SEND_RETRY_COUNT repeats.
411         do {
412             switch (qo.getModuleType()) {
413                 case PHCBindingConstants.CHANNELS_AM:
414                     sendAm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand());
415                     break;
416                 case PHCBindingConstants.CHANNELS_EM_LED:
417                     sendEm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand());
418                     break;
419                 case PHCBindingConstants.CHANNELS_JRM:
420                     sendJrm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand(), qo.getTime());
421                     break;
422                 case PHCBindingConstants.CHANNELS_DIM:
423                     sendDim(qo.getModuleAddress(), qo.getChannel(), qo.getCommand(), qo.getTime());
424                     break;
425             }
426
427             sendCount++;
428             try {
429                 Thread.sleep(SEND_RETRY_TIME_MILLIS);
430             } catch (InterruptedException e) {
431                 Thread.currentThread().interrupt();
432             }
433         } while (!isChannelOutputState(qo.getModuleType(), qo.getModuleAddress(), qo.getChannel(), qo.getCommand())
434                 && sendCount < SEND_RETRY_COUNT);
435
436         if (PHCBindingConstants.CHANNELS_JRM.equals(qo.getModuleType())) {
437             // there aren't state per channel for JRM modules
438             amOutputState[qo.getModuleAddress() & 0x1F] = -1;
439         } else if (PHCBindingConstants.CHANNELS_DIM.equals(qo.getModuleType())) {
440             // state ist the same for every dim level except zero/off -> inizialize state
441             // with 0x0F after sending a command.
442             dmOutputState[qo.getModuleAddress() & 0x1F] |= (0x0F << (qo.getChannel() * 4));
443         }
444
445         if (sendCount >= SEND_RETRY_COUNT) {
446             // change the toggle: if no acknowledge received it may be wrong.
447             byte module = qo.getModuleAddress();
448             if (PHCBindingConstants.CHANNELS_AM.equals(qo.getModuleType())
449                     || PHCBindingConstants.CHANNELS_JRM.equals(qo.getModuleType())) {
450                 module |= 0x40;
451             } else if (PHCBindingConstants.CHANNELS_DIM.equals(qo.getModuleType())) {
452                 module |= 0xA0;
453             }
454             toggleMap.put(module, !getToggle(module));
455
456             if (logger.isDebugEnabled()) {
457                 logger.debug("No acknowledge from the module {} received.", qo.getModuleAddress());
458             }
459         }
460     }
461
462     private void setModuleOutputState(String moduleType, byte moduleAddress, byte state, byte channel) {
463         if (PHCBindingConstants.CHANNELS_EM_LED.equals(moduleType)) {
464             emLedOutputState[moduleAddress] = state;
465         } else if (PHCBindingConstants.CHANNELS_AM.equals(moduleType)) {
466             amOutputState[moduleAddress & 0x1F] = state;
467         } else if (PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
468             dmOutputState[moduleAddress & 0x1F] = (byte) (state << channel * 4);
469         }
470     }
471
472     private boolean isChannelOutputState(String moduleType, byte moduleAddress, byte channel, Command cmd) {
473         int state = OnOffType.OFF.equals(cmd) ? 0 : 1;
474
475         if (PHCBindingConstants.CHANNELS_EM_LED.equals(moduleType)) {
476             return ((emLedOutputState[moduleAddress & 0x1F] >>> channel) & 0x01) == state;
477         } else if (PHCBindingConstants.CHANNELS_AM.equals(moduleType)) {
478             return ((amOutputState[moduleAddress & 0x1F] >>> channel) & 0x01) == state;
479         } else if (PHCBindingConstants.CHANNELS_JRM.equals(moduleType)) {
480             return (amOutputState[moduleAddress & 0x1F] != -1);
481         } else if (PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
482             return ((dmOutputState[moduleAddress & 0x1F] >>> channel * 4) & 0x0F) != 0x0F;
483         } else {
484             return false;
485         }
486     }
487
488     private boolean getToggle(byte moduleAddress) {
489         if (!toggleMap.containsKey(moduleAddress)) {
490             toggleMap.put(moduleAddress, false);
491         }
492
493         return toggleMap.get(moduleAddress);
494     }
495
496     /**
497      * Put the given command into the queue to send.
498      *
499      * @param moduleType
500      * @param moduleAddress
501      * @param channel
502      * @param command
503      * @param upDownTime
504      */
505     public void send(@Nullable String moduleType, int moduleAddress, String channel, Command command,
506             short upDownTime) {
507         if (PHCBindingConstants.CHANNELS_JRM.equals(moduleType)
508                 || PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
509             sendQueue.offer(new QueueObject(moduleType, moduleAddress, channel, command, upDownTime));
510         } else {
511             sendQueue.offer(new QueueObject(moduleType, moduleAddress, channel, command));
512         }
513     }
514
515     private void sendAm(byte moduleAddress, byte channel, Command command) {
516         byte module = (byte) (moduleAddress | 0x40);
517
518         byte[] cmd = { (byte) (channel << 5) };
519
520         if (OnOffType.ON.equals(command)) {
521             cmd[0] |= 2;
522         } else {
523             cmd[0] |= 3;
524         }
525         serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
526     }
527
528     private void sendEm(byte moduleAddress, byte channel, Command command) {
529         byte[] cmd = { (byte) (channel << 4) };
530
531         if (OnOffType.ON.equals(command)) {
532             cmd[0] |= 2;
533         } else {
534             cmd[0] |= 3;
535         }
536         serialWrite(buildMessage(moduleAddress, channel, cmd, getToggle(moduleAddress)));
537     }
538
539     private void sendJrm(byte moduleAddress, byte channel, Command command, short upDownTime) {
540         // The up and the down message needs two additional bytes for the time.
541         int size = (command == StopMoveType.STOP) ? 2 : 4;
542         byte[] cmd = new byte[size];
543         if (channel == 0) {
544             channel = 4;
545         }
546
547         byte module = (byte) (moduleAddress | 0x40);
548
549         cmd[0] = (byte) (channel << 5);
550         cmd[1] = 0x3F;
551
552         switch (command.toString()) {
553             case "UP":
554                 cmd[0] |= 5;
555                 cmd[2] = (byte) (upDownTime & 0xFF);// Time 1/10 sec. LSB
556                 cmd[3] = (byte) ((upDownTime >> 8) & 0xFF); // 1/10 sec. MSB
557                 break;
558             case "DOWN":
559                 cmd[0] |= 6;
560                 cmd[2] = (byte) (upDownTime & 0xFF);// Time 1/10 sec. LSB
561                 cmd[3] = (byte) ((upDownTime >> 8) & 0xFF); // 1/10 sec. MSB
562                 break;
563             case "STOP":
564                 cmd[0] |= 2;
565                 break;
566         }
567
568         serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
569     }
570
571     private void sendDim(byte moduleAddress, byte channel, Command command, short dimTime) {
572         byte module = (byte) (moduleAddress | 0xA0);
573         byte[] cmd = new byte[(command instanceof PercentType percentCommand && percentCommand.byteValue() != 0) ? 3
574                 : 1];
575
576         cmd[0] = (byte) (channel << 5);
577
578         if (command instanceof OnOffType) {
579             if (OnOffType.ON.equals(command)) {
580                 cmd[0] |= 3;
581             } else if (OnOffType.OFF.equals(command)) {
582                 cmd[0] |= 4;
583             }
584         } else {
585             if (((PercentType) command).byteValue() == 0) {
586                 cmd[0] |= 4;
587             } else {
588                 cmd[0] |= 22;
589                 cmd[1] = (byte) (((PercentType) command).byteValue() * 2.55);
590                 cmd[2] = (byte) dimTime;
591             }
592         }
593         serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
594     }
595
596     private void sendPorBroadcast() {
597         byte[] msg = buildMessage((byte) 0xFF, 0, new byte[] { 0 }, false);
598         for (int i = 0; i < 20; i++) {
599             serialWrite(msg);
600
601         }
602     }
603
604     private void sendAmConfig(byte moduleAddress) {
605         byte[] cmd = new byte[3];
606
607         cmd[0] = (byte) 0xFE;
608         cmd[1] = 0;
609         cmd[2] = (byte) 0xFF;
610
611         serialWrite(buildMessage(moduleAddress, 0, cmd, false));
612     }
613
614     private void sendEmConfig(byte moduleAddress) {
615         byte[] cmd = new byte[52];
616         int pos = 0;
617
618         cmd[pos++] = (byte) 0xFE;
619         cmd[pos++] = (byte) 0x00; // POR
620
621         cmd[pos++] = 0x00;
622         cmd[pos++] = 0x00;
623
624         for (int i = 0; i < 16; i++) { // 16 inputs
625             cmd[pos++] = (byte) ((i << 4) | 0x02);
626             cmd[pos++] = (byte) ((i << 4) | 0x03);
627             cmd[pos++] = (byte) ((i << 4) | 0x05);
628         }
629
630         serialWrite(buildMessage(moduleAddress, 0, cmd, false));
631     }
632
633     private void sendEmAcknowledge(byte module, boolean toggle) {
634         byte[] msg = buildMessage(module, 0, new byte[] { 0 }, toggle);
635         for (int i = 0; i < 3; i++) { // send three times stops the module faster from sending messages if the first
636                                       // response is not recognized.
637             serialWrite(msg);
638         }
639     }
640
641     /**
642      * Build a serial message from the given parameters.
643      *
644      * @param modulAddr
645      * @param channel
646      * @param cmd
647      * @param toggle
648      * @return
649      */
650     private byte[] buildMessage(byte modulAddr, int channel, byte[] cmd, boolean toggle) {
651         int len = cmd.length;
652         byte[] buffer = new byte[len + 4];
653
654         buffer[0] = modulAddr;
655         buffer[1] = (byte) (toggle ? (len | 0x80) : len); // 0x80: 1000 0000
656
657         System.arraycopy(cmd, 0, buffer, 2, len);
658
659         short crc = calcCrc(modulAddr, buffer[1], cmd);
660
661         buffer[2 + len] = (byte) (crc & 0xFF);
662         buffer[3 + len] = (byte) ((crc >> 8) & 0xFF);
663
664         return buffer;
665     }
666
667     /**
668      * Calculate the 16 bit crc of the message.
669      *
670      * @param module
671      * @param sizeToggle
672      * @param cmd
673      * @return
674      */
675     private short calcCrc(byte module, byte sizeToggle, byte[] cmd) {
676         short crc = (short) 0xFFFF;
677
678         crc = crc16Update(crc, module);
679         crc = crc16Update(crc, sizeToggle);
680
681         for (byte b : cmd) {
682             crc = crc16Update(crc, b);
683         }
684
685         crc ^= 0xFFFF;
686         return crc;
687     }
688
689     /**
690      * Update the 16 bit crc of the message.
691      *
692      * @param crc
693      * @param data
694      * @return
695      */
696     private short crc16Update(short crc, byte messagePart) {
697         byte data = (byte) (messagePart ^ (crc & 0xFF));
698         data ^= data << 4;
699         short data16 = data;
700
701         return (short) (((data16 << 8) | (((crc >> 8) & 0xFF) & 0xFF)) ^ ((data >> 4) & 0xF)
702                 ^ ((data16 << 3) & 0b11111111111));
703     }
704
705     /**
706      * Send the incoming command to the appropriate handler and channel.
707      *
708      * @param moduleAddress
709      * @param channel
710      * @param cmd
711      * @param rcvCrc
712      */
713     private void handleIncomingCommand(byte moduleAddress, int channel, OnOffType onOff) {
714         ThingUID uid = PHCHelper.getThingUIDreverse(PHCBindingConstants.THING_TYPE_EM, moduleAddress);
715         Thing thing = getThing().getThing(uid);
716         String channelId = "em#" + StringUtils.padLeft(Integer.toString(channel), 2, "0");
717
718         if (thing != null && thing.getHandler() != null) {
719             logger.debug("Input: {}, {}, {}", thing.getUID(), channelId, onOff);
720
721             PHCHandler handler = (PHCHandler) thing.getHandler();
722             if (handler != null) {
723                 handler.handleIncoming(channelId, onOff);
724             } else {
725                 logger.debug("No Handler for Thing {} available.", thing.getUID());
726             }
727
728         } else {
729             logger.debug("No Thing with UID {} available.", uid.getAsString());
730         }
731     }
732
733     private void serialWrite(byte[] msg) {
734         if (serialOut != null) {
735             try {
736                 // write to serial port
737                 serialOut.write(msg);
738                 serialOut.flush();
739             } catch (IOException e) {
740                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
741                         "Error writing '" + msg + "' to serial port : " + e.getMessage());
742             }
743
744             if (logger.isTraceEnabled()) {
745                 logger.trace("send: {}", PHCHelper.bytesToBinaryString(msg));
746             }
747         }
748     }
749
750     /**
751      * Adds the given address to the module list.
752      *
753      * @param module
754      */
755     public void addModule(byte module) {
756         modules.add(module);
757     }
758
759     @Override
760     public void handleCommand(ChannelUID channelUID, Command command) {
761         // unnecessary
762     }
763
764     @Override
765     public void dispose() {
766         threadPoolExecutor.shutdownNow();
767         if (commPort != null) {
768             commPort.close();
769             commPort = null;
770         }
771     }
772 }