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}