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                    || node.getNodeType() == SerialNode.ESP32NODE) {
139                value = new StringBuilder();
140                for (int i = 0; i < SerialNode.NUMCPNODEOPTS; i++) {
141                    value.append(Integer.toHexString((node.getcpnodeOpts(i) & 0xF)));
142                }
143                n.addContent(makeParameter("cpnodeoptions", value.toString().toUpperCase()));
144                log.debug("Node {} NODE Options Written = {}", node.nodeAddress, value);
145            }
146
147            // node description
148            n.addContent(makeParameter("cmrinodedesc", node.getcmriNodeDesc()));
149
150            // look for the next node
151            node = (SerialNode) tc.getNode(index);
152            index++;
153        }
154    }
155
156    protected Element makeParameter(String name, String value) {
157        Element p = new Element("parameter");
158        p.setAttribute("name", name);
159        p.addContent(value);
160        return p;
161    }
162
163    @Override
164    protected void getInstance() {
165        if (adapter == null) {
166            adapter = new SerialDriverAdapter();
167        }
168    }
169
170    @Override
171    protected void getInstance(Object object) {
172        adapter = ((AbstractSerialConnectionConfig) object).getAdapter();
173    }
174
175    @Override
176    protected void unpackElement(Element shared, Element perNode) {
177        Element externalConfig = shared.getChild("external-config");
178        if (externalConfig != null) {
179            loadExternalConfig(externalConfig);
180        } else {
181            loadConfig(shared);
182        }
183    }
184
185    private void loadExternalConfig(Element externalConfig) {
186        try {
187            String filename = externalConfig.getTextTrim();
188            ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo())
189                    .setExternalConfig(filename);
190            File file = new File(FileUtil.getExternalFilename(filename));
191            Element root = new XmlFile().rootFromURL(FileUtil.fileToURL(file));
192            Element config = root.getChild("config");
193            loadConfig(config);
194        } catch (IOException | JDOMException e) {
195            throw new RuntimeException(e);
196        }
197    }
198
199    private void loadConfig(Element shared) {
200        // --------------------------------------
201        // Load the poll list sequence if present
202        // --------------------------------------
203        List<Element> pl = shared.getChildren("polllist");
204        if (!pl.isEmpty()) {
205            Element ps = pl.get(0);
206            if (ps != null) {
207                String pseq = ps.getAttributeValue("pollseq");
208                if (pseq != null) {
209                    StringTokenizer nodes = new StringTokenizer(pseq, " ,");
210                    SerialTrafficController tcPL = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController();
211                    while (nodes.hasMoreTokens()) {
212                        tcPL.cmriNetPollList.add(Integer.parseInt(nodes.nextToken()));
213                    }
214                    log.debug("Poll List = {}", tcPL.cmriNetPollList);
215                }
216            }
217        }
218
219        // Load the node specific parameters
220        int pollListSize = ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController().cmriNetPollList.size();
221        int nextPollPos = pollListSize + 1;
222
223        List<Element> l = shared.getChildren("node");
224        for (int i = 0; i < l.size(); i++) {
225            Element n = l.get(i);
226            int addr = Integer.parseInt(n.getAttributeValue("name"));
227            int type = Integer.parseInt(findParmValue(n, "nodetype"));
228            int bpc = Integer.parseInt(findParmValue(n, "bitspercard"));
229            int delay = Integer.parseInt(findParmValue(n, "transmissiondelay"));
230            int num2l = Integer.parseInt(findParmValue(n, "num2lsearchlights"));
231            int pulseWidth = 500;
232            if ((findParmValue(n, "pulsewidth")) != null) {
233                pulseWidth = Integer.parseInt(findParmValue(n, "pulsewidth"));
234            }
235
236            String slb = findParmValue(n, "locsearchlightbits");
237            String ctl = findParmValue(n, "cardtypelocation");
238            String opts = "";
239
240            // create node (they register themselves)
241            SerialNode node = new SerialNode(addr, type, ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController());
242            node.setNumBitsPerCard(bpc);
243            node.setTransmissionDelay(delay);
244            node.setNum2LSearchLights(num2l);
245            node.setPulseWidth(pulseWidth);
246
247            // From the loaded poll list, assign the poll list position to the node
248            boolean assigned = false;
249            if (pollListSize > 0) {
250                for (int pls = 0; pls < pollListSize; pls++) {
251                    if (((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController().cmriNetPollList.get(pls) == node.getNodeAddress()) {
252                        node.setPollListPosition(pls + 1);
253                        assigned = true;
254                    }
255                }
256                if (!assigned) {
257                    node.setPollListPosition(nextPollPos++);
258                }
259            }
260
261            // CMRInet Options
262            //----------------
263            if (findParmValue(n, "cmrinetoptions") != null) {
264                opts = findParmValue(n, "cmrinetoptions");
265                // Convert and load the  value into the node options array
266                for (int j = 0; j < SerialNode.NUMCMRINETOPTS; j++) {
267                    node.setCMRInetOpts(j, (opts.charAt(j) - '0'));
268                }
269                log.debug("Node {} NET Options Read = {}", node.nodeAddress, opts);
270
271            } else {
272                // This must be the first time the nodes were loaded into a cpNode
273                // supported version.  Set the Auto Poll option to avoid confusion
274                // with installed configurations.
275                for (int j = 0; j < SerialNode.NUMCMRINETOPTS; j++) {
276                    node.setCMRInetOpts(j, 0);
277                }
278                node.setOptNet_AUTOPOLL(1);
279                log.debug("Node {} AUTO POLL Set ", node.nodeAddress);
280
281            }
282
283            for (int j = 0; j < slb.length(); j++) {
284                node.setLocSearchLightBits(j, (slb.charAt(j) - '0'));
285            }
286
287            for (int j = 0; j < ctl.length(); j++) {
288                node.setCardTypeLocation(j, (ctl.charAt(j) - '0'));
289            }
290
291            if (type == SerialNode.CPNODE || type == SerialNode.CPMEGA || type == SerialNode.ESP32NODE) {
292                // cpNode Options
293                if (findParmValue(n, "cpnodeoptions") != null) {
294                    opts = findParmValue(n, "cpnodeoptions");
295                    // Convert and load the  value into the node options array
296                    for (int j = 0; j < SerialNode.NUMCPNODEOPTS; j++) {
297                        node.setcpnodeOpts(j, (opts.charAt(j) - '0'));
298                    }
299                }
300
301                log.debug("Node {} NODE Options Read = {}", node.nodeAddress, opts);
302            }
303
304            if (findParmValue(n, "cmrinodedesc") != null) {
305                node.setcmriNodeDesc(findParmValue(n, "cmrinodedesc"));
306            } else {
307                log.debug("No Description - Node {}", addr);
308            }
309
310            // Trigger initialization of this Node to reflect these parameters
311            ((CMRISystemConnectionMemo) adapter.getSystemConnectionMemo()).getTrafficController().initializeSerialNode(node);
312        }
313    }
314
315    @Override
316    protected void register() {
317        this.register(new ConnectionConfig(adapter));
318    }
319
320    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConnectionConfigXml.class);
321}