001package jmri.jmrix.bidib; 002 003import java.util.HashMap; 004import java.util.Map; 005import javax.annotation.*; 006 007import jmri.implementation.AbstractSignalMast; 008import jmri.SystemConnectionMemo; 009import jmri.InstanceManager; 010import jmri.SignalMast; 011import org.bidib.jbidibc.messages.enums.LcOutputType; 012 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016/** 017 * This class implements a SignalMast that use <B>BiDiB Accessories</B> 018 * to set aspects. 019 * <p> 020 * This implementation writes out to BiDiB when it's commanded to 021 * change appearance, and updates its internal state when it receives a status feedback from BiDiB. 022 * <p> 023 * System name specifies the creation information: 024 * <pre> 025 * BF$bsm:basic:one-searchlight(123) 026 * </pre> The name is a colon-separated series of terms: 027 * <ul> 028 * <li>B - system prefix 029 * <li>F$bsm - defines signal masts of this type 030 * <li>basic - name of the signaling system 031 * <li>one-searchlight - name of the particular aspect map 032 * <li>(node:123) - BiDiB Accessory address 033 * </ul> 034 * <p> 035 * To keep the state consistent, {@link #setAspect} does not immediately 036 * change the local aspect. Instead, it produces the relevant BiDiB message on the 037 * network, waiting for that to return and do the local state change, notification, etc. 038 * <p> 039 * Based upon {@link jmri.implementation.DccSignalMast} by Kevin Dickerson 040 * 041 * @author Bob Jacobsen Copyright (c) 2017, 2018 042 * @author Eckart Meyer Copyright (c) 2020-2023 043 */ 044public class BiDiBSignalMast extends AbstractSignalMast implements BiDiBNamedBeanInterface { 045 046 BiDiBTrafficController tc; 047 //MessageListener messageListener = null; 048 BiDiBOutputMessageHandler messageHandler = null; 049 BiDiBAddress addr; 050 private char typeLetter; 051 int commandedAspect = -1; 052 053 public BiDiBSignalMast(String sys, String user) { 054 super(sys, user); 055 configureFromName(sys); 056 } 057 058 public BiDiBSignalMast(String sys) { 059 super(sys); 060 configureFromName(sys); 061 } 062 063 public BiDiBSignalMast(String sys, String user, String mastSubType) { 064 super(sys, user); 065 mastType = mastSubType; 066 configureFromName(sys); 067 } 068 069 //protected String mastType = "F$bsm"; 070 protected String mastType = getNamePrefix(); 071 072 static public String getNamePrefix() { 073 return "F$bsm"; 074 } 075 076 077 private void configureFromName(String systemName) { 078 // split out the basic information 079 BiDiBSystemConnectionMemo memo = null; 080 String[] parts = systemName.split(":", 3); //the last part contains a BiDiB address and therfor may contain a colon itself 081 if (parts.length < 3) { 082 log.error("SignalMast system name needs at least three parts: {}", systemName); 083 throw new IllegalArgumentException("System name needs at least three parts: " + systemName); 084 } 085 if (!parts[0].endsWith(mastType)) { 086 log.warn("First part of SignalMast system name is incorrect {} : {}", systemName, mastType); 087 } else { 088 String systemPrefix = parts[0].substring(0, parts[0].indexOf("$") - 1); 089 java.util.List<SystemConnectionMemo> memoList = jmri.InstanceManager.getList(SystemConnectionMemo.class); 090 091 for (SystemConnectionMemo m : memoList) { 092 if (m.getSystemPrefix().equals(systemPrefix)) { 093 if (m instanceof jmri.jmrix.bidib.BiDiBSystemConnectionMemo) { 094 tc = ((BiDiBSystemConnectionMemo) m).getBiDiBTrafficController(); 095 memo = (BiDiBSystemConnectionMemo)m; 096 } else { 097 log.error("Can't create mast \"{}\" because system \"{}}\" is not BiDiBSystemConnectionMemo but rather {}", 098 systemName, systemPrefix, m.getClass()); 099 } 100 break; 101 } 102 } 103 104 if (tc == null) { 105 log.error("No BiDiB connection found for system prefix \"{}\", so mast \"{}\" will not function", 106 systemPrefix, systemName); 107 } 108 } 109 String system = parts[1]; 110 String mast = parts[2]; 111 112 mast = mast.substring(0, mast.indexOf("(")); 113 log.trace("In configureFromName setMastType to {}", mast); 114 setMastType(mast); 115 116 if (memo != null) { 117 char accessoryTypeLetter = 'T'; 118 String accessorySystemName = memo.getSystemPrefix() + accessoryTypeLetter + parts[2].substring(parts[2].indexOf("(") + 1, parts[2].indexOf(")")); 119 addr = new BiDiBAddress(accessorySystemName, accessoryTypeLetter, memo); 120 log.info("New SIGNALMAST created: {} {} -> {}", systemName, accessorySystemName, addr); 121 typeLetter = accessoryTypeLetter; 122// if (!addr.isValid()) { 123// log.warn("BiDiB signal mast accessory address SystemName {} is not in the correct format", systemName); 124// } 125 } 126 configureSignalSystemDefinition(system); 127 configureAspectTable(system, mast); 128 129 createSignalMastListener(); 130 131 messageHandler.sendQueryConfig(); 132 } 133 134 135 protected HashMap<String, Integer> appearanceToOutput = new HashMap<>(); 136 137 public void setOutputForAppearance(String appearance, int number) { 138 log.debug("setOutputForAppearance: {} -> {}", appearance, number); 139 if (appearanceToOutput.containsKey(appearance)) { 140 log.debug("Appearance {} is already defined as {}", appearance, appearanceToOutput.get(appearance)); 141 appearanceToOutput.remove(appearance); 142 } 143 appearanceToOutput.put(appearance, number); 144 } 145 146 public int getOutputForAppearance(String appearance) { 147 if (!appearanceToOutput.containsKey(appearance)) { 148 log.error("Trying to get appearance {} but it has not been configured", appearance); 149 return -1; 150 } 151 return appearanceToOutput.get(appearance); 152 } 153 154 /* 155 0. "Stop" 156 1. "Take Siding" 157 2. "Stop-Orders" 158 3. "Stop-Proceed" 159 4. "Restricting" 160 5. "Permissive" 161 6. "Slow-Approach" 162 7. "Slow" 163 8. "Slow-Medium" 164 9. "Slow-Limited" 165 10. "Slow-Clear" 166 11. "Medium-Approach" 167 12. "Medium-Slow" 168 13. "Medium" 169 14. "Medium-Ltd" 170 15. "Medium-Clr" 171 16. "Limited-Approach" 172 17. "Limited-Slow" 173 18. "Limited-Med" 174 19. "Limited" 175 20. "Limited-Clear" 176 21. "Approach" 177 22. "Advance-Appr" 178 23. "Appr-Slow" 179 24. "Adv-Appr-Slow" 180 25. "Appr-Medium" 181 26. "Adv-Appr-Med" 182 27. "Appr-Limited" 183 28. "Adv-Appr-Ltd" 184 29. "Clear" 185 30. "Cab-Speed" 186 31. "Dark" */ 187 188 @Override 189 public void setAspect(@Nonnull String aspect) { 190 if (appearanceToOutput.containsKey(aspect) && appearanceToOutput.get(aspect) != -1) { 191 sendMessage(appearanceToOutput.get(aspect)); 192 } else { 193 log.warn("Trying to set aspect ({}) that has not been configured on mast {}", aspect, getDisplayName()); 194 } 195 //super.setAspect(aspect); 196 String oldAspect = this.aspect; 197 this.aspect = null; //means UNKNOWN - there is no INCONSISTENT 198 //this.speed = (String) getSignalSystem().getProperty(aspect, "speed"); // NOI18N 199 firePropertyChange("Aspect", oldAspect, aspect); // NOI18N 200 } 201 202 @Override 203 public void setLit(boolean newLit) { 204 if (!allowUnLit() || newLit == getLit()) { 205 return; 206 } 207 if (newLit) { 208 String a = getAspect(); 209 if (a != null) { 210 setAspect(a); 211 } 212 super.setLit(newLit); 213 } else { 214 sendMessage(unLitId); 215 } 216 //super.setLit(newLit); 217 } 218 219 /** 220 * Request the state of the accessory from the layout. 221 * The listener gets the answer. 222 */ 223 public void queryAccessory() { 224 messageHandler.sendQuery(); 225 } 226 227 /** 228 * Send a accessory message to BiDiB 229 * 230 * @param aspect to send 231 */ 232 protected void sendMessage(int aspect) { 233 // TODO: check FEATURE_GEN_SWITCH_ACK 234 log.debug("Signal Mast set aspect: {}, addr: {}", aspect, addr); 235 //newKnownAspect(INCONSISTENT); 236 if (addr.isValid()) { 237 int state; 238 if (addr.isPortAddr()) { 239 state = (aspect == 0) ? 0 : 1; 240 switch (messageHandler.getLcType()) { 241 case LIGHTPORT: 242 state = (aspect == 0) ? 2 : 3; //use Dim function - we can't configure this so far... 243 break; 244 case SERVOPORT: 245 case ANALOGPORT: 246 case BACKLIGHTPORT: 247 state = (aspect == 0) ? 0 : 255; 248 break; 249 case MOTORPORT: 250 state = (aspect == 0) ? 0 : 126; 251 break; 252 case INPUTPORT: 253 log.warn("output to INPUT port is not possible, addr: {}", addr); 254 return; 255 default: 256 break; 257 } 258 } 259 else { 260 state = aspect; 261 } 262 messageHandler.sendOutput(state); 263 commandedAspect = aspect; 264 } 265 } 266 267 int unLitId = 31; 268 269 public void setUnlitId(int i) { 270 unLitId = i; 271 } 272 273 public int getUnlitId() { 274 return unLitId; 275 } 276 277 public String getAccessoryAddress() { 278 if (addr.isValid()) { 279 return addr.getAddrString(); 280 } 281 return ""; 282 } 283 284 public BiDiBTrafficController getTrafficController() { 285 return tc; 286 } 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override 292 public BiDiBAddress getAddr() { 293 return addr; 294 } 295 296 /** 297 * {@inheritDoc} 298 */ 299 @Override 300 public void nodeNew() { 301 //create a new BiDiBAddress 302 addr = new BiDiBAddress(getSystemName(), typeLetter, tc.getSystemConnectionMemo()); 303 if (addr.isValid()) { 304 log.info("new signal mast address created: {} -> {}", getSystemName(), addr); 305 log.debug("current aspect is {}, commanded aspect {}", getAspect(), commandedAspect); 306 if (addr.isPortAddr()) { 307 messageHandler.sendQueryConfig(); 308 messageHandler.waitQueryConfig(); 309 } 310 sendMessage(commandedAspect); 311 } 312 } 313 314 /** 315 * {@inheritDoc} 316 */ 317 @Override 318 public void nodeLost() { 319 super.setLit(false); 320 } 321 322 /** 323 * {@inheritDoc} 324 * 325 * Remove the Message Listener for this signal mast 326 */ 327 @Override 328 public void dispose() { 329 if (messageHandler != null) { 330 tc.removeMessageListener(messageHandler); 331 messageHandler = null; 332 } 333 super.dispose(); 334 } 335 336 public static String isAccessoryAddressUsed(BiDiBAddress address) { 337 for (SignalMast mast : InstanceManager.getDefault(jmri.SignalMastManager.class).getNamedBeanSet()) { 338 if (mast instanceof BiDiBSignalMast) { 339 if (((BiDiBSignalMast) mast).getAddr().isAddressEqual(address)) { 340 return ((BiDiBSignalMast) mast).getDisplayName(); 341 } 342 } 343 } 344 return null; 345 } 346 347 private void newKnownAspect(int aspectNum) { 348 //log.debug("Available aspects: {}, addr: {}", appearanceToOutput.size(), addr); 349 for(Map.Entry<String, Integer> entry : appearanceToOutput.entrySet()) { 350 //log.debug(" check for aspect {}", entry.getValue()); 351 if (entry.getValue() == aspectNum) { 352 //log.debug("set aspect {} ({})", entry.getKey(), aspectNum); 353 if (aspectNum == unLitId) { 354 //log.debug(" setLit"); 355 super.setLit(false); 356 } 357 else { 358 log.debug(" setAspect to {}", entry.getKey()); 359 super.setAspect(entry.getKey()); 360 super.setLit(true); 361 } 362 } 363 } 364 } 365 366 private void createSignalMastListener() { 367 //messageHandler = new BiDiBOutputMessageHandler("SIGNALMAST", addr, tc){ 368 messageHandler = new BiDiBOutputMessageHandler(this, "SIGNALMAST", tc){ 369 @Override 370 public void newOutputState(int state) { 371 int newAspect; 372 if (addr.isPortAddr()) { 373 // since we do not know what other states than 0 (or 2 for LIGHTPORTS) mean (which aspect), 374 // we simply use what was commanded. Does not really work for spontaneous messages. 375 // So, preferrably use accessory addresses which supports the various aspects. 376 if (messageHandler.getLcType() == LcOutputType.LIGHTPORT) { 377 newAspect = (state == 2 || state == 0) ? 0 : commandedAspect; 378 } 379 else { 380 newAspect = (state == 0) ? 0 : commandedAspect; 381 } 382 } 383 else { 384 // for others (accessories), the state is the new aspect number 385 newAspect = state; 386 } 387 log.debug("SIGNALMAST new aspect: {}", newAspect); 388 newKnownAspect(newAspect); 389 } 390 @Override 391 public void outputWait(int time) { 392 log.debug("SIGNALMAST wait: {}", time); 393 //newKnownAspect(commandedAspect); 394 } 395 @Override 396 public void errorState(int err) { 397 log.warn("SIGNALMAST error: {} addr: {}", err, addr); 398 //newKnownAspect(getUnlitId()); 399 //super.setAspect(aspect); 400 String oldAspect = aspect; 401 aspect = null; //means UNKNOWN - there is no INCONSISTENT 402 //this.speed = (String) getSignalSystem().getProperty(aspect, "speed"); // NOI18N 403 firePropertyChange("Aspect", oldAspect, aspect); // NOI18N 404 } 405 }; 406 tc.addMessageListener(messageHandler); 407 } 408 409 private final static Logger log = LoggerFactory.getLogger(BiDiBSignalMast.class); 410 411} 412 413