001package jmri.jmrix.cmri.serial.networkdriver.configurexml;
002
003import java.io.*;
004import java.nio.charset.StandardCharsets;
005import java.util.List;
006import java.util.StringTokenizer;
007
008import jmri.jmrit.XmlFile;
009import jmri.jmrix.cmri.CMRISystemConnectionMemo;
010import jmri.jmrix.cmri.serial.SerialNode;
011import jmri.jmrix.cmri.serial.SerialTrafficController;
012import jmri.jmrix.cmri.serial.networkdriver.ConnectionConfig;
013import jmri.jmrix.cmri.serial.networkdriver.NetworkDriverAdapter;
014import jmri.jmrix.configurexml.AbstractNetworkConnectionConfigXml;
015import jmri.util.FileUtil;
016import jmri.util.jdom.JDOMUtil;
017import jmri.util.prefs.JmriConfigurationProvider;
018import jmri.util.xml.XMLUtil;
019
020import org.jdom2.Element;
021import org.jdom2.JDOMException;
022import org.w3c.dom.Document;
023
024/**
025 * Handle XML persistence of layout connections by persisting the
026 * NetworkDriverAdapter (and connections).
027 * <p>
028 * Note this is named as the XML version of a ConnectionConfig object, but it's
029 * actually persisting the NetworkDriverAdapter.
030 * <p>
031 * This class is invoked from jmrix.JmrixConfigPaneXml on write, as that class
032 * is the one actually registered. Reads are brought here directly via the class
033 * attribute in the XML.
034 *
035 * @author Bob Jacobsen Copyright: Copyright (c) 2003, 2015
036 * @author kcameron Copyright (C) 2010 added multiple connections
037 */
038public class ConnectionConfigXml extends AbstractNetworkConnectionConfigXml {
039
040    public ConnectionConfigXml() {
041        super();
042    }
043
044    @Override
045    protected void getInstance() {
046        if(adapter == null) {
047           adapter = new NetworkDriverAdapter();
048        }
049    }
050
051    @Override
052    protected void getInstance(Object object) {
053        adapter = ((ConnectionConfig) object).getAdapter();
054    }
055
056    /**
057     * Write out the SerialNode objects too
058     *
059     * @param e Element being extended
060     */
061    @Override
062    protected void extendElement(Element e) {
063        var memo = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo());
064        String externalConfig = memo.getExternalConfig();
065        if (externalConfig != null) {
066            Element element = new Element("external-config");
067            element.addContent(externalConfig);
068            e.addContent(element);
069            storeExternalConfig(externalConfig);
070        } else {
071            storeConfig(e);
072        }
073    }
074
075    private void storeExternalConfig(String externalConfig) {
076        try {
077            Document doc = XMLUtil.createDocument("external-cmri-configuration", JmriConfigurationProvider.NAMESPACE, null, null); // NOI18N
078            var root = doc.getDocumentElement();
079            Element config = new Element("config");
080            storeConfig(config);
081            var w3cElement = JDOMUtil.toW3CElement(config);
082            root.appendChild(root.getOwnerDocument().importNode(w3cElement, true));
083            String filename = FileUtil.getExternalFilename(externalConfig);
084            try (final OutputStream os = new FileOutputStream(filename)) {
085                XMLUtil.write(doc, os, StandardCharsets.UTF_8.name());
086            }
087        } catch (IOException | JDOMException e) {
088            throw new RuntimeException(e);
089        }
090    }
091
092    private void storeConfig(Element e) {
093        // Create a polling list from the configured nodes
094        StringBuilder polllist = new StringBuilder("");
095        SerialTrafficController tcPL = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController();
096        SerialNode plNode = (SerialNode) tcPL.getNode(0);
097        int index = 1;
098        while (plNode != null) {
099            if (index != 1) {
100                polllist.append(",");
101            }
102            polllist.append(Integer.toString(plNode.getNodeAddress()));
103            plNode = (SerialNode) tcPL.getNode(index);
104            index++;
105        }
106
107        Element l = new Element("polllist");
108        l.setAttribute("pollseq", polllist.toString());
109        e.addContent(l);
110
111        SerialTrafficController tc = ((CMRISystemConnectionMemo)adapter.getSystemConnectionMemo()).getTrafficController();
112        SerialNode node = (SerialNode) tc.getNode(0);
113        index = 1;
114
115        while (node != null) {
116            // add node as an element
117            Element n = new Element("node");
118            n.setAttribute("name", "" + node.getNodeAddress());
119            e.addContent(n);
120            // add parameters to the node as needed
121            n.addContent(makeParameter("nodetype", "" + node.getNodeType()));
122            n.addContent(makeParameter("bitspercard", "" + node.getNumBitsPerCard()));
123            n.addContent(makeParameter("transmissiondelay", "" + node.getTransmissionDelay()));
124            n.addContent(makeParameter("num2lsearchlights", "" + node.getNum2LSearchLights()));
125            n.addContent(makeParameter("pulsewidth", "" + node.getPulseWidth()));
126            StringBuilder value = new StringBuilder("");
127            for (int i = 0; i < node.getLocSearchLightBits().length; i++) {
128                value.append(Integer.toHexString(node.getLocSearchLightBits()[i] & 0xF));
129            }
130            n.addContent(makeParameter("locsearchlightbits", value.toString()));
131            value = new StringBuilder("");
132            for (int i = 0; i < node.getCardTypeLocation().length; i++) {
133                value.append(Integer.toHexString(node.getCardTypeLocation()[i] & 0xF));
134            }
135            n.addContent(makeParameter("cardtypelocation", value.toString()));
136            log.debug("Node {} Card Type Written = {}", node.nodeAddress, value);
137
138            // CMRInet Options
139            value = new StringBuilder("");
140            for (int i = 0; i < SerialNode.NUMCMRINETOPTS; i++) {
141                value.append(Integer.toHexString((node.getCMRInetOpts(i) & 0xF)));
142            }
143            n.addContent(makeParameter("cmrinetoptions", value.toString().toUpperCase()));
144            log.debug("Node {} NET Options Written = {}", node.nodeAddress, value);
145
146            // cpNode Options  Classic CMRI nodes do not have options
147            if (node.getNodeType() == SerialNode.CPNODE || node.getNodeType() == SerialNode.CPMEGA
148                    || node.getNodeType() == SerialNode.ESP32NODE) {
149                value = new StringBuilder();
150                for (int i = 0; i < SerialNode.NUMCPNODEOPTS; i++) {
151                    value.append(Integer.toHexString((node.getcpnodeOpts(i) & 0xF)));
152                }
153                n.addContent(makeParameter("cpnodeoptions", value.toString().toUpperCase()));
154                log.debug("Node {} NODE Options Written = {}", node.nodeAddress, value);
155            }
156
157            // node description
158            n.addContent(makeParameter("cmrinodedesc", node.getcmriNodeDesc()));
159
160            // look for the next node
161            node = (SerialNode) tc.getNode(index);
162            index++;
163        }
164    }
165
166    protected Element makeParameter(String name, String value) {
167        Element p = new Element("parameter");
168        p.setAttribute("name", name);
169        p.addContent(value);
170        return p;
171    }
172
173    @Override
174    protected void unpackElement(Element shared, Element perNode) {
175
176        Element externalConfig = shared.getChild("external-config");
177        if (externalConfig != null) {
178            loadExternalConfig(externalConfig);
179        } else {
180            loadConfig(shared);
181        }
182    }
183
184    private void loadExternalConfig(Element externalConfig) {
185        try {
186            String filename = externalConfig.getTextTrim();
187            ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo())
188                    .setExternalConfig(filename);
189            File file = new File(FileUtil.getExternalFilename(filename));
190            Element root = new XmlFile().rootFromURL(FileUtil.fileToURL(file));
191            Element config = root.getChild("config");
192            loadConfig(config);
193        } catch (IOException | JDOMException e) {
194            throw new RuntimeException(e);
195        }
196    }
197
198    private void loadConfig(Element shared) {
199        // --------------------------------------
200        // Load the poll list sequence if present
201        // --------------------------------------
202        List<Element> pl = shared.getChildren("polllist");
203        if (!pl.isEmpty()) {
204            Element ps = pl.get(0);
205            if (ps != null) {
206                String pseq = ps.getAttributeValue("pollseq");
207                if (pseq != null) {
208                    StringTokenizer nodes = new StringTokenizer(pseq, " ,");
209                    SerialTrafficController tcPL = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController();
210                    while (nodes.hasMoreTokens()) {
211                        tcPL.cmriNetPollList.add(Integer.parseInt(nodes.nextToken()));
212                    }
213                    log.debug("Poll List = {}", tcPL.cmriNetPollList);
214                }
215            }
216        }
217
218        // Load the node specific parameters
219        int pollListSize = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController().cmriNetPollList.size();
220        int nextPollPos = pollListSize + 1;
221
222        List<Element> l = shared.getChildren("node");
223        for (int i = 0; i < l.size(); i++) {
224            Element n = l.get(i);
225            int addr = Integer.parseInt(n.getAttributeValue("name"));
226            int type = Integer.parseInt(findParmValue(n, "nodetype"));
227            int bpc = Integer.parseInt(findParmValue(n, "bitspercard"));
228            int delay = Integer.parseInt(findParmValue(n, "transmissiondelay"));
229            int num2l = Integer.parseInt(findParmValue(n, "num2lsearchlights"));
230            int pulseWidth = 500;
231            if ((findParmValue(n, "pulsewidth")) != null) {
232                pulseWidth = Integer.parseInt(findParmValue(n, "pulsewidth"));
233            }
234
235            String slb = findParmValue(n, "locsearchlightbits");
236            String ctl = findParmValue(n, "cardtypelocation");
237            String opts = "";
238
239            // create node (they register themselves)
240            SerialNode node = new SerialNode(addr, type,((CMRISystemConnectionMemo)adapter.getSystemConnectionMemo()).getTrafficController());
241            node.setNumBitsPerCard(bpc);
242            node.setTransmissionDelay(delay);
243            node.setNum2LSearchLights(num2l);
244            node.setPulseWidth(pulseWidth);
245
246            // From the loaded poll list, assign the poll list position to the node
247            boolean assigned = false;
248            if (pollListSize > 0) {
249                for (int pls = 0; pls < pollListSize; pls++) {
250                    if (((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController().cmriNetPollList.get(pls) == node.getNodeAddress()) {
251                        node.setPollListPosition(pls + 1);
252                        assigned = true;
253                    }
254                }
255                if (!assigned) {
256                    node.setPollListPosition(nextPollPos++);
257                }
258            }
259
260            // CMRInet Options
261            //----------------
262            if (findParmValue(n, "cmrinetoptions") != null) {
263                opts = findParmValue(n, "cmrinetoptions");
264                // Convert and load the  value into the node options array
265                for (int j = 0; j < SerialNode.NUMCMRINETOPTS; j++) {
266                    node.setCMRInetOpts(j, (opts.charAt(j) - '0'));
267                }
268                log.debug("Node {} NET Options Read = {}", node.nodeAddress, opts);
269
270            } else {
271                // This must be the first time the nodes were loaded into a cpNode
272                // supported version.  Set the Auto Poll option to avoid confusion
273                // with installed configurations.
274                for (int j = 0; j < SerialNode.NUMCMRINETOPTS; j++) {
275                    node.setCMRInetOpts(j, 0);
276                }
277                node.setOptNet_AUTOPOLL(1);
278                log.debug("Node {} AUTO POLL Set ", node.nodeAddress);
279
280            }
281
282            for (int j = 0; j < slb.length(); j++) {
283                node.setLocSearchLightBits(j, (slb.charAt(j) - '0'));
284            }
285
286            for (int j = 0; j < ctl.length(); j++) {
287                node.setCardTypeLocation(j, (ctl.charAt(j) - '0'));
288            }
289
290            if (type == SerialNode.CPNODE || type == SerialNode.CPMEGA || type == SerialNode.ESP32NODE) {
291                // cpNode Options
292                if (findParmValue(n, "cpnodeoptions") != null) {
293                    opts = findParmValue(n, "cpnodeoptions");
294                    // Convert and load the  value into the node options array
295                    for (int j = 0; j < SerialNode.NUMCPNODEOPTS; j++) {
296                        node.setcpnodeOpts(j, (opts.charAt(j) - '0'));
297                    }
298                }
299
300                log.debug("Node {} NODE Options Read = {}", node.nodeAddress, opts);
301            }
302
303            if (findParmValue(n, "cmrinodedesc") != null) {
304                node.setcmriNodeDesc(findParmValue(n, "cmrinodedesc"));
305            } else {
306                log.debug("No Description - Node {}", addr);
307            }
308
309            // Trigger initialization of this Node to reflect these parameters
310            ((CMRISystemConnectionMemo)adapter.getSystemConnectionMemo()).getTrafficController().initializeSerialNode(node);
311        }
312    }
313
314    @Override
315    protected void register() {
316        this.register(new ConnectionConfig(adapter));
317    }
318
319    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConnectionConfigXml.class);
320}