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