001package jmri.jmrix.loconet.alm.almi;
002
003import jmri.jmrix.loconet.LnConstants;
004import jmri.jmrix.loconet.LocoNetMessage;
005import jmri.jmrix.loconet.alm.Alm;
006
007//import org.slf4j.Logger;
008//import org.slf4j.LoggerFactory;
009
010/**
011 * ALM Interpretation for Routes
012 *
013 * @author Bob Milhaupt  Copyright (C) 2022
014 */
015public class Almir {
016    private Almir () {
017        throw new IllegalStateException("Utility class"); // NOI18N
018    }
019
020    static String interpretAlmRoutes(LocoNetMessage l) {
021        if ((l.getElement(2) != 1) && (l.getElement(2) != 2)) {
022            return EMPTY;
023        }
024        if (l.getNumDataElements() != 0x10) {
025            return EMPTY;
026        }
027        if (l.equals(DESEL)) {
028            return Bundle.getMessage("MSG_LN_ALM_DEVICE_DESEL");
029        }
030
031        String ret;
032        ret = cardq(l);
033        if (ret.length() > 1) {
034            return ret;
035        }
036        String key = EMPTY;
037        if ((l.getOpCode() == 0xE6) && (l.getElement(3) == 0x02)) {
038            if (l.getElement(2) == 1) {
039                key = "LN_MSG_ALM_ROUTE_CMD_STN_REPORT"; // NOI18N
040            } else if (l.getElement(2) != 2) {
041                return EMPTY;
042            } else {
043                key = "LN_MSG_ALM_ROUTE_DEV_REPORT"; // NOI18N
044            }
045        } else if ((l.getOpCode() == 0xEE) && (l.getElement(3) == 3)) {
046            if (l.getElement(2) == 1) {
047                key = "LN_MSG_ALM_ROUTE_CMD_STN_WRITE"; // NOI18N
048            } else if (l.getElement(2) != 2) {
049                return EMPTY;
050            } else {
051                key = "LN_MSG_ALM_ROUTE_DEV_WRITE"; // NOI18N
052            }
053        }
054        if (key.length() > 1) {
055            return Bundle.getMessage(key,
056                    1 + (((l.getElement(4) + l.getElement(5)*128)/2) & 0x7f),
057                    1 + ((l.getElement(4) & 0x1)<< 2),
058                    4 + ((l.getElement(4) & 0x1)<< 2),
059                    1 + (((l.getElement(4) + l.getElement(5)*128)/4) & 0x3F),
060                    1 + ((l.getElement(4) & 0x3) << 2),
061                    4 + ((l.getElement(4) & 0x3) << 2),
062                    getTurnoutNum(l, 0), getTurnoutStat(l, 0),
063                    getTurnoutNum(l, 1), getTurnoutStat(l, 1),
064                    getTurnoutNum(l, 2), getTurnoutStat(l, 2),
065                    getTurnoutNum(l, 3), getTurnoutStat(l, 3));
066        }
067
068        ret = checkarcq(l);
069        if (ret.length() > 1) {
070            return ret;
071        }
072
073        ret = checkCsrc1(l);
074        if (ret.length()>1) {
075            return ret;
076        }
077
078        ret = checkCsrc2(l);
079        if (ret.length()>1) {
080            return ret;
081        }
082
083        ret = dealWithAlmStyle2(l);
084        if (ret.length() > 1) {
085            return ret;
086        }
087
088        return EMPTY;
089    }
090
091    private static String checkarcq(LocoNetMessage l) {
092        if ((l.equals(almRouteCapabilitiesQuery)) || (l.equals(almRouteCapabilitiesQuery2))) {
093            return Bundle.getMessage("LN_MSG_ALM_RTS_CAP_Q");
094        }
095        return EMPTY;
096    }
097
098    private static String cardq(LocoNetMessage l) {
099        if (l.equals(AlmRoutesDataQuery, ardqm)) {
100            return Bundle.getMessage("LN_MSG_CMD_STN_ROUTE_QUERY",
101                    getRouteNum(l),
102                    getTurnoutGroup(l),
103                    (getTurnoutGroup(l) + 3),
104                    getAltRouteNum(l),
105                    getAltTurnoutGroup(l),
106                    (getAltTurnoutGroup(l) + 3));
107            }
108        return EMPTY;
109    }
110
111    private static int getTurnoutGroup(LocoNetMessage l) {
112        return 1 + ((l.getElement(4) & 0x1)<< 2);
113    }
114
115    private static int getAltTurnoutGroup(LocoNetMessage l) {
116        return 1 + ((l.getElement(4) & 0x3) << 2);
117    }
118
119    private static int getRouteNum(LocoNetMessage l) {
120        return 1 + (((l.getElement(4) + l.getElement(5)*128)/2) & 0x7f);
121    }
122
123    private static int getAltRouteNum(LocoNetMessage l) {
124        return 1 + (((l.getElement(4) + l.getElement(5)*128)/4) & 0x3F);
125    }
126
127    private static String checkCsrc1 (LocoNetMessage l) {
128        if (l.equals(cmdStnRoutesCap)) {
129            return Bundle.getMessage("LN_MSG_ALM_RTS_CAP_R");
130        }
131        return EMPTY;
132    }
133
134    private static String checkCsrc2 (LocoNetMessage l) {
135        if (l.equals(cmdStnRoutesCap2)) {
136            return Bundle.getMessage("LN_MSG_ALM_RTS_CAP_R2");
137        }
138        return EMPTY;
139    }
140
141    private static String getTurnoutNum(LocoNetMessage l, int num) {
142        if ((l.getElement(7+(num*2)) == 0x7f) && (l.getElement(8+(num*2)) == 0x7f)) {
143            return Bundle.getMessage("LN_ROUTE_UNUSED_ENTRY_HELPER");
144        }
145        if ((num < 0) || (num > 3)) {
146            throw new java.lang.IllegalArgumentException();
147        }
148        int val = 1 + l.getElement(7+(num*2)) + ((l.getElement(8+(num*2)) & 15)<<7);
149        return Integer.toString(val);
150    }
151
152    private static String getTurnoutStat(LocoNetMessage l, int num) {
153        if ((l.getElement(7+(num*2)) == 0x7f) && (l.getElement(8+(num*2)) == 0x7f)) {
154            return "";
155        }
156        if ((num <0) || (num > 3)) {
157            throw new java.lang.IllegalArgumentException();
158        }
159        boolean isClosed;
160        isClosed = (l.getElement(8 + (num*2)) & 0x20) == 0x20;
161        return isClosed ? Bundle.getMessage("LN_SW_CLOSED")
162                        :Bundle.getMessage("LN_SW_THROWN");
163    }
164
165    private static String dealWithAlmStyle2(LocoNetMessage l) {
166        int sn = getSN(l);
167        String ser = Integer.toHexString(sn);
168        int bs = getBS(l); // starting address
169        int be;            // ending address
170        String enable;
171        boolean enb = getEnb(l);
172        enable = (enb ? Bundle.getMessage("LN_MSG_HELPER_DISABLED")
173                : Bundle.getMessage("LN_MSG_HELPER_ENABLED"));
174        DevMode mod = getMode(l);
175        String mode;
176        String dev;
177        int rts;            // number of routes
178        int ents;           // number of entries in routes
179        switch (l.getElement(9)) {
180            case LnConstants.RE_IPL_DIGITRAX_HOST_DS74:
181                dev = "DS74"; //NOI18N
182                rts = 8;
183                ents = 8;
184                be = ((mod == DevMode.DS74_LIGHT)?(bs + 7):(bs + 3));
185                break;
186            case LnConstants.RE_IPL_DIGITRAX_HOST_DS78V:
187                dev = "DS78V"; //NOI18N
188                rts = 16;
189                ents = 8;
190                be = ((mod == DevMode.DS78V_3_POS)?(bs + 15):(bs + 7));
191                break;
192            case LnConstants.RE_IPL_DIGITRAX_HOST_SE74:
193                dev = "SE74"; //NOI18N
194                rts = 64;
195                ents = 16;
196                be = bs + 36;
197                break;
198            case LnConstants.RE_IPL_DIGITRAX_HOST_PM74:
199                dev = "PM74"; //NOI18N
200                rts = 0;
201                ents = 0;
202                be = bs + 7;
203                break;
204
205            default:
206                dev = Bundle.getMessage("LN_MSG_ALM_HELPER_DEVICE_UNKNOWN");
207                be = bs;
208                rts = 0;
209                ents = 0;
210        }
211
212        int rn = 1 + (l.getElement(4) / 2) + ((l.getElement(5) & 3)<<6);
213        int re = ((l.getElement(4)& 1) == 1)?5:1;
214        if (Alm.isDs7xRQ(l)) {
215            // This code (and associated key/value pair) will require update if
216            // any device is introduced which supports ALM-based routes with anything
217            // other than 16 entries.
218            return Bundle.getMessage("LN_MSG_ALM_SEL_ROUTE_QUERY", rn, re, re+3);
219        }
220
221        if (Alm.isDs74CapsRpt(l) || Alm.isDs78vCapsRpt(l) ||
222                Alm.isSe74CapsRpt(l) || Alm.isPm74CapsRpt(l) ) {
223            if (Alm.isDs74CapsRpt(l) || Alm.isDs78vCapsRpt(l) ) {
224                switch ((l.getElement(10) & 0x1e) >>1) {
225                    case 0:
226                        mode = "LN_MSG_ALM_HELPER_DEV_MODE_PS"; // NOI18N
227                        be = bs + 3;
228                        break;
229                    case 1:
230                        mode = "LN_MSG_ALM_HELPER_DEV_MODE_SM"; // NOI18N
231                        be = bs + 3;
232                        break;
233                    case 2:
234                        mode = "LN_MSG_ALM_HELPER_DEV_MODE_S2"; // NOI18N
235                        be = bs + 7;
236                        break;
237                    case 5:
238                        mode = "LN_MSG_ALM_HELPER_DEV_MODE_LT"; // NOI18N
239                        be = bs + 7;
240                        break;
241                    case 6:
242                        mode = "LN_MSG_ALM_HELPER_DEV_MODE_S3"; // NOI18N
243                        be = bs + 15;
244                        break;
245                    default:
246                        mode = "LN_MSG_ALM_HELPER_DEV_MODE_UNDEF"; // NOI18N
247                        be = bs;
248                        break;
249                }
250            } else if (Alm.isSe74CapsRpt(l)) { // element 10 observed at 0
251                mode = "LN_MSG_ALM_HELPER_DEV_MODE_UNDEF"; // NOI18N
252                // addressing has already been set above
253            } else if (Alm.isPm74CapsRpt(l)) { // element 10 observed at 0
254                mode = "LN_MSG_ALM_HELPER_DEV_MODE_UNDEF"; // NOI18N
255                // addressing has already been set above
256            } else {
257                be = bs;  // only show one address
258                mode = "LN_MSG_ALM_HELPER_DEV_MODE_UNDEF"; // NOI18N
259            }
260
261            mode = Bundle.getMessage(mode);
262
263            if (Alm.isPm74CapsRpt(l)) {
264                return Bundle.getMessage("LN_MSG_DEVICE_NO_ROUTES_CAPABILITIES_REPLY",
265                   dev, ser, bs );
266            }
267
268            return Bundle.getMessage("LN_MSG_DEVICE_ROUTES_CAPABILITIES_REPLY",
269                   dev, ser, mode, enable, bs, be, rts, ents );
270        }
271
272        LocoNetMessage seldev = new LocoNetMessage(new int[] {
273            0xee, 0x10, 0x02, 0x0e, 0,0,0,0,0,0,0,0,0,0,0,0
274        });
275        int sdm[] = {255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0};
276        if (l.equals(seldev, sdm)) {
277            return Bundle.getMessage("LN_MSG_DEVICE_ROUTES_SELECT_REQUEST",
278                    dev, ser, bs, be);
279        }
280        seldev.setElement(0, LnConstants.OPC_ALM_READ);
281        sdm[4] = 0; sdm[5] = 0; sdm[6] = 0; sdm[7] = 0; sdm[8] = 0;
282        if (l.equals(seldev, sdm)) {
283           return Bundle.getMessage("LN_MSG_DEV_ROUTES_SELECT_REPLY", dev, ser, Integer.toString(bs), Integer.toString(be));
284        }
285
286        if (Alm.isDevBAW(l)) {
287            // change starting turnout address
288            return Bundle.getMessage("LN_MSG_ALM_DEVICE_CHG_SA", dev, ser, Integer.toString(bs));
289        }
290
291        return EMPTY;
292    }
293
294    private static int getSN(LocoNetMessage l) {
295        return l.getElement(11) + (l.getElement(12)<<7);
296    }
297
298    private static int getBS(LocoNetMessage l) {
299        return l.getElement(13) + (l.getElement(14)<<7) + 1;
300    }
301
302    private static boolean getEnb(LocoNetMessage l) {
303        return (l.getElement(10)& 0x40) == 0x40;
304    }
305    private static DevMode getMode(LocoNetMessage l) {
306        if (l.getElement(9) == 0x74) {
307            switch (l.getElement(10) & 0x1E) {
308                case 0:
309                    return DevMode.DS74_SOLE;
310                case 2:
311                    return DevMode.DS74_STALL;
312                case 0xA:
313                    return DevMode.DS74_LIGHT;
314                default:
315                    break;
316            }
317        } else if (l.getElement(9) == 0x7c) {
318            switch (l.getElement(10) & 0xF) {
319                case 4:
320                    return DevMode.DS78V_2_POS;
321                case 0xC:
322                    return DevMode.DS78V_3_POS;
323                default:
324                    break;
325            }
326        }
327        return DevMode.UNKN;
328    }
329
330    private static final String EMPTY = "";
331
332    private static final LocoNetMessage DESEL = new LocoNetMessage(new int[] {
333    0xEE, 0x10, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3});
334
335    private static final LocoNetMessage almRouteCapabilitiesQuery = new LocoNetMessage(new int[] {
336        0xEE, 0x10, 1, 0, 0, 0, 0, 0,
337        0, 0, 0, 0, 0, 0, 0, 0});
338
339    private static final LocoNetMessage almRouteCapabilitiesQuery2 = new LocoNetMessage(new int[] {
340        0xEE, 0x10, 1, 0, 0, 0, 15, 0,
341        0, 0, 0, 0, 0, 0, 0, 0});
342
343    private static final LocoNetMessage cmdStnRoutesCap = new LocoNetMessage(new int[] {
344            0xE6, 0x10, 1, 0, 0x40, 0, 3, 2, 8,
345            0x7F, 0, 0, 0, 0, 0, 0x64});
346
347    private static final LocoNetMessage cmdStnRoutesCap2 = new LocoNetMessage(new int[] {
348            0xE6, 0x10, 1, 0, 0, 2, 3, 2, 0x10,
349            0x7F, 0, 0, 0, 0, 0, 0x64});
350
351    private static final LocoNetMessage AlmRoutesDataQuery = new LocoNetMessage(new int[] {
352        0xEE, 0x10, 1, 2, 0, 0, 0, 0,
353        0, 0, 0, 0, 0, 0, 0, 0});
354
355    private static final int[] ardqm = new int[] {255, 255, 255, 255, 0, 0, 0, 0,
356        0, 0, 0, 0, 0, 0, 0, 0};
357
358    private enum DevMode {
359        DS74_SOLE,
360        DS74_STALL,
361        DS74_LIGHT,
362        DS78V_2_POS,
363        DS78V_3_POS,
364        UNKN
365    }
366//    private final static Logger log = LoggerFactory.getLogger(Almir.class);
367
368}