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