001package jmri.jmrix.bidib; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.Locale; 006import java.util.Map; 007import jmri.JmriException; 008import jmri.Sensor; 009 010import org.bidib.jbidibc.messages.BidibLibrary; 011import org.bidib.jbidibc.messages.Node; 012import org.bidib.jbidibc.messages.message.FeedbackGetRangeMessage; 013//import org.bidib.jbidibc.messages.message.BidibRequestFactory; 014import org.bidib.jbidibc.messages.utils.NodeUtils; 015 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * Implement SensorManager for BiDiB systems. 021 * 022 * @author Bob Jacobsen Copyright (C) 2008 023 * @author Eckart Meyer Copyright (C) 2019-2023 024 */ 025public class BiDiBSensorManager extends jmri.managers.AbstractSensorManager { 026 027 // Whether we accumulate partially loaded turnouts in pendingTurnouts. 028 private boolean isLoading = false; 029 // Turnouts that are being loaded from XML. 030 private final ArrayList<BiDiBSensor> pendingSensors = new ArrayList<>(); 031 private final Map<Node, Integer> pendingNodeMinAddr = new HashMap<>(); 032 private final Map<Node, Integer> pendingNodeMaxAddr = new HashMap<>(); 033 034 public BiDiBSensorManager(BiDiBSystemConnectionMemo memo) { 035 super(memo); 036 } 037 038 /** 039 * {@inheritDoc} 040 */ 041 @Override 042 public BiDiBSystemConnectionMemo getMemo() { 043 return (BiDiBSystemConnectionMemo) memo; 044 } 045 046 /** 047 * {@inheritDoc} 048 */ 049 @Override 050 public void dispose() { 051 super.dispose(); 052 } 053 054 // BiDiB-specific methods 055 /** 056 * {@inheritDoc} 057 */ 058 @Override 059 public Sensor createNewSensor(String systemName, String userName) { 060 log.trace("createNewSensor {} - {}", systemName, userName); 061 //String addr = systemName.substring(getSystemPrefix().length() + 1); 062 // first, check validity 063 try { 064 validateSystemNameFormat(systemName); 065 } catch (IllegalArgumentException e) { 066 log.error("Illegal address", e); 067 throw e; 068 } 069 // OK, make 070 BiDiBSensor s = new BiDiBSensor(systemName, this); 071 s.setUserName(userName); 072 073 synchronized (pendingSensors) { 074 if (isLoading) { 075 pendingSensors.add(s); 076 if (s.getAddr().isFeedbackAddr()) { 077 // try to build minimum/maximum address to use bulk query later 078 BiDiBAddress a = s.getAddr(); 079 Node node = a.getNode(); 080 if (!pendingNodeMinAddr.containsKey(node) || a.getAddr() < pendingNodeMinAddr.get(node)) { 081 pendingNodeMinAddr.put(node, a.getAddr()); 082 } 083 if (!pendingNodeMaxAddr.containsKey(node) || a.getAddr() > pendingNodeMaxAddr.get(node)) { 084 pendingNodeMaxAddr.put(node, a.getAddr()); 085 } 086 } 087 } else { 088 s.finishLoad(); 089 } 090 } 091 092 return s; 093 } 094 095 /** 096 * This function is invoked before an XML load is started. We defer initialization of the 097 * newly created turnouts until finishLoad because the feedback type might be changing as we 098 * are parsing the XML. 099 */ 100 public void startLoad() { 101 log.debug("Sensor manager : start load"); 102 synchronized (pendingSensors) { 103 isLoading = true; 104 } 105 } 106 107 /** 108 * This function is invoked after the XML load is complete and all Sensors are instantiated 109 * and their type is read in. We use this hook to finalize the construction of the 110 * objects whose instantiation was deferred until the feedback type was known. 111 */ 112 public void finishLoad() { 113 log.info("Sensor manager : finish load"); 114 synchronized (pendingSensors) { 115 pendingSensors.forEach((s) -> { 116 if (!s.getAddr().isFeedbackAddr()) { 117 // sensor is not a feedback (BiDiB BM), may be a port input 118 s.finishLoad(); 119 } 120 }); 121 // now request feedbacks as bulk request from each node 122 pendingNodeMinAddr.forEach((node, min) -> { 123 updateNodeFeedbacks(node, min, pendingNodeMaxAddr.get(node)); 124 }); 125 pendingNodeMinAddr.clear(); 126 pendingNodeMaxAddr.clear(); 127 pendingSensors.clear(); 128 isLoading = false; 129 } 130 } 131 132 public void updateNodeFeedbacks(Node node) { 133 updateNodeFeedbacks(node, 0, 128); 134 } 135 136 public void updateNodeFeedbacks(Node node, int min, int max) { 137 BiDiBTrafficController tc = getMemo().getBiDiBTrafficController(); 138 int bmSize = tc.getNodeFeature(node, BidibLibrary.FEATURE_BM_SIZE); 139 if (bmSize > 0) { 140 int first = (min / 8) * 8; 141 //int max = pendingNodeMaxAddr.get(node); 142 if (max > (bmSize - 1)) { 143 max = (bmSize - 1); 144 } 145 int end = ((max + 8) / 8) * 8; //exclusive end address 146 log.debug("sensor finish load: node: {}, requesting feedback from {} to {}", node, first, end); 147 tc.sendBiDiBMessage(new FeedbackGetRangeMessage(first, end), node); 148 } 149 } 150 151 /** 152 * {@inheritDoc} 153 */ 154 @Override 155 public String createSystemName(String curAddress, String prefix) throws JmriException { 156 log.trace("createSystemName from {} - {}", curAddress, prefix); 157 try { 158 int i = 1; 159 int curNum = Integer.parseInt(curAddress); 160 for (Sensor s : getNamedBeanSet()) { 161 //log.trace("turnout: {}/{} {}", i, curNum, s.getSystemName()); 162 if (i++ == curNum) { 163 return s.getSystemName(); 164 } 165 } 166 } catch (java.lang.NumberFormatException ex) { 167 throw new JmriException("Hardware Address passed "+curAddress+" should be a number"); 168 } 169// // first, check validity 170// try { 171// validateAddressFormat(curAddress); 172// } catch (IllegalArgumentException e) { 173// throw new JmriException(e.toString()); 174// } 175// // getSystemPrefix() unsigned int with "+" as service to user 176// String newAddress = CbusAddress.validateSysName(curAddress); 177// return prefix + typeLetter() + newAddress; 178 return prefix + typeLetter() + curAddress; 179 } 180 181 /** 182 * {@inheritDoc} 183 */ 184 @Override 185 public boolean allowMultipleAdditions(String systemName) { 186 return true; 187 } 188 189 /** 190 * {@inheritDoc} 191 */ 192 @Override 193 public String validateSystemNameFormat(String name, Locale locale) { 194 log.trace("validateSystemNameFormat: name: {}, typeLetter: {}", name, typeLetter()); 195 validateSystemNamePrefix(name, locale); 196 //validateAddressFormat(name.substring(getSystemNamePrefix().length())); 197 if (!BiDiBAddress.isValidSystemNameFormat(name, typeLetter(), getMemo())) { 198 throw new jmri.NamedBean.BadSystemNameException(Locale.getDefault(), "InvalidSystemName",name); 199 } 200 return name; 201 } 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 public NameValidity validSystemNameFormat(String systemName) { 208 log.trace("validSystemNameFormat: systemNname: {}", systemName); 209 210 if (systemName.length() <= getSystemPrefix().length()) { 211 return NameValidity.INVALID; 212 } 213 214// try { 215// validateAddressFormat(addr); 216// } catch (IllegalArgumentException e) { 217// return NameValidity.INVALID; 218// } 219 return NameValidity.VALID; 220 } 221 222 /** 223 * Work out the details for BiDiB hardware address validation. Logging of 224 * handled cases no higher than WARN. 225 * 226 * @param address the hardware address to check 227 * @throws IllegalArgumentException when delimiter is not found 228 */ 229 //TODO! 230// void validateAddressFormat(String address) throws IllegalArgumentException { 231// String newAddress = CbusAddress.validateSysName(address); 232// log.debug("validated system name {}", newAddress); 233// } 234 235 /** 236 * {@inheritDoc} 237 */ 238 @Override 239 public String getEntryToolTip() { 240 return Bundle.getMessage("AddInputEntryToolTip"); 241 } 242 243 /* 244 * {@inheritDoc} Send a query message to get all sensors. 245 */ 246 @Override 247 public void updateAll() { 248 BiDiBTrafficController tc = getMemo().getBiDiBTrafficController(); 249 //BidibRequestFactory rf = tc.getBidib().getRootNode().getRequestFactory(); 250 tc.getNodeList().forEach( (uid, node) -> { 251 int bmSize = tc.getNodeFeature(node, BidibLibrary.FEATURE_BM_SIZE); 252 if (NodeUtils.hasFeedbackFunctions(node.getUniqueId()) && bmSize > 0 ) { 253 log.info("Requesting feedback status ({} sensors) on node {}", bmSize, node); 254 tc.sendBiDiBMessage(new FeedbackGetRangeMessage(0, bmSize), node); 255 } 256/* TODO 257 Feature f = tc.findNodeFeature(node, BidibLibrary.FEATURE_CTRL_INPUT_COUNT); 258 if (NodeUtils.hasSwitchFunctions(node.getUniqueId()) && (f == null || f.getValue() > 0) ) { 259 log.info("Requesting input port status on node {}", node); 260 if (node.getProtocolVersion().isHigherThan(ProtocolVersion.VERSION_0_6)) { 261 // fast bulk query of all ports (new in bidib protocol version 0.7) 262 BidibCommandMessage m = (BidibCommandMessage)rf.createPortQueryAll(1 << BidibLibrary.BIDIB_PORTTYPE_INPUT, 0x0000, 0xFFFF); 263 tc.sendBiDiBMessage(m, node); 264 } 265 else { 266 // old version - request every single sensor 267 getNamedBeanSet().forEach((nb) -> { 268 if (nb instanceof BiDiBSensor) { 269 BiDiBAddress addr = new BiDiBAddress(((BiDiBSensor) nb).getSystemName(), typeLetter(), getMemo()); 270 if (addr.isValid() && addr.isPortAddr() && addr.getNode().equals(node)) { 271 BidibCommandMessage m = (BidibCommandMessage)rf.createLcPortQuery(tc.getPortModel(node), LcOutputType.INPUTPORT, addr.getAddr()); 272 log.trace("...from port {}", addr.getAddr()); 273 tc.sendBiDiBMessage(m, node); 274 } 275 } 276 }); 277 } 278 } 279*/ 280 }); 281 } 282 283 284 private final static Logger log = LoggerFactory.getLogger(BiDiBSensorManager.class); 285 286}