001package jmri.jmrix.dcc4pc;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Enumeration;
006import java.util.HashMap;
007import java.util.List;
008import java.util.concurrent.ConcurrentHashMap;
009import javax.annotation.Nonnull;
010import jmri.JmriException;
011import jmri.Sensor;
012
013/**
014 * Implement SensorManager for Dcc4Pc systems. The Manager handles all the state
015 * changes.
016 * <p>
017 * System names are "DSnn:yy", where D is the user configurable system prefix,
018 * nn is the board id and yy is the port on that board.
019 *
020 * @author Kevin Dickerson Copyright (C) 2009
021 */
022public class Dcc4PcSensorManager extends jmri.managers.AbstractSensorManager
023        implements Dcc4PcListener {
024
025    public Dcc4PcSensorManager(Dcc4PcTrafficController tc, Dcc4PcSystemConnectionMemo memo) {
026        super(memo);
027        this.tc = tc;
028        this.reportManager = (Dcc4PcReporterManager) memo.get(jmri.ReporterManager.class);
029        jmri.InstanceManager.store(Dcc4PcSensorManager.this, Dcc4PcSensorManager.class);
030        this.boardManager = new Dcc4PcBoardManager(tc, this);
031        // Finally, create and register a shutdown task to ensure clean exit
032        this.pollShutDownTask = this::stopPolling;
033        startPolling();
034    }
035
036    Dcc4PcReporterManager reportManager;
037    Runnable pollShutDownTask;
038    Dcc4PcBoardManager boardManager;
039
040    Dcc4PcTrafficController tc;
041
042    @Override
043    public Dcc4PcSensor getSensor(@Nonnull String name) {
044        return (Dcc4PcSensor) super.getSensor(name);
045
046    }
047
048    /**
049     * {@inheritDoc}
050     */
051    @Override
052    @Nonnull
053    public Dcc4PcSystemConnectionMemo getMemo() {
054        return (Dcc4PcSystemConnectionMemo) memo;
055    }
056
057    /**
058     * {@inheritDoc}
059     */
060    @Override
061    @Nonnull
062    protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
063        Sensor s = new Dcc4PcSensor(systemName, userName);
064        s.setUserName(userName);
065        extractBoardID(systemName);
066        return s;
067    }
068
069    /**
070     * This extracts the board id out from the system name.
071     * @param systemName including system prefix and type letter.
072     */
073    void extractBoardID(String systemName) {
074        if (systemName.contains(":")) {
075            int indexOfSplit = systemName.indexOf(":");
076            systemName = systemName.substring(0, indexOfSplit);
077            indexOfSplit = getSystemPrefix().length() + 1; // +1 includes the typeletter which is a char
078            systemName = systemName.substring(indexOfSplit);
079            int boardNo;
080            try {
081                boardNo = Integer.parseInt(systemName);
082            } catch (NumberFormatException ex) {
083                log.error("Unable to find the board address from system name {}", systemName);
084                return;
085            }
086            addBoard(boardNo);
087        }
088    }
089
090    @Override
091    public boolean allowMultipleAdditions(@Nonnull String systemName) {
092        return true;
093    }
094
095    // we want the system name to be in the format of board:input
096    @Override
097    @Nonnull
098    public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
099        String iName;
100        if (curAddress.contains(":")) {
101            int board = 0;
102            int channel;
103            // Address format passed is in the form of board:channel or T:turnout address
104            int seperator = curAddress.indexOf(":");
105            try {
106                board = Integer.parseInt(curAddress.substring(0, seperator));
107            } catch (NumberFormatException ex) {
108                throw new JmriException("Unable to convert "+curAddress+" into the cab and channel format of nn:xx");
109            }
110
111            try {
112                channel = Integer.parseInt(curAddress.substring(seperator + 1));
113                if ((channel > 16) || (channel < 1)) {
114                    throw new JmriException("In Address "+curAddress+" Channel number should be in the range of 1 to 16");
115                }
116            } catch (NumberFormatException ex) {
117                throw new JmriException("Unable to convert "+curAddress+" into the cab and channel format of nn:xx");
118            }
119            iName = curAddress;
120            addBoard(board);
121        } else {
122            throw new JmriException("Unable to convert "+curAddress+" into the cab and channel format of nn:xx");
123        }
124        return prefix + typeLetter() + iName;
125    }
126
127    public void notifyReply(Dcc4PcReply m) {
128        // is this a list of sensors?
129    }
130
131    public void notifyMessage(Dcc4PcMessage m) {
132        // messages are ignored
133    }
134
135    Thread pollThread;
136    boolean stopPolling = true;
137
138    protected void stopPolling() {
139        synchronized (this) {
140            stopPolling = true;
141        }
142        if (pollThread != null) {
143            // we want to wait for the polling thread to finish what it is currently working on.
144            try {
145                pollThread.join();
146            } catch (InterruptedException e) {
147                // Don't need to worry
148            }
149        }
150    }
151
152    final protected void startPolling() {
153        if (stopPolling && pollThread != null) {
154            pollThread = null;
155        }
156        stopPolling = false;
157
158        if (pollThread == null) {
159            pollThread = new Thread(this::pollManager, "DCC4PC Sensor Poll");
160            pollThread.start();
161        }
162    }
163
164    void addBoard(int newBoard) {
165        boardManager.addBoard(newBoard);
166    }
167
168    @Override
169    public void reply(Dcc4PcReply r) {
170        if (log.isDebugEnabled()) {
171            log.debug("Reply details sm: {}", r.toHexString());
172        }
173
174        if (r.getNumDataElements() == 0 && r.getElement(0) == 0x00) {
175            //Simple acknowledgement reply, no further action required
176            return;
177        }
178        if (r.getBoard() == -1) {
179            log.debug("Message is not for a detection board so ignore");
180            return;
181        }
182        if (r.isError()) {
183            log.debug("Reply is in error {}", r.toHexString());
184            synchronized (this) {
185                awaitingReply = false;
186                this.notify();
187            }
188        } else if (!r.isUnsolicited()) {
189            synchronized (this) {
190                awaitingReply = false;
191                this.notify();
192            }
193            if (log.isDebugEnabled()) {
194                log.debug("Get Data inputs {}", r.toHexString());
195            }
196            class ProcessPacket implements Runnable {
197
198                Dcc4PcReply reply;
199
200                ProcessPacket(Dcc4PcReply r) {
201                    reply = r;
202                }
203
204                @Override
205                public void run() {
206                    ActiveBoard curBoard = activeBoards.get(r.getBoard());
207                    if (curBoard != null) {
208                        curBoard.processInputPacket(reply);
209                    } else {
210                        log.error("Board disappeared from system {}", r.getBoard());
211                    }
212                }
213            }
214            if (r.getBoard() > -1) {
215                Thread thr = new Thread(new ProcessPacket(r), "Dcc4PCSensor Process Packet for " + r.getBoard());
216                try {
217                    thr.start();
218                } catch (java.lang.IllegalThreadStateException ex) {
219                    log.error("Exception {} {}", thr.getName(), ex.getMessage());
220                }
221            } else {
222                log.error("Do not know who this board message is for");
223            }
224
225        }
226    }
227
228    // This possibly needs to be handled better
229    void getInputState(int[] longArray, int board) {
230        String sensorPrefix = getSystemPrefix() + typeLetter() + board + ":";
231        String reporterPrefix = getSystemPrefix() + "R" + board + ":";
232        int inputNo = 1; //Maximum number of inputs is 16, but some might not be enabled, so need to handle this some how at a later date
233        for (int i = 0; i < 4; i++) {
234            Dcc4PcSensor s;
235            Dcc4PcReporter r;
236            int state = getInputState(longArray[i], inputNo);
237            s = getSensor(sensorPrefix + (inputNo));
238            if (s != null) {
239                s.setOwnState(state);
240            }
241            r = ((Dcc4PcReporter) reportManager.getReporter(reporterPrefix + (inputNo)));
242            if (r != null) {
243                r.setRailComState(state);
244            }
245            inputNo++;
246
247            state = getInputState(longArray[i], inputNo);
248            s = getSensor(sensorPrefix + (inputNo));
249            if (s != null) {
250                s.setOwnState(state);
251            }
252            r = ((Dcc4PcReporter) reportManager.getReporter(reporterPrefix + (inputNo)));
253            if (r != null) {
254                r.setRailComState(state);
255            }
256            inputNo++;
257
258            state = getInputState(longArray[i], inputNo);
259            s = getSensor(sensorPrefix + (inputNo));
260            if (s != null) {
261                s.setOwnState(state);
262            }
263            r = ((Dcc4PcReporter) reportManager.getReporter(reporterPrefix + (inputNo)));
264            if (r != null) {
265                r.setRailComState(state);
266            }
267            inputNo++;
268
269            state = getInputState(longArray[i], inputNo);
270            s = getSensor(sensorPrefix + (inputNo));
271            if (s != null) {
272                s.setOwnState(state);
273            }
274            r = ((Dcc4PcReporter) reportManager.getReporter(reporterPrefix + (inputNo)));
275            if (r != null) {
276                r.setRailComState(state);
277            }
278            inputNo++;
279        }
280    }
281
282    int getInputState(int value, int input) {
283        int lastbit = 7;
284        switch (input) {
285            case 5:
286            case 9:
287            case 13:
288            case 1:
289                lastbit = 1;
290                break;
291            case 6:
292            case 10:
293            case 14:
294            case 2:
295                lastbit = 3;
296                break;
297            case 7:
298            case 11:
299            case 15:
300            case 3:
301                lastbit = 5;
302                break;
303            case 8:
304            case 12:
305            case 16:
306            case 4:
307                lastbit = 7;
308                break;
309            default:
310                break;
311        }
312        int tempValue = value << (31 - lastbit);
313        switch (tempValue >>> (31 - lastbit + (lastbit - 1))) {
314            case 0:
315                return Sensor.INACTIVE;
316            case 1:
317                return Sensor.ACTIVE;
318            case 2:
319                return Dcc4PcSensor.ORIENTA; //Occupied RailCom Orientation A
320            case 3:
321                return Dcc4PcSensor.ORIENTB; //Occupied RailCom Orientation B
322            default:
323                return Sensor.UNKNOWN;
324        }
325    }
326
327    public static String decodeInputState(int state) {
328        String rtr;
329        switch (state) {
330            case Sensor.INACTIVE:
331                rtr = "UnOccupied";
332                break;
333            case Sensor.ACTIVE:
334                rtr = "Occupied No RailCom Data";
335                break;
336            case Dcc4PcSensor.ORIENTA:
337                rtr = "Occupied RailCom Orientation A";
338                break;
339            case Dcc4PcSensor.ORIENTB:
340                rtr = "Occupied RailCom Orientation B";
341                break;
342            default:
343                rtr = "Unknown";
344        }
345        return rtr;
346    }
347
348    public final static int NO_ADDRESS = 0x00;
349    public final static int SHORT_ADDRESS = 0x02;
350    public final static int LONG_ADDRESS = 0x04;
351    public final static int CONSIST_ADDRESS = 0x08;
352
353    /**
354     * Determine if the Railcom data is duplicated.
355     * If it is then this instructs the Reporter to move things about.
356     * @param value Railcom data
357     * @param seq message sequence number
358     * @param rc Reporter to request action(s) from
359     * @return value calculated locally
360     */
361    int decodeDuplicatePacket(int value, int seq, Dcc4PcReporter rc) {
362        int lastbit = 7;
363        //probably a better way of doing this..
364        while (seq >= 4) {
365            seq = seq - 4;
366        }
367
368        switch (seq) {
369            case 0:
370                lastbit = 1;
371                break;
372            case 1:
373                lastbit = 3;
374                break;
375            case 2:
376                lastbit = 5;
377                break;
378            case 3:
379                lastbit = 7;
380                break;
381            default:
382                break;
383        }
384        int tempValue = value << (31 - lastbit);
385        tempValue = (tempValue >>> (31 - lastbit + (lastbit - 1)));
386        if (tempValue != 0) {
387            rc.duplicatePacket(tempValue);
388        }
389        return tempValue;
390    }
391
392    private final int shortCycleInterval = 1; // Time to wait between sending out poll messages
393    private final int pollTimeout = 600;    // in case of lost response
394    private boolean awaitingReply = false;
395
396    void pollManager() {
397        for (int boardAddress : activeBoards.keySet()) {
398            Dcc4PcMessage m = Dcc4PcMessage.resetBoardData(boardAddress);
399            m.setTimeout(100);
400            tc.sendDcc4PcMessage(m, null);
401        }
402        while (!stopPolling) {
403            if (activeBoards.isEmpty()) {
404                //If we have no boards to poll then wait a second.
405                try {
406                    Thread.sleep(1000);
407                } catch (java.lang.InterruptedException e) {
408                }
409            } else {
410                for (int boardAddress : activeBoards.keySet()) {
411                    if (!activeBoards.get(boardAddress).doNotPoll()) {
412                        log.debug("Poll board {}", boardAddress);
413                        Dcc4PcMessage m = Dcc4PcMessage.pollBoard(boardAddress);
414                        log.debug("queueing poll request for board {}", boardAddress);
415                        tc.sendDcc4PcMessage(m, this);
416                        synchronized (this) {
417                            awaitingReply = true;
418                            try {
419                                wait(pollTimeout);
420                            } catch (InterruptedException e) {
421                                Thread.currentThread().interrupt(); // retain if needed later
422                            }
423                        }
424                        int delay = shortCycleInterval;
425                        synchronized (this) {
426                            if (awaitingReply) {
427                                log.warn("timeout awaiting poll response for board {}", boardAddress);
428                                delay = pollTimeout;
429                            }
430                            try {
431                                wait(delay);
432                            } catch (InterruptedException e) {
433                                Thread.currentThread().interrupt(); // retain if needed later
434                            } finally {
435                                /*awaitingDelay = false;*/
436                            }
437                        }
438                    } else {
439                        log.debug("Board set to Do Not Poll {}", boardAddress);
440                    }
441                    synchronized (this) {
442                        if (stopPolling) {
443                            log.debug("Polling stopped {}", stopPolling);
444                            Thread.currentThread().interrupt();
445                            return;
446                        }
447                    }
448                }
449            }
450        }
451    }
452
453    ConcurrentHashMap<Integer, ActiveBoard> activeBoards = new ConcurrentHashMap<>(5);
454
455    /**
456     * Generate all the sensor and reporter details based upon
457     * the reply message from the board.
458     *
459     * @param r Reply that we build the information from
460     */
461    protected void createSensorsFromReply(Dcc4PcReply r) {
462        int boardAddress = r.getBoard();
463        log.debug("createSensorsFromReply: Get enabled inputs {}", r.toHexString());
464
465        String sensorPrefix = getSystemPrefix() + typeLetter() + boardAddress + ":";
466        String reporterPrefix = getSystemPrefix() + "R" + boardAddress + ":";
467
468        int x = 1;
469        for (int i = 0; i < r.getNumDataElements(); i++) {
470
471            for (int j = 0; j < 8; j++) {
472                Dcc4PcSensor s = (Dcc4PcSensor) createNewSensor(sensorPrefix + (j + x), null);
473                register(s);
474                s.setInput(j + x);
475                activeBoards.get(boardAddress).addSensor(j + x, s);
476                if ((r.getElement(i) & 0x01) == 0x01) {
477                    s.setEnabled(true);
478                }
479                Dcc4PcReporter report = (Dcc4PcReporter) reportManager.createNewReporter(reporterPrefix + (j + x), null);
480                activeBoards.get(boardAddress).addReporter(j + x, report);
481
482            }
483            x = x + 8;
484        }
485        activeBoards.get(boardAddress).setDoNotPoll(false);
486        log.debug("     created {} sensors", x);
487    }
488
489    @Override
490    public void handleTimeout(Dcc4PcMessage m) {
491        log.debug("timeout received to our last message {}", m);
492        if (!stopPolling) {
493            synchronized (this) {
494                awaitingReply = false;
495                this.notify();
496            }
497        }
498    }
499
500    @Override
501    public void message(Dcc4PcMessage m) {
502
503    }
504
505    public void changeBoardAddress(int oldAddress, int newAddress) {
506        // Block polling on this board
507        ActiveBoard board = activeBoards.get(oldAddress);
508        board.setDoNotPoll(true);
509        Dcc4PcMessage m = new jmri.jmrix.dcc4pc.Dcc4PcMessage(new byte[]{(byte) 0x0b, (byte) oldAddress, (byte) 0x03, (byte) newAddress});
510        tc.sendDcc4PcMessage(m, null);
511        // Need to stop polling otherwise we get a concurrent modification exception
512        stopPolling();
513        activeBoards.remove(oldAddress);
514        activeBoards.put(newAddress, board);
515        board.setAddress(newAddress);
516        startPolling();
517        String sensorPrefix = getSystemPrefix() + typeLetter() + newAddress + ":";
518        String reporterPrefix = getSystemPrefix() + "R" + newAddress + ":";
519        // We create a new set of sensors and reporters, but leave the old ones.
520        for (int i = 1; i < board.getNumEnabledSensors() + 1; i++) {
521            board.getSensorAtIndex(i).setEnabled(false);
522            int input = board.getSensorAtIndex(i).getInput();
523            Dcc4PcSensor s = (Dcc4PcSensor) createNewSensor(sensorPrefix + (input), null);
524            register(s);
525            s.setInput(input);
526            s.setEnabled(true);
527            board.addSensor(input, s);
528            Dcc4PcReporter report = (Dcc4PcReporter) reportManager.createNewReporter(reporterPrefix + (input), null);
529            board.addReporter(input, report);
530        }
531        // Need to update the sensors used.
532        board.setDoNotPoll(false);
533    }
534
535    class ActiveBoard {
536
537        ActiveBoard(int address, String version, int inputs, int encoding) {
538            this.version = version;
539            this.inputs = inputs;
540            this.encoding = encoding;
541            this.address = address;
542        }
543
544        void setAddress(int address) {
545            this.address = address;
546        }
547
548        int inputs;
549        int encoding;
550        String version;
551        String description;
552        int address;
553
554        int failedRequests = 0;
555
556        void addFailedRequests() {
557            failedRequests++;
558        }
559
560        void clearFailedRequests() {
561            failedRequests = 0;
562        }
563
564        boolean doNotPoll = true;
565
566        void setDoNotPoll(boolean poll) {
567            if (!poll) {
568                Dcc4PcMessage m = Dcc4PcMessage.resetBoardData(address);
569                m.setTimeout(100);
570                tc.sendDcc4PcMessage(m, null);
571            }
572            doNotPoll = poll;
573        }
574
575        boolean doNotPoll() {
576            return doNotPoll;
577        }
578
579        HashMap<Integer, Dcc4PcSensor> inputPorts = new HashMap<>(16);
580
581        void addSensor(int port, Dcc4PcSensor sensor) {
582            inputPorts.put(port, sensor);
583        }
584
585        String getEncodingAsString() {
586            if ((encoding & 0x01) == 0x01) {
587                return "Supports Cooked RailCom Encoding";
588
589            } else {
590                return "Supports Raw RailCom Encoding";
591            }
592        }
593
594        void setDescription(String description) {
595            this.description = description;
596        }
597
598        Dcc4PcSensor getSensorAtIndex(int i) {
599            if (!inputPorts.containsKey(i)) {
600                return null;
601            }
602            return inputPorts.get(i);
603        }
604
605        int getNumEnabledSensors() {
606            return inputPorts.size();
607        }
608
609        HashMap<Integer, Dcc4PcReporter> inputReportersPorts = new HashMap<>(16);
610
611        void addReporter(int port, Dcc4PcReporter reporter) {
612            inputReportersPorts.put(port, reporter);
613        }
614
615        Dcc4PcReporter getReporterAtIndex(int i) {
616            if (!inputReportersPorts.containsKey(i)) {
617                return null;
618            }
619            return inputReportersPorts.get(i);
620        }
621
622        synchronized void processInputPacket(Dcc4PcReply r) {
623            if (log.isDebugEnabled()) {
624                log.debug("==== Process Packet ====");
625                log.debug("hex = {}", r.toHexString());
626            }
627            int packetTypeCmd = 0x00;
628            int currentByteLocation = 0;
629            while (currentByteLocation < r.getNumDataElements()) {
630                log.debug("--- Start {} ---", currentByteLocation);
631                int oldstart = currentByteLocation;
632                if ((r.getElement(currentByteLocation) & 0x80) == 0x80) {
633                    log.debug("Error at head");
634                    packetTypeCmd = 0x03;
635                    ++currentByteLocation;
636                } else if ((r.getElement(currentByteLocation) & 0x40) == 0x40) {
637                    log.debug("Correct Type 2 packet");
638                    currentByteLocation = processPacket(r, 0x02, packetTypeCmd, currentByteLocation);
639                } else {
640                    log.debug("Correct Type 1 packet");
641                    currentByteLocation = processPacket(r, 0x01, packetTypeCmd, currentByteLocation);
642                }
643                if (log.isDebugEnabled()) {
644                    StringBuilder buf = new StringBuilder();
645                    for (int i = oldstart; i < currentByteLocation; i++) {
646                        buf.append(Integer.toHexString(r.getElement(i) & 0xff)).append(",");
647                    }
648                    log.debug("olstart - current hex {}", buf.toString());
649                    log.debug("--- finish packet {} ---", (currentByteLocation - 1));
650                }
651            }
652            log.debug("==== Finish Processing Packet ====");
653        }
654
655        public int processPacket(Dcc4PcReply r, int packetType, int packetTypeCmd, int currentByteLocation) {
656            // int packetType;
657            int dccpacketlength;
658            if (packetType == 0x02) {
659                dccpacketlength = (r.getElement(currentByteLocation) - 0x40) + 1;
660            } else {
661                dccpacketlength = (r.getElement(currentByteLocation)) + 1;
662            }
663
664            ++currentByteLocation;
665
666            int[] dcc_Data = new int[dccpacketlength];
667
668            for (int i = 0; i < dccpacketlength; i++) {
669                dcc_Data[i] = r.getElement(currentByteLocation);
670                ++currentByteLocation;
671            }
672            try {
673                decodeDCCPacket(dcc_Data);
674            } catch (Exception ex) {
675                log.error("decodeDCCPacket Exception", ex);
676            }
677
678            if (packetType == 0x02) {
679                getInputState(Arrays.copyOfRange(r.getDataAsArray(), currentByteLocation, currentByteLocation + 4), address);
680                currentByteLocation = currentByteLocation + 4;
681            }
682
683            ArrayList<Dcc4PcReporter> railCommDataForSensor = new ArrayList<>();
684            for (int i = 1; i < getNumEnabledSensors() + 1; i++) {
685                if (getReporterAtIndex(i).getRailComState() >= Dcc4PcSensor.ORIENTA) {
686                    if (log.isDebugEnabled()) {
687                        log.debug("Adding reporter for input {}", getReporterAtIndex(i).getSystemName());
688                    }
689                    railCommDataForSensor.add(getReporterAtIndex(i));
690                }
691            }
692
693            int railComDupPacket = (int) Math.ceil((railCommDataForSensor.size()) / 4.0f);
694            log.debug("We have {} Byte(s) to read on data", railComDupPacket);
695
696            log.debug("Now to handle the duplicate packet data");
697            int j = 0;
698
699            for (int i = 0; i < railComDupPacket; i++) {
700                int inputNo = 0;
701                int dup = decodeDuplicatePacket(r.getElement(currentByteLocation), inputNo, railCommDataForSensor.get(j));
702                if (log.isDebugEnabled()) {
703                    log.debug("Input {} - {}", railCommDataForSensor.get(j).getDisplayName(), dup);
704                }
705                if (dup == 0) {
706                    j++;
707                } else {
708                    railCommDataForSensor.remove(j);
709                }
710
711                inputNo++;
712                if (j < railCommDataForSensor.size()) {
713                    dup = decodeDuplicatePacket(r.getElement(currentByteLocation), inputNo, railCommDataForSensor.get(j));
714                    if (log.isDebugEnabled()) {
715                        log.debug("Input {} - {}", railCommDataForSensor.get(j).getDisplayName(), dup);
716                    }
717                    if (dup == 0) {
718                        j++;
719                    } else {
720                        railCommDataForSensor.remove(j);
721                    }
722                    inputNo++;
723                }
724                if (j < railCommDataForSensor.size()) {
725                    dup = decodeDuplicatePacket(r.getElement(currentByteLocation), inputNo, railCommDataForSensor.get(j));
726                    if (log.isDebugEnabled()) {
727                        log.debug("Input {} - {}", railCommDataForSensor.get(j).getDisplayName(), dup);
728                    }
729                    if (dup == 0) {
730                        j++;
731                    } else {
732                        railCommDataForSensor.remove(j);
733                    }
734
735                    inputNo++;
736                }
737                if (j < railCommDataForSensor.size()) {
738                    dup = decodeDuplicatePacket(r.getElement(currentByteLocation), inputNo, railCommDataForSensor.get(j));
739                    if (log.isDebugEnabled()) {
740                        log.debug("Input {} - {}", railCommDataForSensor.get(j).getDisplayName(), dup);
741                    }
742                    if (dup == 0) {
743                        j++;
744                    } else {
745                        railCommDataForSensor.remove(j);
746                    }
747                }
748                currentByteLocation++;
749            }
750
751            if (log.isDebugEnabled()) {
752                for (Dcc4PcReporter dcc4PcReporter : railCommDataForSensor) {
753                    log.debug("Data for sensor {}", dcc4PcReporter.getDisplayName());
754                }
755            }
756            // re-use the variable to gather the size of each railcom input data
757            railComDupPacket = (int) Math.ceil((railCommDataForSensor.size()) / 2.0f);
758            log.debug("We have {} size byte(s) to read on data", railComDupPacket);
759
760            // This now becomes the length bytes for the rail comm information
761            j = 0;
762            for (int i = 0; i < railComDupPacket; i++) {
763                int tempValue = r.getElement(currentByteLocation) << (31 - 3);
764                tempValue = (tempValue >>> (31 - 3 + (0)));
765                railCommDataForSensor.get(j).setPacketLength(tempValue);
766                j++;
767                if (j < railCommDataForSensor.size()) {
768                    tempValue = r.getElement(currentByteLocation) << (31 - 7);
769                    tempValue = (tempValue >>> (31 - 7 + 4));
770                    railCommDataForSensor.get(j).setPacketLength(tempValue);
771                    j++;
772                }
773                currentByteLocation++;
774            }
775            for (int i = 0; i < railCommDataForSensor.size(); i++) {
776                log.debug("railCommDataForSensor {} {}", railCommDataForSensor.get(i).getDisplayName(), railCommDataForSensor.get(i).getPacketLength());
777                int[] arraytemp = new int[railCommDataForSensor.get(i).getPacketLength()];
778                for (j = 0; j < railCommDataForSensor.get(i).getPacketLength(); j++) {
779                    arraytemp[j] = 0xFF & r.getElement(currentByteLocation);
780                    currentByteLocation++;
781                }
782                railCommDataForSensor.get(i).setPacket(arraytemp, dcc_addr_type, addr, cvNumber, speed, packetTypeCmd);
783            }
784            return currentByteLocation;
785        }
786
787        int addr = 0;
788        int dcc_addr_type = NO_ADDRESS;
789        int cvNumber = 0;
790        int speed = 0;
791
792        int[] lastDCCPacketSeen = new int[0];
793
794        void decodeDCCPacket(int[] packet) {
795            if (Arrays.equals(packet, lastDCCPacketSeen)) {
796                return;
797            }
798            for (int idx = 0; idx < packet.length; ++idx) {
799                lastDCCPacketSeen = packet.clone();
800            }
801            // lastDCCPacketSeen = packet;
802            speed = 0;
803            addr = 0;
804            dcc_addr_type = NO_ADDRESS;
805            if (log.isDebugEnabled()) {
806
807                StringBuilder buf = new StringBuilder();
808                for (int i = 0; i < packet.length; ++i) {
809                    buf.append(Integer.toHexString(packet[i])).append(",");
810                }
811                String s = buf.toString();
812                log.debug("bytes to process {}", s);
813            }
814            // Basic Accessory Decoder packet 10aaaaaa 1aaacddd eeeeeeee
815            // Extended Accessory decoder packet starts 10aaaaaa 0aaa0aa1 000xxxxx eeeeeeee
816            int i = 0;
817            // Skip accessory decoder packets at this point
818            if ((packet[i] & 0xFF) == 0x00) {
819                return;
820                //Broadcast packet
821            } else if ((packet[i] & 0xff) == 0xff) {
822                // log.debug("Idle Packet");
823                return;
824            } else if ((packet[i] & 0x80) == 0x80) {
825                // Accessory decoinfoder packet
826                if ((packet[i] & 0x80) == 0x80) {
827                    // i++;  don't increment as the for loop will do this
828                    // basic Accessory Decoder packet one byte to follow
829                } else {
830                    i++;
831                    // TODO extended decoder packet two bytes
832                }
833                return;
834            } else {
835                int addr_1;
836                //The way that this determins of there is a two part extended address packet isn't great./
837                if (packet.length > 0) {
838                    addr_1 = packet[i] & 0xFF;
839                    i++;
840                } else {
841                    addr_1 = 0;
842                }
843                if (addr_1 == 0) {
844                    dcc_addr_type = NO_ADDRESS;
845                } else if ((addr_1 >= 1) && (addr_1 <= 127)) { //Short address
846                    dcc_addr_type = SHORT_ADDRESS;
847                    addr = addr_1;
848                } else if ((addr_1 >= 128) && (addr_1 <= 191)) {
849                    dcc_addr_type = NO_ADDRESS; //this is an accessory decoder address should have already of been filtered out
850                } else if ((addr_1 >= 192) && (addr_1 <= 231)) { //14 bit address
851                    dcc_addr_type = LONG_ADDRESS;
852                    addr = (((addr_1 & 0x3F) << 8) | (packet[i] & 0xFF));
853                    i++;
854                } else {
855                    dcc_addr_type = NO_ADDRESS;
856                }
857                String addt;
858                switch (dcc_addr_type) {
859                    case LONG_ADDRESS:
860                        addt = "Long";
861                        break;
862                    case SHORT_ADDRESS:
863                        addt = "Short";
864                        break;
865                    default:
866                        addt = "No Address";
867                        break;
868                }
869
870                log.debug("DCC address type {} addr {}", addt, addr);
871                if ((dcc_addr_type != NO_ADDRESS)) {
872                    log.debug("Current index  {} value {}", i, packet[i] & 0xFF);
873                    if ((packet[i] & 0xE0) == 0xE0) {
874                        i++;
875                        cvNumber = ((packet[i] & 0xff) + 1);
876                        log.debug("CV Access cv:{}", cvNumber);
877                        // two byte instruction when 1111
878                        // three byte instuction when 1110
879                    } else if ((packet[i] & 0xC0) == 0xC0) {
880                        log.debug("Future");
881                        i++;
882                        if ((packet[i] & 0x1F) == 0x1F) {
883                            // F21-F28 Two byte instruction
884                            i++;
885                        } else if ((packet[i] & 0x1E) == 0x1E) {
886                            // F13-F20 Two byte instruction
887                            i++;
888                        } else if ((packet[i] & 0x1D) == 0x1D) {
889                            // Two byte instruction
890                            i++;
891
892                        } else if ((packet[i] >> 3) == 0x00) {
893                            // This should fall through to 00000 three byte instruction
894                            // i = i + 2;
895
896                        } else {
897                            // Remainder are reserved
898                        }
899                        // Two or three byte instruction
900                    } else if ((packet[i] & 0xA0) == 0xa0) {
901                        log.debug("For Function Group 2");
902                        if ((packet[i] & 0x10) == 0x10) {
903                            log.debug("Functions 5 to 8");
904                            if ((packet[i] & 0x08) == 0x08) {
905                                log.debug("Function 8 on");
906                            } else {
907                                log.debug("Function 8 off");
908                            }
909                            if ((packet[i] & 0x04) == 0x04) {
910                                log.debug("Function 7 on");
911                            } else {
912                                log.debug("Function 7 off");
913                            }
914                            if ((packet[i] & 0x02) == 0x02) {
915                                log.debug("Function 6 on");
916                            } else {
917                                log.debug("Function 6 off");
918                            }
919                            if ((packet[i] & 0x01) == 0x01) {
920                                log.debug("Function 5 on");
921                            } else {
922                                log.debug("Function 5 off");
923                            }
924                        } else {
925                            log.debug("Functions 9 to 12");
926                            if ((packet[i] & 0x08) == 0x08) {
927                                log.debug("Function 12 on");
928                            } else {
929                                log.debug("Function 12 off");
930                            }
931                            if ((packet[i] & 0x04) == 0x04) {
932                                log.debug("Function 11 on");
933                            } else {
934                                log.debug("Function 11 off");
935                            }
936                            if ((packet[i] & 0x02) == 0x02) {
937                                log.debug("Function 10 on");
938                            } else {
939                                log.debug("Function 10 off");
940                            }
941                            if ((packet[i] & 0x01) == 0x01) {
942                                log.debug("Function 9 on");
943                            } else {
944                                log.debug("Function 9 off");
945                            }
946                        }
947                        // Single byte instruction
948                        // i++;
949                    } else if ((packet[i] & 0x80) == 0x80) {
950                        log.debug("For Function Group 1");
951                    } else if ((packet[i] & 0x60) == 0x60) {
952                        speed = (packet[i] & 0xff) - 0x60;
953                        log.debug("Speed for forward 14 speed steps {}", speed);
954                        //Only a single byte instruction
955                    } else if ((packet[i] & 0x40) == 0x40) {
956                        speed = ((packet[i] & 0xff) - 0x40);
957                        log.debug("Speed for reverse 14 speed steps {}", speed);
958                        //Only a single byte instruction
959                    } else if ((packet[i] & 0x20) == 0x20) {
960                        log.debug("Advanced Op");
961                        //Two byte instruction
962                        if ((packet[i] & 0x1f) == 0x1f) {
963                            i++;
964                            log.debug("128 speed step control");
965                            if ((packet[i] & 0x80) == 0x80) {
966                                speed = ((packet[i]) & 0xff) - 0x80;
967                                log.debug("Forward {}", speed);
968                            } else {
969                                speed = ((packet[i]) & 0xff);
970                                log.debug("Reverse {}", speed);
971                            }
972                        }
973                    } else {
974                        log.debug("Decoder and Consist Instruction");
975                        // decoder control is 0000
976                        // consist control is 0001
977                    }
978                }
979
980            }
981            log.debug("---End Decode DCC Packet---");
982        }
983
984        int getInputs() {
985            return inputs;
986        }
987
988        String getDescription() {
989            return description;
990        }
991
992        String getVersion() {
993            return version;
994        }
995    }
996
997    public List<Integer> getBoards() {
998        ArrayList<Integer> list = new ArrayList<>();
999        Enumeration<Integer> keys = activeBoards.keys();
1000        while (keys.hasMoreElements()) {
1001            Integer key = keys.nextElement();
1002            list.add(key);
1003        } // end while
1004        return list;
1005    }
1006
1007    public int getBoardInputs(int board) {
1008        if (!activeBoards.containsKey(board)) {
1009            return -1;
1010        }
1011        return activeBoards.get(board).getInputs();
1012    }
1013
1014    public String getBoardEncodingAsString(int board) {
1015        if (!activeBoards.containsKey(board)) {
1016            return "unknown";
1017        }
1018        return activeBoards.get(board).getEncodingAsString();
1019    }
1020
1021    public String getBoardVersion(int board) {
1022        if (!activeBoards.containsKey(board)) {
1023            return "unknown";
1024        }
1025        return activeBoards.get(board).getVersion();
1026    }
1027
1028    public String getBoardDescription(int board) {
1029        if (!activeBoards.containsKey(board)) {
1030            return "unknown";
1031        }
1032        return activeBoards.get(board).getDescription();
1033    }
1034
1035    protected boolean isBoardCreated(int address) {
1036        return activeBoards.containsKey(address);
1037    }
1038
1039    protected void addActiveBoard(int address, String version, int inputs, int encoding) {
1040        activeBoards.put(address, new ActiveBoard(address, version, inputs, encoding));
1041    }
1042
1043    protected void setBoardDescription(int address, String description) {
1044        ActiveBoard board = activeBoards.get(address);
1045        board.setDescription(description);
1046    }
1047
1048    protected void createSensorsForBoard(Dcc4PcReply r) {
1049        if (r.getBoard() == -1) {
1050            log.debug("Reply has no board associated with it");
1051            return;
1052        }
1053        class SensorMaker implements Runnable {
1054
1055            Dcc4PcReply reply;
1056
1057            SensorMaker(Dcc4PcReply r) {
1058                reply = r;
1059            }
1060
1061            @Override
1062            public void run() {
1063                createSensorsFromReply(reply);
1064            }
1065        }
1066
1067        Thread thr = new Thread(new SensorMaker(r), "Dcc4PCSensor Maker board " + r.getBoard());
1068        try {
1069            thr.start();
1070        } catch (java.lang.IllegalThreadStateException ex) {
1071            log.error("Exception {} {}", thr.getName(), ex.getMessage());
1072        }
1073    }
1074
1075    /**
1076     * Validates to contain at least 1 number . . .
1077     * <p>
1078     * TODO: Custom validation for Dcc4PcSensorManager could be improved.
1079     * {@inheritDoc}
1080     */
1081    @Override
1082    @Nonnull
1083    public String validateSystemNameFormat(@Nonnull String name, @Nonnull java.util.Locale locale) throws jmri.NamedBean.BadSystemNameException {
1084        return validateTrimmedMin1NumberSystemNameFormat(name,locale);
1085    }
1086
1087    @Override
1088    public void dispose() {
1089        stopPolling();
1090        super.dispose();
1091    }
1092
1093    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Dcc4PcSensorManager.class);
1094
1095}