001package jmri.jmrix.bidib; 002 003import java.util.Map; 004import java.util.LinkedList; 005import java.util.SortedSet; 006 007import jmri.InstanceManager; 008 009import org.bidib.jbidibc.core.BidibInterface; 010import org.bidib.jbidibc.core.node.BidibNode; 011import org.bidib.jbidibc.messages.BidibLibrary; 012import org.bidib.jbidibc.messages.Feature; 013import org.bidib.jbidibc.messages.FeatureData; 014import org.bidib.jbidibc.messages.Node; 015import org.bidib.jbidibc.messages.StringData; 016import org.bidib.jbidibc.messages.exception.ProtocolException; 017import org.bidib.jbidibc.messages.utils.ByteUtils; 018 019import org.slf4j.Logger; 020import org.slf4j.LoggerFactory; 021 022/** 023 * This class initializes or deinitializes a BiDiB node when it is found on system startup or if it 024 * is discovered or lost while the system is running.\ 025 * 026 * The real work is done in its own thread and from a node queue. Initializing is a time consuming 027 * process since a lot of data is read from the node. 028 * 029 * @author Eckart Meyer Copyright (C) 2023 030 */ 031public class BiDiBNodeInitializer implements Runnable { 032 033 private static class SimplePair { 034 public Node node; 035 public boolean isNewNode; //true: new node, false: node lost 036 037 public SimplePair(Node node, boolean isNewNode) { 038 this.node = node; 039 this.isNewNode = isNewNode; 040 } 041 } 042 043 private final Map<Long, Node> nodes; 044 private final BidibInterface bidib; 045 private final BiDiBTrafficController tc; 046 private SimplePair currentNode; 047 private Thread initThread; 048 private final LinkedList<SimplePair> queue; 049 050 051 public BiDiBNodeInitializer(BiDiBTrafficController tc, BidibInterface bidib, Map<Long, Node> nodes) { 052 this.bidib = bidib; 053 this.nodes = nodes; 054 this.tc = tc; 055 queue = new LinkedList<>(); 056 log.debug("BiDiB node initializer created"); 057 } 058 059 /** 060 * Get everything we need from the node. The node must already be inserted into the BiDiB node list. 061 * 062 * @param node node to initialize 063 * @throws ProtocolException when features can't be loaded 064 */ 065 public void initNode(Node node) throws ProtocolException { 066 if (node != null) { 067 BidibNode bidibNode = bidib.getNode(node); 068 log.info("+++ found node: {}", node); 069 070 int magic = bidibNode.getMagic(0); 071 log.debug("Node returned magic: 0x{}", ByteUtils.magicToHex(magic)); 072 if (magic == 0xAFFE) { 073 node.setStoredString(StringData.INDEX_PRODUCTNAME, bidibNode.getString(0, StringData.INDEX_PRODUCTNAME).getValue()); 074 node.setStoredString(StringData.INDEX_USERNAME, bidibNode.getString(StringData.NAMESPACE_NODE, StringData.INDEX_USERNAME).getValue()); 075 node.setProtocolVersion(bidibNode.getProtocolVersion()); 076 node.setSoftwareVersion(bidibNode.getSwVersion()); 077 log.info("Product name: {}", node.getStoredString(StringData.INDEX_PRODUCTNAME)); 078 log.info("User name: {}", node.getStoredString(StringData.INDEX_USERNAME)); 079 log.info("Protocol version: {}", node.getProtocolVersion()); 080 log.info("Software version: {}", node.getSoftwareVersion()); 081 082 try { 083 FeatureData features = bidibNode.getFeaturesAll(); 084 log.info("featureCount: {}", features.getFeatureCount()); 085 if (features.isStreamingSupport()) { 086 int k = 1;//counter is for debug only 087 for (Feature feature : features.getFeatures()) { 088 log.trace("feature #{}/{}", k++, features.getFeatureCount()); 089 log.info("feature.type: {}, value: {}, name: {}", feature.getType(), feature.getValue(), feature.getFeatureName()); 090 node.setFeature(feature); 091 } 092 } 093 else { 094 Feature feature; 095 int k = 1;//counter is for debug only 096 try { 097 while ((feature = bidibNode.getNextFeature()) != null) { 098 log.trace("feature #{}/{}", k++, features.getFeatureCount()); 099 log.info("feature.type: {}, value: {}, name: {}", feature.getType(), feature.getValue(), feature.getFeatureName()); 100 node.setFeature(feature); 101 } 102 } 103 catch (ProtocolException ex) { 104 log.debug("No more features."); 105 } 106 } 107 } 108 catch (ProtocolException ex) { 109 log.error("Features can't be loaded from node: {}", ex.getMessage()); 110 } 111 log.info("Finished query features."); // NOSONAR 112 113 node.setFeature(new Feature(BidibLibrary.FEATURE_ACCESSORY_MACROMAPPED, 0)); //we do not handle macros in JMRI, so for test, don't assume them to be loaded 114 //node.setFeature(new Feature(BidibLibrary.FEATURE_GEN_SWITCH_ACK, 0)); //Test 115 116 Feature relevantPidBits = Feature.findFeature(node.getFeatures(), BidibLibrary.FEATURE_RELEVANT_PID_BITS); 117 if (relevantPidBits != null) { 118 node.setRelevantPidBits(relevantPidBits.getValue()); 119 } 120 Feature stringSize = Feature.findFeature(node.getFeatures(), BidibLibrary.FEATURE_STRING_SIZE); 121 if (stringSize != null) { 122 node.setStringSize(stringSize.getValue()); 123 } 124 Integer portcount = 0; 125 Feature flatModel = Feature.findFeature(node.getFeatures(), BidibLibrary.FEATURE_CTRL_PORT_FLAT_MODEL_EXTENDED); 126 if (flatModel != null) { 127 portcount = flatModel.getValue() * 256; 128 } 129 flatModel = Feature.findFeature(node.getFeatures(), BidibLibrary.FEATURE_CTRL_PORT_FLAT_MODEL); 130 if (flatModel != null) { 131 portcount += flatModel.getValue(); 132 } 133 if (portcount > 0) { 134 node.setPortFlatModel(portcount); 135 } 136 } 137 log.debug("+++ node init finished: {}", node); 138 } 139 } 140 141 /** 142 * Remove a node from all named beans and from the nodes list 143 * 144 * @param node to remove 145 */ 146 public void nodeLost(Node node) { 147 log.error("BiDiB node lost! {}", node); 148 startNodeUpdate(node, false); 149 } 150 151 /** 152 * Add a node to nodes list and notify all named beans to update 153 * 154 * @param node to add 155 */ 156 public void nodeNew(Node node) { 157 log.warn("New BiDiB node found {}", node); 158 long uid = node.getUniqueId() & 0x0000ffffffffffL; //mask the classid 159 nodes.put(uid, node); 160 startNodeUpdate(node, true); 161 } 162 163 164 // private methods to execute nodeLost/nodeNew in one low priority thread 165 166 private <T> void nodeLost(SortedSet<T> beanSet, long uniqueId) { 167 beanSet.forEach( (nb) -> { 168 if (nb instanceof BiDiBNamedBeanInterface) { 169 BiDiBAddress addr = ((BiDiBNamedBeanInterface)nb).getAddr(); 170 log.trace("check bean: {}", nb); 171 if (addr.getNodeUID() == uniqueId) { 172 addr.invalidate(); 173 ((BiDiBNamedBeanInterface)nb).nodeLost(); 174 } 175 } 176 }); 177 } 178 179 private void nodeLostBeans(long uniqueId) { 180 long uid = uniqueId & 0x0000ffffffffffL; //mask the classid 181 nodeLost(InstanceManager.getDefault(jmri.TurnoutManager.class).getNamedBeanSet(), uid); 182 nodeLost(InstanceManager.getDefault(jmri.SensorManager.class).getNamedBeanSet(), uid); 183 nodeLost(InstanceManager.getDefault(jmri.LightManager.class).getNamedBeanSet(), uid); 184 nodeLost(InstanceManager.getDefault(jmri.ReporterManager.class).getNamedBeanSet(), uid); 185 nodeLost(InstanceManager.getDefault(jmri.SignalMastManager.class).getNamedBeanSet(), uid); 186 nodes.remove(uid); 187 } 188 189 private <T> void nodeNew(SortedSet<T> beanSet, Node node) { 190 beanSet.forEach( (nb) -> { 191 if (nb instanceof BiDiBNamedBeanInterface) { 192 BiDiBAddress addr = ((BiDiBNamedBeanInterface)nb).getAddr(); 193 log.trace("check bean: {}", nb); 194 if (!addr.isValid()) { 195 ((BiDiBNamedBeanInterface)nb).nodeNew(); 196 } 197 } 198 }); 199 } 200 201 private void nodeNewBeans(Node node) { 202 try { 203 tc.getBidib().getRootNode().sysEnable(); 204 } 205 catch (ProtocolException e) { 206 log.warn("failed to ENABLE node {}", node, e); 207 } 208 nodeNew(InstanceManager.getDefault(jmri.TurnoutManager.class).getNamedBeanSet(), currentNode.node); 209 nodeNew(InstanceManager.getDefault(jmri.SensorManager.class).getNamedBeanSet(), currentNode.node); 210 nodeNew(InstanceManager.getDefault(jmri.LightManager.class).getNamedBeanSet(), currentNode.node); 211 nodeNew(InstanceManager.getDefault(jmri.ReporterManager.class).getNamedBeanSet(), currentNode.node); 212 nodeNew(InstanceManager.getDefault(jmri.SignalMastManager.class).getNamedBeanSet(), currentNode.node); 213 BiDiBSensorManager bs = (BiDiBSensorManager)tc.getSystemConnectionMemo().getSensorManager(); 214 if (bs != null) { 215 bs.updateNodeFeedbacks(node); 216 } 217 BiDiBReporterManager br = (BiDiBReporterManager)tc.getSystemConnectionMemo().getReporterManager(); 218 if (br != null) { 219 br.updateNode(node); 220 } 221 } 222 223 /** 224 * Insert a node into the queue. Start Thread if currently not running 225 * 226 * @param node to add or remove 227 * @param isNewNode - true: add new node, false: remove node 228 */ 229 private void startNodeUpdate(Node node, boolean isNewNode) { 230 231 synchronized (queue) { 232 // check if the thread is still working 233 if (queue.isEmpty() || initThread == null || !initThread.isAlive()) { 234 if (initThread != null) { 235 try { 236 initThread.join(1000); //wait until the thread has definitly died 237 } 238 catch (InterruptedException e) {} 239 } 240 initThread = new Thread(this, "NodeInitThread"); //create a new thread 241 initThread.setPriority(Thread.MIN_PRIORITY); 242 queue.add(new SimplePair(node, isNewNode)); 243 initThread.start(); 244 log.debug("thread was started - return"); 245 } 246 else { 247 // Thread running, just add the node to the queue 248 queue.add(new SimplePair(node, isNewNode)); 249 log.debug("thread running, just add node to queue and return"); 250 } 251 } 252 } 253 254 255 /** 256 * Execute queued node init and named beans update. 257 * Finish the thread if the queue is empty 258 */ 259 @Override 260 public void run() { 261 log.debug("starting thread for node initialization"); 262 while (true) { 263 log.trace("-- loop, queue size: {}", queue.size()); 264 synchronized (queue) { 265 log.trace(" currentNode: {}", currentNode); 266 if (currentNode != null) { //if we just processed a node ... 267 queue.removeFirst(); //...remove it from the queue 268 currentNode = null; 269 } 270 currentNode = queue.peekFirst(); //get next from queue 271 if (currentNode == null) { 272 break; //exit while loop and stop thread by exiting run() 273 } 274 } 275 // now do the real work - initialize node and beans 276 if (currentNode.isNewNode) { 277 try { 278 initNode(currentNode.node); 279 nodeNewBeans(currentNode.node); 280 } 281 catch (Exception e) { 282 log.warn("error initializing node {}", currentNode.node, e); 283 } 284 } 285 else { 286 nodeLostBeans(currentNode.node.getUniqueId()); 287 } 288 } 289 log.debug("thread finished for node"); 290 } 291 292 private final static Logger log = LoggerFactory.getLogger(BiDiBNodeInitializer.class); 293}