001package jmri.jmrix.loconet;
002
003import javax.annotation.Nonnull;
004
005import jmri.jmrix.loconet.alm.LnSimple7thGenDevicesRoutes;
006import jmri.jmrix.loconet.alm.LnSimple7thGenDeviceRoutes;
007import jmri.jmrix.loconet.alm.LnSimple7thGenRoute;
008import jmri.jmrix.loconet.alm.LnSimpleRouteEntry;
009import jmri.jmrix.loconet.alm.RouteSwitchPositionEnum;
010
011/**
012 *
013 * @author aptly
014 */
015public class Ln7gAccyRoutesManager implements LocoNetListener  {
016
017    private static final String DEVICE_DS74 = "DS74";
018    private static final String DEVICE_DS78V = "DS78V";
019    private static final String DEVICE_PM74 = "PM74";
020    private static final String DEVICE_SE74 = "SE74";
021
022    private LocoNetSystemConnectionMemo memo;
023    private final LnSimple7thGenDevicesRoutes devicesRoutes;
024    private int activeDeviceType;
025    private int activeDeviceSerNum;
026    private int activeDeviceBaseAddr;
027    private int activeEntrySet;
028    private int activeRouteNum;
029    
030    public Ln7gAccyRoutesManager() {
031        this.devicesRoutes = new LnSimple7thGenDevicesRoutes();
032    }
033    
034    public void initComponents(LocoNetSystemConnectionMemo c) {
035        memo = c;
036        memo.getLnTrafficController().addLocoNetListener(~0, this);
037        deselectAlmRoutesDevice();
038        // nothing more to do
039    }
040
041    public Ln7gAccyRoutesManager initContext(Object context) {
042        if (context instanceof LocoNetSystemConnectionMemo) {
043            initComponents((LocoNetSystemConnectionMemo)context);
044        }
045        return this;
046    }
047    private void deselectAlmRoutesDevice() {
048        activeDeviceType = 0;
049        activeDeviceSerNum = 0;
050        activeDeviceBaseAddr = 0;
051        activeEntrySet = -1;
052        activeRouteNum = -1;
053    }
054    
055    private void selectAlmRoutesDevice(int deviceType, int serNum, int baseAddr) {
056        activeDeviceType = deviceType;
057        activeDeviceSerNum = serNum;
058        activeDeviceBaseAddr = baseAddr;
059        activeEntrySet = -1;
060        activeRouteNum = -1;
061    }
062
063    /**
064     * Update from saved data.
065     *
066     * Read the file of devices/routes.
067     */
068    public  void initializeDeviceRoutes() {
069        loadTheXML();
070    }
071
072    /**
073     * Get String which shows all 7th Gen Accy devices which can store routes, 
074     * with any route data.
075     * @return string of 7th-gen Accy devices which can have enabled routes
076     */
077    public String showStoredDevicesWithRoutes() {
078        if (getCountOfDevicesWithRoutes() < 1) {
079            return "No Digitrax 7th-gen Accessory devices with stored/storable routes.";
080        }
081        StringBuilder s = new StringBuilder("Known Digitrax 7th-gen Accessory devices with stored/storable routes:\n");
082        for (int i = 0; i < getCountOfDevicesWithRoutes(); i++) {
083            LnSimple7thGenDeviceRoutes d = devicesRoutes.getDeviceRoutes(i);
084            s.append("\tDevice ");
085            s.append(d.getDeviceType());
086            s.append(", ser. num. ");
087            s.append(d.getSerNum());
088            s.append(", base addr. ");
089            s.append(d.getBaseAddr());
090
091            for (int j = 0; j < d.getRoutes().length; ++j) {
092                LnSimple7thGenRoute route = d.getRoutes(j);
093                s.append("\n\tRoute ");
094                s.append(j);
095                s.append(": ");
096                for (int l = 0; l < 8; l++) {
097                    s.append(", ");
098                    s.append(route.getRouteEntry(l).getNumber());
099                    s.append(route.getRouteEntry(l).getPosition());
100                }
101            }
102            s.append(".\n");
103        }
104        return s.toString();
105    }
106
107
108    /**
109     * Returns the manager's memo.
110     * @return a LocoNetSystemConnectionMemo
111     */
112    @Nonnull
113    public LocoNetSystemConnectionMemo getMemo() {
114        return memo;
115    }
116
117    private void checkMessage4Byte(LocoNetMessage m) {
118        if ((m.getNumDataElements() == 4) &&
119                (m.getOpCode() == 0xb4) && (m.getElement(1) == 0x6e) &&
120                (m.getElement(2) == 0x7f)) {
121            // Ignore the ALM device route write long ack response
122            //      rather than accepting at the write request!
123            log.trace("message: write acknowledge- Ignored.");
124        }
125    }
126    
127    private boolean checkReportAlmCSRoutesCapabilities(LocoNetMessage m) {
128        if ((m.getNumDataElements() == 16) &&
129                (m.getOpCode() == LnConstants.OPC_ALM_READ) && (m.getElement(1) == 0x10) &&
130                (m.getElement(2) == 0x1) && (m.getElement(3) == 0x0) &&
131                (m.getElement(10) == 0) &&
132                (m.getElement(11) == 0) && (m.getElement(12) == 0) &&
133                (m.getElement(13) == 0) && (m.getElement(14) == 0)) {
134            // [E6 10 01 00 00 02 03 02 10 7F 00 00 00 00 00 64]  Command Station Routes Capabilities reply: 64 routes, 16 entries each route.
135            log.debug("Report ALM CS Routes Capabilities");
136            // nothing to do at this time
137            return true;
138        }
139        return false;
140    }
141    
142    private boolean checkRequestDeviceRoutes(LocoNetMessage m) {
143        if ((m.getNumDataElements() == 16) &&
144                (m.getOpCode() == 0xeE) && (m.getElement(1) == 0x10) &&
145                (m.getElement(2) == 0x2) && (m.getElement(3) == 0x0e) &&
146                (m.getElement(4) == 0) && (m.getElement(5) == 0) &&
147                (m.getElement(6) == 0) && (m.getElement(7) == 0) &&
148                (m.getElement(8) == 0)) {
149            log.debug("request select ALM Device Routes");
150            // Ignore byte 10: devopsw!
151            // force the device as unselected (for now)!
152            deselectAlmRoutesDevice();
153            return true;
154        }
155        return false;
156    }
157    
158    private boolean checkAlmDeviceIsSelected(LocoNetMessage m) {
159        if ((m.getNumDataElements() == 16) &&
160                (m.getOpCode() == 0xe6) && (m.getElement(1) == 0x10) &&
161                (m.getElement(2) == 0x2) && (m.getElement(3) == 0x0e) &&
162                ((m.getElement(4) & 0x4f) == 0) && // bits 5:1 imply # routes
163                (m.getElement(5) == 0) &&
164                (m.getElement(6) == 0) && (m.getElement(7) == 0x02) &&
165                (m.getElement(8) == 8)) {
166            log.debug("response from device: ALM Device Routes selected");
167            // Ignore byte 10: devopsw!
168            // selected the currect ALM Routes device
169            int dev = m.getElement(9);
170            int ser = (m.getElement(12) << 7) + m.getElement(11);
171            int base = (m.getElement(14) << 7) + m.getElement(13);
172
173            // set the device as selected for routes read/write
174            selectAlmRoutesDevice(dev, ser, base);
175            return true;
176        }
177        return false;
178    }
179
180    private boolean checkAlmDeviceIsDeselected(LocoNetMessage m) {
181        if ((m.getNumDataElements() == 16) &&
182                (m.getOpCode() == 0xe6) && (m.getElement(1) == 0x10) &&
183                (m.getElement(2) == 0x2) && (m.getElement(3) == 0) &&
184                (m.getElement(4) == 0) && (m.getElement(5) == 0) &&
185                (m.getElement(7) == 0) && (m.getElement(8) == 0) &&
186                (m.getElement(9) == 0) && (m.getElement(10) == 0) &&
187                (m.getElement(11) == 0) && (m.getElement(12) == 0) &&
188                (m.getElement(13) == 0) && (m.getElement(14) == 0)) {
189            // Ignoring byte 6!
190
191            // deselect any currect ALM Routes device
192            log.debug("message: deselect the ALM routes device at the de-select request");
193            deselectAlmRoutesDevice();
194            return true;
195        }
196        return false;
197    }
198
199    private boolean checkRequestAlmDeviceReadRequest(LocoNetMessage m) {
200        if ((m.getNumDataElements() == 16) &&
201                (m.getOpCode() == 0xee) && (m.getElement(1) == 0x10) &&
202                (m.getElement(2) == 0x2) && (m.getElement(3) == 0x2) &&
203                (m.getElement(7) == 0) && (m.getElement(8) == 0) &&
204                (m.getElement(9) == 0) && (m.getElement(10) == 0) &&
205                (m.getElement(11) == 0) && (m.getElement(12) == 0) &&
206                (m.getElement(13) == 0) && (m.getElement(14) == 0)
207                ) {
208            // Ignoring byte 6!
209
210            // watch for ALM device route read request; capture data for later use
211            activeEntrySet = m.getElement(4) & 1;
212            activeRouteNum = (m.getElement(4) >> 1) + (m.getElement(5) << 6);
213            log.debug("message: read request - ALM 7th gen read - entry {} entrySet {}", activeRouteNum, activeEntrySet);
214            return true;
215        }
216        return false;
217    }
218
219    private boolean checkAlmDeviceReadReport(LocoNetMessage m) {
220        if ((m.getNumDataElements() == 16) &&
221                (m.getOpCode() == 0xe6) && (m.getElement(1) == 0x10) &&
222                (m.getElement(2) == 0x2) && (m.getElement(3) == 0x2) &&
223                (m.getElement(7) == 0) && (m.getElement(8) == 0) &&
224                (m.getElement(9) == 0) && (m.getElement(10) == 0) &&
225                (m.getElement(11) == 0) && (m.getElement(12) == 0) &&
226                (m.getElement(13) == 0) && (m.getElement(14) == 0)
227                ) {
228            // Ignoring byte 6!
229
230            // watch for ALM device route read report and keep the route data
231            activeEntrySet = m.getElement(4) & 1;
232            activeRouteNum = (m.getElement(4) >> 1) + (m.getElement(5) << 6);
233            int entrya = m.getElement(6) + (m.getElement(7) << 7);
234            int entryb = m.getElement(8) + (m.getElement(8) << 7);
235            int entryc = m.getElement(10) + (m.getElement(11) << 7);
236            int entryd = m.getElement(12) + (m.getElement(13) << 7);
237
238            log.debug("message: read report - ALM 7th gen read - entry {} entrySet {}: entryA = {}, entryB = {}, entryC = {}, entryD = {}.",
239                    activeEntrySet, activeEntrySet, entrya, entryb, entryc, entryd);
240            saveData(activeDeviceType, activeDeviceSerNum,
241                    activeDeviceBaseAddr, activeEntrySet, activeRouteNum,
242                    entrya, entryb, entryc, entryd);
243            return true;
244        }
245        return false;
246    }
247
248    private boolean checkAlmDeviceWriteRequest(LocoNetMessage m) {
249        if ((m.getNumDataElements() == 16) &&
250                (m.getOpCode() == 0xee) && (m.getElement(1) == 0x10) &&
251                (m.getElement(2) == 0x2) && (m.getElement(3) == 0x3)) {
252            // Ignoring byte 6!
253
254            // watch for ALM device route write request
255            activeEntrySet = m.getElement(4) & 1;
256            activeRouteNum = (m.getElement(4)>>1) + (m.getElement(5) << 6);
257            // get 4 entries
258            int entrya = m.getElement(7) + (m.getElement(8) << 7);
259            int entryb = m.getElement(9) + (m.getElement(10) << 7);
260            int entryc = m.getElement(11) + (m.getElement(12) << 7);
261            int entryd = m.getElement(13) + (m.getElement(14) << 7);
262
263            log.debug("message: write request - ALM 7th gen write - entry {} entrySet {}: entryA = {}, entryB = {}, entryC = {}, entryD = {}.\n",
264                    activeEntrySet, activeEntrySet, entrya, entryb, entryc, entryd);
265
266            saveData(activeDeviceType, activeDeviceSerNum, activeDeviceBaseAddr,
267                activeEntrySet, activeRouteNum,
268                entrya, entryb, entryc, entryd);
269            return true;
270        }
271        return false;
272    }
273
274    @Override
275    public void message(LocoNetMessage m) {
276        log.trace("RtsMgr:message - length {}", m.getNumDataElements());
277        if (checkReportAlmCSRoutesCapabilities(m)) {
278        } else if (checkRequestDeviceRoutes(m)) {
279        } else if (checkAlmDeviceIsSelected(m)) {
280        } else if (checkAlmDeviceIsDeselected(m)) {
281        } else if (checkRequestAlmDeviceReadRequest(m)) {
282        } else if (checkAlmDeviceReadReport(m)) {
283        } else if (checkAlmDeviceWriteRequest(m)) {
284        } else {
285            checkMessage4Byte(m);
286        }
287    }
288
289    /**
290     * Save 4 entries of a route.
291     *
292     * @param deviceType Device type number
293     * @param deviceSerNum Device serial number
294     * @param deviceBaseAddr Device base address
295     * @param entrySet Entry "set" number
296     * @param routeNum Route number
297     * @param entrya Entry A
298     * @param entryb Entry B
299     * @param entryc Entry C
300     * @param entryd Entry D
301     */
302     public void saveData(int deviceType, int deviceSerNum,
303            int deviceBaseAddr, int entrySet, int routeNum,
304            int entrya, int entryb, int entryc, int entryd) {
305
306        log.debug("saveData: updating 4 entries for device {} serNum {} routeNum {}"
307                + " entries {} to {}, \n\twith entrya = {}, entryb = {}, entryc = {}, entryd = {}.",
308                deviceType, deviceSerNum, routeNum,
309                entrySet * 4, (entrySet *4) + 3,
310                entrya, entryb, entryc, entryd);
311
312        // find/create the device's storage
313        LnSimple7thGenDeviceRoutes device = devicesRoutes.getDeviceRoutes(deviceType, deviceSerNum);
314        if (device == null) {
315            //  Didn't find one.  Create one
316            addDevice(new LnSimple7thGenDeviceRoutes(deviceType, deviceSerNum));
317
318            device = devicesRoutes.getDeviceRoutes(deviceType, deviceSerNum);
319            device.setBaseAddr(deviceBaseAddr);
320            log.debug("saveData: New device's type = {}, serNum = {}, baseAddr = {}",
321                    device.getDeviceType(), device.getSerNum(), device.getBaseAddr());
322        }
323        // update the four entries at routeNum, "entrySet" half
324        device.setFourEntries(routeNum, entrySet, entrya, entryb, entryc, entryd);
325
326        LnSimple7thGenDeviceRoutes d = devicesRoutes.getDeviceRoutes(deviceType, deviceSerNum);
327        LnSimpleRouteEntry ea = d.getRoutes(routeNum).getRouteEntry(4 * entrySet);
328        LnSimpleRouteEntry eb = d.getRoutes(routeNum).getRouteEntry((4 * entrySet) + 1);
329        LnSimpleRouteEntry ec = d.getRoutes(routeNum).getRouteEntry(4 * entrySet + 2);
330        LnSimpleRouteEntry ed = d.getRoutes(routeNum).getRouteEntry(4 * entrySet + 3);
331        log.debug("saveData: device {} serNum {} route {}. entrySet {}, a {}, b {}, c {}, d {}",
332                d.getDeviceType(), d.getSerNum(), routeNum, entrySet,
333                Integer.toString(ea.getNumber())+ea.getPosition().toString(),
334                Integer.toString(eb.getNumber())+eb.getPosition().toString(),
335                Integer.toString(ec.getNumber())+ec.getPosition().toString(),
336                Integer.toString(ed.getNumber())+ed.getPosition().toString()
337        );
338    }
339
340    public String getDevName(int devType) {
341        switch (devType) {
342            case LnConstants.RE_IPL_DIGITRAX_HOST_DS74:
343                return DEVICE_DS74;
344            case LnConstants.RE_IPL_DIGITRAX_HOST_DS78V:
345                return DEVICE_DS78V;
346            case LnConstants.RE_IPL_DIGITRAX_HOST_PM74:
347                return DEVICE_PM74;
348            case LnConstants.RE_IPL_DIGITRAX_HOST_SE74:
349                return DEVICE_SE74;
350            default:
351                throw new IllegalArgumentException("Bad Device Type: " +
352                    Integer.toString(devType) + ".");
353        }
354    }
355
356    public int getDevType(String deviceName) {
357        switch (deviceName) {
358            case DEVICE_DS74:
359                return LnConstants.RE_IPL_DIGITRAX_HOST_DS74;
360            case DEVICE_DS78V:
361                return LnConstants.RE_IPL_DIGITRAX_HOST_DS78V;
362            case DEVICE_PM74:
363                return LnConstants.RE_IPL_DIGITRAX_HOST_PM74;
364            case DEVICE_SE74:
365                return LnConstants.RE_IPL_DIGITRAX_HOST_SE74;
366            default:
367                throw new IllegalArgumentException("Wrong device name: "+deviceName+".");
368        }
369    }
370
371    public boolean loadTheXML() {
372        log.debug("loadXML: starting; current getCountOfDevicesWithRoutes is {}",
373                getCountOfDevicesWithRoutes());
374        // get info from file
375        jmri.jmrix.loconet.configurexml.Digitrax7thGenAccyRoutesXML xmlThingy =
376                new jmri.jmrix.loconet.configurexml.Digitrax7thGenAccyRoutesXML(this);
377        xmlThingy.loadXML();
378
379        log.debug("loadTheXML: Finished reading the 'DigitraxRoutes' file.");
380        return true;
381    }
382
383    public  int getCountOfDevicesWithRoutes() {
384        return devicesRoutes.size();
385    }
386
387    public  void addDeviceRoutesRoute(String devType, int serNum, int baseNum,
388            int[][] turnouts, RouteSwitchPositionEnum[][] positions) {
389        // get device type (IPL) number
390        int idevNumber = LnSimple7thGenDeviceRoutes.getDeviceType(devType);
391
392        log.debug("addDeviceRoutesRoute: checking for device already entered - devNumber {}, serNum {}.",
393                idevNumber, serNum);
394        // get a device by number & serial number
395        LnSimple7thGenDeviceRoutes  existingDevRts =
396                devicesRoutes.getDeviceRoutes(idevNumber, serNum );
397
398        if (existingDevRts != null) {
399            devicesRoutes.removeExistingDevice(idevNumber, serNum);
400            log.debug("removed previous device in favor of the current data");
401        }
402        LnSimple7thGenDeviceRoutes ls7gdr = new LnSimple7thGenDeviceRoutes(idevNumber, serNum);
403        ls7gdr.setBaseAddr(baseNum);
404
405        for (int i = 0; i < (devType.equalsIgnoreCase(DEVICE_DS78V)?16:8); ++i) {
406            for (int j = 0; j < 8; ++j) {
407                ls7gdr.setOneEntry(i, j, turnouts[i][j], positions[i][j]);
408            }
409        }
410        addDevice(ls7gdr);
411    }
412
413    public  void addDevice(LnSimple7thGenDeviceRoutes dev) {
414        devicesRoutes.add(dev);
415        log.debug("addDevice: done adding the device!");
416    }
417
418    public  LnSimple7thGenDeviceRoutes getDevice(int i) {
419        return devicesRoutes.getDeviceRoutes(i);
420    }
421
422    public void dispose() {
423        if ( memo != null ) {
424            memo.getLnTrafficController().removeLocoNetListener(~0, this);
425        }
426    }
427
428    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Ln7gAccyRoutesManager.class);
429}