001package jmri.jmrix.cmri.serial.serialdriver.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.AbstractSerialConnectionConfig;
010import jmri.jmrix.cmri.CMRISystemConnectionMemo;
011import jmri.jmrix.cmri.serial.SerialNode;
012import jmri.jmrix.cmri.serial.SerialTrafficController;
013import jmri.jmrix.cmri.serial.serialdriver.ConnectionConfig;
014import jmri.jmrix.cmri.serial.serialdriver.SerialDriverAdapter;
015import jmri.jmrix.configurexml.AbstractSerialConnectionConfigXml;
016import jmri.util.FileUtil;
017import jmri.util.jdom.JDOMUtil;
018import jmri.util.prefs.JmriConfigurationProvider;
019import jmri.util.xml.XMLUtil;
020
021import org.jdom2.Element;
022import org.jdom2.JDOMException;
023import org.w3c.dom.Document;
024
025/**
026 * Handle XML persistence of layout connections by persisting the
027 * SerialDriverAdapter (and connections). Note this is named as the XML version
028 * of a ConnectionConfig object, but it's actually persisting the
029 * SerialDriverAdapter.
030 * <p>
031 * This class is invoked from {@link jmri.jmrix.configurexml.JmrixConfigPaneXml}
032 * on write, as that class is the one actually registered. Reads are brought
033 * here directly via the class attribute in the XML.
034 *
035 * @author Bob Jacobsen Copyright: Copyright (c)
036 * @author Chuck Catania Copyright: Copyright (c) 2014, 2015, 2016, 2017
037 *
038 */
039public class ConnectionConfigXml extends AbstractSerialConnectionConfigXml {
040
041    public ConnectionConfigXml() {
042        super();
043    }
044
045    /**
046     * Write out the SerialNode objects too
047     *
048     * @param e Element being extended
049     */
050    @Override
051    protected void extendElement(Element e) {
052        var memo = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo());
053        String externalConfig = memo.getExternalConfig();
054        if (externalConfig != null) {
055            Element element = new Element("external-config");
056            element.addContent(externalConfig);
057            e.addContent(element);
058            storeExternalConfig(externalConfig);
059        } else {
060            storeConfig(e);
061        }
062    }
063
064    private void storeExternalConfig(String externalConfig) {
065        try {
066            Document doc = XMLUtil.createDocument("external-cmri-configuration", JmriConfigurationProvider.NAMESPACE, null, null); // NOI18N
067            var root = doc.getDocumentElement();
068            Element config = new Element("config");
069            storeConfig(config);
070            var w3cElement = JDOMUtil.toW3CElement(config);
071            root.appendChild(root.getOwnerDocument().importNode(w3cElement, true));
072            String filename = FileUtil.getExternalFilename(externalConfig);
073            try (final OutputStream os = new FileOutputStream(filename)) {
074                XMLUtil.write(doc, os, StandardCharsets.UTF_8.name());
075            }
076        } catch (IOException | JDOMException e) {
077            throw new RuntimeException(e);
078        }
079    }
080
081    private void storeConfig(Element e) {
082        // Create a polling list from the configured nodes
083        StringBuilder polllist = new StringBuilder("");
084        SerialTrafficController tcPL = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController();
085        SerialNode plNode = (SerialNode) tcPL.getNode(0);
086        int index = 1;
087        while (plNode != null) {
088            if (index != 1) {
089                polllist.append(",");
090            }
091            polllist.append(Integer.toString(plNode.getNodeAddress()));
092            plNode = (SerialNode) tcPL.getNode(index);
093            index++;
094        }
095
096        Element l = new Element("polllist");
097        l.setAttribute("pollseq", polllist.toString());
098        e.addContent(l);
099
100        SerialTrafficController tc = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController();
101        SerialNode node = (SerialNode) tc.getNode(0);
102        index = 1;
103
104        while (node != null) {
105            // add node as an element
106            Element n = new Element("node");
107            n.setAttribute("name", Integer.toString(node.getNodeAddress()));
108            e.addContent(n);
109            // add parameters to the node as needed
110            n.addContent(makeParameter("nodetype", Integer.toString(node.getNodeType())));
111            n.addContent(makeParameter("bitspercard", Integer.toString(node.getNumBitsPerCard())));
112            n.addContent(makeParameter("transmissiondelay", Integer.toString(node.getTransmissionDelay())));
113            n.addContent(makeParameter("num2lsearchlights", Integer.toString(node.getNum2LSearchLights())));
114            n.addContent(makeParameter("pulsewidth", Integer.toString(node.getPulseWidth())));
115
116            StringBuilder value = new StringBuilder();
117            for (int i = 0; i < node.getLocSearchLightBits().length; i++) {
118                value.append(Integer.toHexString(node.getLocSearchLightBits()[i] & 0xF));
119            }
120            n.addContent(makeParameter("locsearchlightbits", value.toString()));
121            value = new StringBuilder();
122            for (int i = 0; i < node.getCardTypeLocation().length; i++) {
123                value.append(Integer.toHexString(node.getCardTypeLocation()[i] & 0xF));
124            }
125            n.addContent(makeParameter("cardtypelocation", value.toString()));
126            log.debug("Node {} Card Type Written = {}", node.nodeAddress, value);
127
128            // CMRInet Options
129            value = new StringBuilder("");
130            for (int i = 0; i < SerialNode.NUMCMRINETOPTS; i++) {
131                value.append(Integer.toHexString((node.getCMRInetOpts(i) & 0xF)));
132            }
133            n.addContent(makeParameter("cmrinetoptions", value.toString().toUpperCase()));
134            log.debug("Node {} NET Options Written = {}", node.nodeAddress, value);
135
136            // cpNode Options  Classic CMRI nodes do not have options
137            if (node.getNodeType() == SerialNode.CPNODE || node.getNodeType() == SerialNode.CPMEGA) {
138                value = new StringBuilder();
139                for (int i = 0; i < SerialNode.NUMCPNODEOPTS; i++) {
140                    value.append(Integer.toHexString((node.getcpnodeOpts(i) & 0xF)));
141                }
142                n.addContent(makeParameter("cpnodeoptions", value.toString().toUpperCase()));
143                log.debug("Node {} NODE Options Written = {}", node.nodeAddress, value);
144            }
145
146            // node description
147            n.addContent(makeParameter("cmrinodedesc", node.getcmriNodeDesc()));
148
149            // look for the next node
150            node = (SerialNode) tc.getNode(index);
151            index++;
152        }
153    }
154
155    protected Element makeParameter(String name, String value) {
156        Element p = new Element("parameter");
157        p.setAttribute("name", name);
158        p.addContent(value);
159        return p;
160    }
161
162    @Override
163    protected void getInstance() {
164        if (adapter == null) {
165            adapter = new SerialDriverAdapter();
166        }
167    }
168
169    @Override
170    protected void getInstance(Object object) {
171        adapter = ((AbstractSerialConnectionConfig) object).getAdapter();
172    }
173
174    @Override
175    protected void unpackElement(Element shared, Element perNode) {
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) {
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}