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