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