001package jmri.jmrix.dcc4pc;
002
003import java.util.Hashtable;
004import java.util.Map;
005import jmri.RailCom;
006import jmri.Sensor;
007import jmri.implementation.AbstractRailComReporter;
008
009/**
010 * Extend jmri.implementation.AbstractRailComReporter for Dcc4Pc Reporters.
011 * Implementation for providing status of rail com decoders at this
012 * reporter location.
013 * <p>
014 * The reporter will decode the rail com packets and add the information to the
015 * rail com tag.
016 *
017 * @author Kevin Dickerson Copyright (C) 2012
018 */
019public class Dcc4PcReporter extends AbstractRailComReporter {
020
021    public Dcc4PcReporter(String systemName, String userName) {  // a human-readable Reporter number must be specified!
022        super(systemName, userName);  // can't use prefix here, as still in construction
023    }
024
025    // data members
026    transient RailComPacket[] rcPacket = new RailComPacket[3];
027
028    void setPacket(int[] arraytemp, int dcc_addr_type, int dcc_addr, int cvNumber, int speed, int packetTypeCmd) {
029        log.debug("{} dcc_addr {} {} {}", getDisplayName(), dcc_addr, cvNumber, speed);
030        RailComPacket rc = new RailComPacket(arraytemp, dcc_addr_type, dcc_addr, cvNumber, speed);
031        decodeRailComInfo(rc, packetTypeCmd);
032        rcPacket[2] = rcPacket[1];
033        rcPacket[1] = rcPacket[0];
034        rcPacket[0] = rc;
035        synchronized(lock) {
036            log.debug("Packets Seen {} in error {}", packetseen, packetsinerror);
037        }
038    }
039
040    static class RailComPacket {
041
042        transient final int[] rcPacket;
043        int dcc_addr_type;
044        int dccAddress;
045        int cvNumber;
046        int speed;
047
048        RailComPacket(int[] array, int dcc_addr_type, int dcc_addr, int cvNumber, int speed) {
049            rcPacket = array;
050            this.dcc_addr_type = dcc_addr_type;
051            this.dccAddress = dcc_addr;
052            this.cvNumber = cvNumber;
053            this.speed = speed;
054        }
055
056        int[] getPacket() {
057            return rcPacket;
058        }
059
060        int getAddressType() {
061            return dcc_addr_type;
062        }
063
064        int getDccAddress() {
065            return dccAddress;
066        }
067
068        int getCvNumber() {
069            return cvNumber;
070        }
071
072        int getSpeed() {
073            return speed;
074        }
075
076        String toHexString() {
077            StringBuilder buf = new StringBuilder();
078            buf.append("0x").append(Integer.toHexString(0xFF & rcPacket[0]));
079            for (int i = 1; i < rcPacket.length; i++) {
080                buf.append(", 0x").append(Integer.toHexString(0xFF & rcPacket[i]));
081            }
082            return buf.toString();
083        }
084    }
085
086    void duplicatePacket(int dup) {
087        RailComPacket temp;
088        switch (dup) {
089            case 0x00:
090                break; //re-use the rcPacket at the head.
091            case 0x02:
092                temp = rcPacket[1];  //move rcPacket one to the head
093                rcPacket[1] = rcPacket[0];
094                rcPacket[0] = temp;
095                break;
096            case 0x03:
097                temp = rcPacket[2]; //move rcPacket two to the head
098                rcPacket[2] = rcPacket[1];
099                rcPacket[1] = rcPacket[0];
100                rcPacket[0] = temp;
101                break;
102            default:
103                break;
104        }
105    }
106
107    int state = Sensor.UNKNOWN;
108    
109    static final int REPRESENTS_RAILCOM_ORIENTA = 0x10;
110    static final int REPRESENTS_RAILCOM_ORIENTB = 0x20;
111        
112    public void setRailComState(int ori) {
113        if (state == ori) {
114            return;
115        }
116        state = ori;
117        if (ori == Sensor.INACTIVE || ori == Sensor.UNKNOWN) {
118            //We reset everything as the associated sensor has gone inactive
119            synchronized (this) {
120                addr = 0;
121                address_part_1 = 0x100;
122                address_part_2 = -1;
123                addr_type = -1;
124                actual_speed = -1;
125                actual_load = -1;
126                actual_temperature = -1;
127                fuelLevel = -1;
128                waterLevel = -1;
129                location = -1;
130                routing_no = -1;
131            }
132            cvNumber = -1;
133            cvValues = new Hashtable<>();
134            setReport(null);
135        } else if (ori == REPRESENTS_RAILCOM_ORIENTA || ori == REPRESENTS_RAILCOM_ORIENTB) {
136            if (super.getCurrentReport() != null && super.getCurrentReport() instanceof RailCom) {
137                var orientation = RailCom.Orientation.ORIENTA;
138                if (ori == REPRESENTS_RAILCOM_ORIENTB) orientation = RailCom.Orientation.ORIENTB;
139                
140                ((RailCom) super.getCurrentReport()).setOrientation(orientation);
141            }
142            firePropertyChange("currentReport", null, null);
143        }
144    }
145
146    public int getRailComState() {
147        return state;
148    }
149
150    public String getReport() {
151        if (super.getCurrentReport() != null && super.getCurrentReport() instanceof RailCom) {
152            return ((RailCom) super.getCurrentReport()).getTagID();
153        }
154        if ((getRailComState() < REPRESENTS_RAILCOM_ORIENTA) || (rcPacket[0] == null) || rcPacket[0].getPacket() == null) {
155            return "";
156        }
157        return "";
158    }
159
160    //packet Length is a temp store used for decoding the railcom packet
161    int packetLength = 0;
162
163    void setPacketLength(int i) {
164        packetLength = i;
165    }
166
167    int getPacketLength() {
168        return packetLength;
169    }
170
171    int addr = 0;
172    int address_part_1 = 0x100;
173    int address_part_2 = -1;
174    int addr_type = -1;
175    int actual_speed = -1;
176    int actual_load = -1;
177    int actual_temperature = -1;
178    int fuelLevel = -1;
179    int waterLevel = -1;
180    int location = -1;
181    int routing_no = -1;
182    int cvNumber = -1;
183    int cvvalue = -1;
184
185    int addressp1found = 0;
186
187    static int packetseen = 0;
188    static int packetsinerror = 0;
189    private static final Object lock = new Object();
190
191    Hashtable<Integer, Integer> cvValues = new Hashtable<>();
192
193    void decodeRailComInfo(RailComPacket rc, int packetTypeCmd) {
194
195        synchronized(lock) {
196            addressp1found++;
197            RailCom rcTag = null;
198
199            if (super.getCurrentReport() instanceof RailCom) {
200                rcTag = (RailCom) super.getCurrentReport();
201            }
202            int[] packet = rc.getPacket();
203            char chbyte;
204            char type;
205
206            if (log.isDebugEnabled()) {
207                log.debug("{} packet type {}", getDisplayName(), packetTypeCmd);
208                log.debug("decodeRailComInfo {} {}", this.getDisplayName(), super.getCurrentReport());
209                StringBuilder buf = new StringBuilder();
210                for (int i = 0; i < packet.length; ++i) {
211                    buf.append(packet[i]);
212                }
213                log.debug("Rail Comm Packets {}", buf);
214
215            }
216            int i = 0;
217            while (i < packet.length) {
218                packetseen++;
219                chbyte = (char) packet[i];
220                chbyte = decode[chbyte];
221                if (chbyte == ERROR) {
222                    if (log.isDebugEnabled()) {
223                        log.debug("{} Error packet stage 1: {}", this.getDisplayName(), Integer.toHexString(packet[i]));
224                    }
225                    packetsinerror++;
226                    return;
227                }
228                i++;
229                if ((chbyte & ACK) == ACK) {
230                    chbyte = (char) packet[i];
231                    i++;
232                    chbyte = decode[chbyte];
233                    if (chbyte == ERROR) {
234                        log.debug("{} Error packet stage 2", this.getDisplayName());
235                        packetsinerror++;
236                        return;
237                    }
238                    if ((chbyte & ACK) == ACK) {
239                        if (packet.length <= (i + 1)) {
240                            log.debug("No further data to process Only had the ack 1");
241                            break;
242                        }
243                        chbyte = (char) packet[i];
244                        i++;
245                        chbyte = decode[chbyte];
246                    }
247                }
248                if (packet.length <= i) {
249                    break;
250                }
251                type = chbyte;
252                chbyte = (char) packet[i];
253                chbyte = decode[chbyte];
254                if ((chbyte == ERROR) || ((chbyte & ACK) == ACK)) {
255                    if (log.isDebugEnabled()) {
256                        log.debug("{} Error packet stage 3 {}\n{}", this.getDisplayName(), Integer.toHexString(packet[i]), rc.toHexString());
257                    }
258                    i++;
259                    packetsinerror++;
260                    return;
261                }
262
263                chbyte = (char) (((type & 0x03) << 6) | (chbyte & 0x3f));
264                type = (char) ((type >> 2) & 0x0F);
265
266                switch (type) {
267                    case 0:
268                        log.debug("{} CV Value {}{}", this.getDisplayName(), (int) chbyte, rcTag);
269                        cvvalue = chbyte;
270                        if (rcTag != null) {
271                            rcTag.setWhereLastSeen(this);
272                            if (rcTag.getExpectedCv() != -1) {
273                                rcTag.setCvValue(chbyte);
274                            } else {
275                                rcTag.setCV(rc.getCvNumber(), chbyte);
276                            }
277                        }
278                        break;
279                    case 4:
280                        if (log.isDebugEnabled()) {
281                            log.debug("{} Create/Get id tag for {}", this.getDisplayName(), rc.getDccAddress());
282                        }
283                        addr = rc.getDccAddress();
284                        addr_type = rc.getAddressType();
285                        break;
286                    case 1: // Address byte 1
287                        log.debug("Address Byte 1");
288                        address_part_1 = (0x100 | chbyte);
289                        addressp1found = 0;
290                        break;
291                    case 2: //Address byte 2
292                        log.debug("{} Address part 2:", this.getDisplayName());
293                        address_part_2 = chbyte;
294                        if (packetTypeCmd == 0x03) {
295                            log.debug("Type three packet so shouldn't not pair part one with part two if it came from the previous packet");
296                            //As the last command was a type 3, an address part one packet can not be paired with this address part two packet.  Therefore will set it back to default
297                            //address_part_1 = 0x100;
298                            // break;
299                        }
300                        if (!((address_part_1 & 0x100) == 0x100)) {
301                            log.debug("{} Break at Address part 1, part one not complete", this.getDisplayName());
302                            break;
303                        }
304                        rcTag = decodeAddress();
305                        break;
306                    case 3: //Actual Speed / load
307                        if ((chbyte & 0x80) == 0x80) {
308                            actual_speed = (chbyte & 0x7f);
309                            log.debug("{} Actual Speed: {}", this.getDisplayName(), actual_speed);
310                        } else {
311                            actual_load = (chbyte & 0x7f);
312                            log.debug("{} Actual Load: {}", this.getDisplayName(), actual_load);
313                        }
314                        if (rcTag != null) {
315                            rcTag.setActualLoad(actual_load);
316                            rcTag.setActualSpeed(actual_speed);
317                            rcTag.setWhereLastSeen(this);
318                        }
319                        break;
320                    case 5: //Routing number
321                        routing_no = chbyte;
322                        if (rcTag != null) {
323                            rcTag.setRoutingNo(routing_no);
324                            rcTag.setWhereLastSeen(this);
325                        }
326                        break;
327                    case 6: //Location
328                        location = chbyte;
329                        if (rcTag != null) {
330                            rcTag.setLocation(location);
331                            rcTag.setWhereLastSeen(this);
332                        }
333                        break;
334                    case 7: //Fuel water
335                        if ((chbyte & 0x80) == 0x80) {
336                            fuelLevel = (chbyte & 0x7f);
337                        } else {
338                            waterLevel = (chbyte & 0x7f);
339                        }
340
341                        if (rcTag != null) {
342                            rcTag.setWaterLevel(waterLevel);
343                            rcTag.setFuelLevel(fuelLevel);
344                            rcTag.setWhereLastSeen(this);
345                        }
346                        break;
347                    case 8: //Temp
348                        if (!((chbyte & 0x80) == 0x80)) {
349                            actual_temperature = (chbyte & 0x7F);
350                        }
351                        if (rcTag != null) {
352                            rcTag.setActualTemperature(actual_temperature);
353                            rcTag.setWhereLastSeen(this);
354                        }
355                        break;
356                    case 15: //CV Address  Value
357                        log.debug("{} CV Address and value:", this.getDisplayName());
358                        i = i + 2;
359                        //len = 4;
360                        break;
361                    default:
362                        log.info("unknown railcom type packet {}", type);
363                        break;
364                }
365                i++;
366
367            }
368        }
369    }
370
371    RailCom decodeAddress() {
372        RailCom rcTag;
373        log.debug("{} Create/Get id tag for {}", this.getDisplayName(), addr);
374        rcTag = (RailCom)jmri.InstanceManager.getDefault(jmri.RailComManager.class).provideIdTag("" + addr);
375
376        if ((fuelLevel != -1)) {
377            rcTag.setFuelLevel(fuelLevel);
378        }
379        if ((waterLevel != -1)) {
380            rcTag.setWaterLevel(waterLevel);
381        }
382        if ((routing_no != -1)) {
383            rcTag.setRoutingNo(routing_no);
384        }
385        if ((location != -1)) {
386            rcTag.setLocation(location);
387        }
388        if ((actual_temperature != -1)) {
389            rcTag.setActualTemperature(actual_temperature);
390        }
391        if ((actual_load != -1)) {
392            rcTag.setActualLoad(actual_load);
393        }
394        if ((actual_speed != -1)) {
395            rcTag.setActualSpeed(actual_speed);
396        }
397        for (Map.Entry<Integer, Integer> entry : cvValues.entrySet()) {
398            rcTag.setCV(entry.getKey(), entry.getValue());
399            if (cvvalue != -1) {
400                rcTag.setCvValue(cvvalue);
401            }
402        }
403
404        address_part_1 = 0;
405        address_part_2 = -1;
406        notify(rcTag);
407        return rcTag;
408    }
409
410    RailCom provideTag(int address, int addr_type) {
411        log.debug("provide Tag");
412        RailCom rcTag = (RailCom) jmri.InstanceManager.getDefault(jmri.RailComManager.class).provideIdTag("" + address);
413        notify(rcTag);
414        return rcTag;
415    }
416
417    public final static char ACK = 0x80;
418    public final static char ACK_1 = 0x81;
419    public final static char ACK_2 = 0x82;
420    public final static char ACK_3 = 0x83;
421    public final static char ACK_4 = 0x84;
422    public final static char ACK_5 = 0x85;
423    public final static char ACK_6 = 0x86;
424    public final static char ERROR = 0xFF;
425
426    private final static char[] decode = new char[]{
427        ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR,
428        ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ACK_1,
429        ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x33,
430        ERROR, ERROR, ERROR, 0x34, ERROR, 0x35, 0x36, ERROR,
431        ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x3A,
432        ERROR, ERROR, ERROR, 0x3B, ERROR, 0x3C, 0x37, ERROR,
433        ERROR, ERROR, ERROR, 0x3F, ERROR, 0x3D, 0x38, ERROR,
434        ERROR, 0x3E, 0x39, ERROR, ACK_5, ERROR, ERROR, ERROR,
435        ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x24,
436        ERROR, ERROR, ERROR, 0x23, ERROR, 0x22, 0x21, ERROR,
437        ERROR, ERROR, ERROR, 0x1F, ERROR, 0x1E, 0x20, ERROR,
438        ERROR, 0x1D, 0x1C, ERROR, 0x1B, ERROR, ERROR, ERROR,
439        ERROR, ERROR, ERROR, 0x19, ERROR, 0x18, 0x1A, ERROR,
440        ERROR, 0x17, 0x16, ERROR, 0x15, ERROR, ERROR, ERROR,
441        ERROR, 0x25, 0x14, ERROR, 0x13, ERROR, ERROR, ERROR,
442        0x32, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR,
443        ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ACK_2,
444        ERROR, ERROR, ERROR, 0x0E, ERROR, 0x0D, 0x0C, ERROR,
445        ERROR, ERROR, ERROR, 0x0A, ERROR, 0x09, 0x0B, ERROR,
446        ERROR, 0x08, 0x07, ERROR, 0x06, ERROR, ERROR, ERROR,
447        ERROR, ERROR, ERROR, 0x04, ERROR, 0x03, 0x05, ERROR,
448        ERROR, 0x02, 0x01, ERROR, 0x00, ERROR, ERROR, ERROR,
449        ERROR, 0x0F, 0x10, ERROR, 0x11, ERROR, ERROR, ERROR,
450        0x12, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR,
451        ERROR, ERROR, ERROR, ACK_3, ERROR, 0x2B, 0x30, ERROR,
452        ERROR, 0x2A, 0x2F, ERROR, 0x31, ERROR, ERROR, ERROR,
453        ERROR, 0x29, 0x2E, ERROR, 0x2D, ERROR, ERROR, ERROR,
454        0x2C, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR,
455        ERROR, ACK_6, 0x28, ERROR, 0x27, ERROR, ERROR, ERROR,
456        0x26, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR,
457        ACK_4, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR,
458        ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR};
459
460    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Dcc4PcReporter.class);
461
462}