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