001package jmri.jmrix.bidib; 002 003import java.util.LinkedHashMap; 004import java.util.Map; 005 006import org.bidib.jbidibc.messages.BidibLibrary; //new 007import org.bidib.jbidibc.messages.AccessoryState; 008import org.bidib.jbidibc.messages.AccessoryStateOptions; 009import org.bidib.jbidibc.messages.AddressData; 010import org.bidib.jbidibc.messages.BidibPort; 011import org.bidib.jbidibc.core.DefaultMessageListener; 012import org.bidib.jbidibc.messages.LcConfig; 013import org.bidib.jbidibc.messages.LcConfigX; 014import org.bidib.jbidibc.messages.Node; 015import org.bidib.jbidibc.messages.ProtocolVersion; 016import org.bidib.jbidibc.messages.enums.AccessoryAcknowledge; 017import org.bidib.jbidibc.messages.enums.ActivateCoilEnum; 018import org.bidib.jbidibc.messages.enums.AddressTypeEnum; 019import org.bidib.jbidibc.messages.enums.LcOutputType; 020import org.bidib.jbidibc.messages.enums.PortModelEnum; 021import org.bidib.jbidibc.messages.enums.TimeBaseUnitEnum; 022import org.bidib.jbidibc.messages.enums.TimingControlEnum; 023import org.bidib.jbidibc.messages.message.AccessoryGetMessage; 024import org.bidib.jbidibc.messages.message.AccessorySetMessage; 025import org.bidib.jbidibc.messages.message.BidibCommandMessage; 026import org.bidib.jbidibc.messages.message.BidibRequestFactory; 027import org.bidib.jbidibc.messages.message.CommandStationAccessoryMessage; 028import org.bidib.jbidibc.messages.message.FeedbackGetRangeMessage; 029import org.bidib.jbidibc.messages.port.ReconfigPortConfigValue; 030import org.bidib.jbidibc.messages.utils.ByteUtils; 031import org.bidib.jbidibc.messages.utils.NodeUtils; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * This class handles output to: 037 * - BiDiB Accessories 038 * - DCC Accessories via command station 039 * - BiDiB LC Ports 040 * 041 * Output value is sent to the type according to the address type. 042 * Incoming messages a are catched by the BiDiB Message listener, then some common 043 * processing takes place and the new value is sent back to the listener of this class instance. 044 * 045 * @author Eckart Meyer Copyright (C) 2020-2023 046 */ 047public class BiDiBOutputMessageHandler extends DefaultMessageListener { 048 049 private final BiDiBNamedBeanInterface nb; 050 protected BiDiBTrafficController tc = null; 051 protected String type;// for log output only, e.g. "TURNOUT" 052 protected LcConfigX portConfigx; 053 protected LcOutputType lcType; //cached type from ConfigX or fixed in type based address 054 protected BidibRequestFactory requestFactory = null; 055 final Object portConfigLock = new Object(); 056 057 // the configLock is used to synchronize config messages 058 //private final Object configLock = new Object(); 059 060 // internal cs accessory request aspect table since MSG_CS_ACCESSORY_ACK does not return the accessory state 061 private final Map<BiDiBAddress, Integer> csAccessoryAspectMap = new LinkedHashMap<>(); 062 063 BiDiBOutputMessageHandler(BiDiBNamedBeanInterface nb, String type, BiDiBTrafficController tc) { 064 this.type = type; 065 this.nb = nb; 066 this.tc = tc; 067 BiDiBAddress addr = nb.getAddr(); 068 if (addr.isValid() && tc != null) { 069 lcType = addr.getPortType(); 070 requestFactory = tc.getBidib().getNode(addr.getNode()).getRequestFactory(); 071 } 072 } 073 074 /** 075 * Get the port configuration if output is a BiDiB port 076 * 077 * @return port ConfigX or null if not a BiDiB port 078 */ 079 public LcConfigX getConfigX() { 080 return portConfigx; 081 } 082 083 /** 084 * Get the port output type if output is a BiDiB port 085 * 086 * @return port output type or null if not a BiDiB port 087 */ 088 public LcOutputType getLcType() { 089 return lcType; 090 } 091 092 /** 093 * Send output request to traffic controller 094 * Send new port value or aspect value 095 * 096 * @param portstat BiDiB output value (see protocol description for valid values) 097 */ 098 public void sendOutput(int portstat) { 099 BiDiBAddress addr = nb.getAddr(); 100 log.trace("sendOutput: portstat: {}", portstat); 101 if (addr.isValid()) { 102 log.info("send output message to BiDiB: addr: {}, state: {}", addr, portstat); 103 Node node = addr.getNode(); 104 if (addr.isPortAddr()) { 105 if (portExists() && requestFactory != null) { 106 waitQueryConfig(); 107 BidibCommandMessage m = requestFactory.createLcOutputMessage(tc.getPortModel(node), lcType, addr.getAddr(), portstat); 108 tc.sendBiDiBMessage(m, node); 109 } 110 } 111 else if (addr.isAccessoryAddr()) { 112 if (accessoryExists()) { 113 tc.sendBiDiBMessage(new AccessorySetMessage(addr.getAddr(), portstat), node); 114 } 115 } 116 else if (addr.isTrackAddr()) { //send a CS Accessory Message 117 // can't check address 118 tc.sendBiDiBMessage(new CommandStationAccessoryMessage(addr.getAddr(), AddressTypeEnum.ACCESSORY, 119 TimingControlEnum.COIL_ON_OFF, ActivateCoilEnum.COIL_ON, portstat & 0x1F, TimeBaseUnitEnum.UNIT_100MS, 0), node); 120 // remember the requested aspect since the ACK messgae does not contain any state and we also cannot query the state 121 csAccessoryAspectMap.put(addr, portstat & 0x1F); 122 } 123 else { 124 log.error("sending output message not supported for address type"); 125 } 126 } 127 else { 128 log.warn("node is not available, UID: {}", ByteUtils.formatHexUniqueId(addr.getNodeUID())); 129 } 130 } 131 132 public void sendQueryConfig() { 133 BiDiBAddress addr = nb.getAddr(); 134 log.trace("queryOutput for addr: {}", addr); 135 if (addr.isValid()) { 136 log.debug("send query config message to BiDiB: addr: {}", addr); 137 Node node = addr.getNode(); 138 if (addr.isPortAddr() && portExists() && requestFactory != null) { 139 log.info("send port query config message to BiDiB: addr: {}", addr); 140 // only ports have configurations 141 BidibCommandMessage m; 142 if (node.getProtocolVersion().isHigherThan(ProtocolVersion.VERSION_0_6)) { //ConfigX is available since V0.6 143 m = requestFactory.createLcConfigXGet(tc.getPortModel(node), lcType, addr.getAddr()); 144 } 145 else { 146 m = requestFactory.createLcConfigGet(tc.getPortModel(node), lcType, addr.getAddr()); 147 } 148 portConfigx = null;// invalidate 149 tc.sendBiDiBMessage(m, node); 150 } 151 } 152 else { 153 log.warn("node is not available, UID: {}", ByteUtils.formatHexUniqueId(addr.getNodeUID())); 154 } 155 } 156 157 public void waitQueryConfig() { 158 BiDiBAddress addr = nb.getAddr(); 159 if (addr.isValid()) { 160 if (addr.isPortAddr()) { 161 synchronized (portConfigLock) { 162 while (portConfigx == null) {// "while" instead of "if" - see Doku of Java Object() 163 try { 164 log.debug("wait for config message from BiDiB: addr: {}", addr); 165 // wait will relinquish synchronization claim and wait for notifyAll() 166 portConfigLock.wait(500L); 167 // on return the synchronization claim is re-established 168 } 169 catch (InterruptedException ie) { 170 log.warn("Wait for port config was interrupted.", ie); 171 } 172 } 173 } 174 } 175 } 176 } 177 178 /** 179 * Send output query request to traffic controller 180 */ 181 public void sendQuery() { 182 BiDiBAddress addr = nb.getAddr(); 183 log.trace("queryOutput for addr: {}", addr); 184 if (addr.isValid()) { 185 log.info("send query output message to BiDiB: addr: {}", addr); 186 Node node = addr.getNode(); 187 if (addr.isPortAddr()) { 188 if (portExists() && requestFactory != null) { 189 waitQueryConfig(); 190 BidibCommandMessage m; 191 if (node.getProtocolVersion().isHigherThan(ProtocolVersion.VERSION_0_6) || lcType.getType() <= 7) { 192 m = requestFactory.createLcPortQuery(tc.getPortModel(node), lcType, addr.getAddr()); 193 } 194 else { 195 // this is only used for input ports on nodes with older firmware (<= 0.6) 196 m = requestFactory.createLcKey(addr.getAddr()); 197 } 198 tc.sendBiDiBMessage(m, node); 199 } 200 } 201 else if (addr.isAccessoryAddr()) { 202 if (accessoryExists()) { 203 tc.sendBiDiBMessage(new AccessoryGetMessage(addr.getAddr()), node); 204 } 205 } 206 else if (addr.isTrackAddr()) { 207 // can't check and can't request 208 // no warning/error level here please - would break LightManager test unit (some test expect specific warn/error) 209 log.info("query of a CS accessory is not possible."); 210 } 211 else if (addr.isFeedbackAddr()) { 212 if (feedbackExists()) { 213 int a = (addr.getAddr() / 8) * 8; 214 int b = ((addr.getAddr() + 8) / 8) * 8; //exclusive end address 215 log.debug(" requesting feedback from {} to {}", a, b); 216 tc.sendBiDiBMessage(new FeedbackGetRangeMessage(a, b), node); 217 } 218 } 219 else { 220 log.error("sending query output message not supported for address type, addr: {}", addr); 221 } 222 } 223 else { 224 log.warn("node is not available, UID: {}", ByteUtils.formatHexUniqueId(addr.getNodeUID())); 225 } 226 } 227 228 /** 229 * Get the number of ports for a node by type. 230 * For type based addressing this is the real number of ports of this type, addresses starting from 0. 231 * For flat addressing this is only a hint how many ports of this type exists in the flat address range. 232 * In the latter case this feature may not been implemented and will return 0. 233 * 234 * @param node to check 235 * @param type to look for 236 * @return number of ports for this type. 237 */ 238 private int getPortTypeCount(Node node, LcOutputType type) { 239 int id; 240 switch (type) { 241 case SWITCHPORT: 242 case SWITCHPAIRPORT: 243 id = BidibLibrary.FEATURE_CTRL_SWITCH_COUNT; 244 break; 245 case LIGHTPORT: 246 id = BidibLibrary.FEATURE_CTRL_LIGHT_COUNT; 247 break; 248 case SERVOPORT: 249 id = BidibLibrary.FEATURE_CTRL_SERVO_COUNT; 250 break; 251 case SOUNDPORT: 252 id = BidibLibrary.FEATURE_CTRL_SOUND_COUNT; 253 break; 254 case MOTORPORT: 255 id = BidibLibrary.FEATURE_CTRL_MOTOR_COUNT; 256 break; 257 case ANALOGPORT: 258 id = BidibLibrary.FEATURE_CTRL_ANALOGOUT_COUNT; 259 break; 260 case BACKLIGHTPORT: 261 id = BidibLibrary.FEATURE_CTRL_BACKLIGHT_COUNT; 262 break; 263 case INPUTPORT: 264 id = BidibLibrary.FEATURE_CTRL_INPUT_COUNT; 265 break; 266 default: 267 return 0; 268 } 269 return tc.getNodeFeature(node, id); 270 } 271 272 private boolean portExists() { 273 BiDiBAddress addr = nb.getAddr(); 274 if (addr.isPortAddr()) { 275 Node node = addr.getNode(); 276 if (addr.isPortTypeBasedModel()) { 277 if (addr.getAddr() >= 0 && addr.getAddr() < getPortTypeCount(addr.getNode(), lcType)) { 278 return true; 279 } 280 } 281 else { 282 if (addr.getAddr() >= 0 && addr.getAddr() < node.getPortFlatModel()) { 283 return true; 284 } 285 } 286 } 287 return false; 288 } 289 290 private boolean accessoryExists() { 291 BiDiBAddress addr = nb.getAddr(); 292 if (addr.isAccessoryAddr()) { 293 if (addr.getAddr() >= 0 && addr.getAddr() < tc.getNodeFeature(addr.getNode(), BidibLibrary.FEATURE_ACCESSORY_COUNT)) { 294 return true; 295 } 296 } 297 return false; 298 } 299 300 private boolean feedbackExists() { 301 BiDiBAddress addr = nb.getAddr(); 302 if (addr.isFeedbackAddr()) { 303 if (addr.getAddr() >= 0 && addr.getAddr() < tc.getNodeFeature(addr.getNode(), BidibLibrary.FEATURE_BM_SIZE)) { 304 return true; 305 } 306 } 307 return false; 308 } 309 310// Overridable methods for notifications 311 312 /** 313 * Notify output state 314 * @param state desired state from NamedBean list 315 */ 316 public void newOutputState(int state) { 317 } 318 319 /** 320 * Notify error state 321 * @param err - BiDiB error number 322 */ 323 public void errorState(int err) { 324 } 325 326 /** 327 * Notify output will change later 328 * @param time in msec 329 */ 330 public void outputWait(int time) { 331 } 332 333 /** 334 * Notify LC port ConfigX 335 * 336 * @param lcConfigX input 337 * @param lcType input 338 */ 339 public void newLcConfigX(LcConfigX lcConfigX, LcOutputType lcType) { 340 } 341 342 343// Accessory related received messages 344 345// - BiDiB Accessories 346 347 @Override 348 public void accessoryState(byte[] address, int messageNum, final AccessoryState accessoryState, final AccessoryStateOptions accessoryStateOptions) { 349 BiDiBAddress addr = nb.getAddr(); 350 //log.trace("node UID: {}, node addr: {}, msg node addr: {}, state: {}", addr.getNodeUID(), addr.getNodeAddr(), address, accessoryState); 351 if (addr.isAccessoryAddr() && NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.getAddr() == accessoryState.getAccessoryNumber()) { 352 log.info("{} accessory state was signalled, state: {}, opts: {}, node: {}", 353 type, accessoryState, accessoryStateOptions, addr); 354 if (accessoryState.hasError()) { 355 log.warn("Accessory state error: {}", accessoryState.getErrorInformation()); 356 errorState(accessoryState.getErrorCode()); 357 } 358 else { 359 if (accessoryState.getWait() == 0) { 360 newOutputState(accessoryState.getActiveAspect()); 361 } 362 else { 363 outputWait(accessoryState.getWait()); 364 } 365 } 366 } 367 } 368 369// - DCC Accessories 370 371 @Override 372 public void csAccessoryAcknowledge(byte[] address, int messageNum, int decoderAddress, AccessoryAcknowledge acknowledge) { 373 BiDiBAddress addr = nb.getAddr(); 374 //log.trace("node UID: {}, node addr: {}, msg node addr: {}", addr.getNodeUID(), addr.getNodeAddr(), address); 375 if (addr.isTrackAddr() && NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.getAddr() == decoderAddress) { 376 log.info("{} CS accessory ackn was signalled, acknowledge: {}, decoderAddress: {}, node: {}", type, acknowledge, decoderAddress, addr); 377 if (acknowledge == AccessoryAcknowledge.NOT_ACKNOWLEDGED) { 378 log.warn("NOT acknowledged!"); 379 errorState(csAccessoryAspectMap.get(addr)); 380 } 381 else { 382 if (acknowledge == AccessoryAcknowledge.DELAYED) { 383 outputWait(0); 384 } 385 else { 386 // since the message does not contain an aspect, we just return the requested aspect from the map 387 newOutputState(csAccessoryAspectMap.get(addr)); 388 } 389 } 390 } 391 } 392 @Override 393 public void csAccessoryManual(byte[] address, int messageNum, AddressData decoderAddress, ActivateCoilEnum activate, int aspect) { 394 BiDiBAddress addr = nb.getAddr(); 395 //log.trace("node UID: {}, node addr: {}, msg node addr: {}, decoder address", addr.getNodeUID(), addr.getNodeAddr(), address, decoderAddress); 396 if (addr.isAccessoryAddr() && NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.getAddr() == decoderAddress.getAddress()) { 397 log.info("{} accessory manual was signalled, activate coil: {}, aspect: {}, decoder address: {}, node: {}", 398 type, activate.getType(), aspect, decoderAddress.getAddress(), addr); 399 newOutputState(aspect); 400 } 401 } 402 403// LightControl related received messages 404 405 private void notifyLcConfigX(LcConfigX lcConfigX) { 406 BiDiBAddress addr = nb.getAddr(); 407 log.trace("portConfigx: {}", lcConfigX); 408 if (addr.getNode().isPortFlatModelAvailable()) { 409 ReconfigPortConfigValue p = (ReconfigPortConfigValue)lcConfigX.getPortConfig().get(BidibLibrary.BIDIB_PCFG_RECONFIG); 410 log.warn("reconfig: {}, type: {}", p, p.getCurrentOutputType()); 411 if (lcType != p.getCurrentOutputType()) { 412 log.warn("** reconfig: {}, type changed: {} -> {}", p, lcType, p.getCurrentOutputType()); 413 } 414 lcType = p.getCurrentOutputType(); 415 } 416 else { 417 lcType = lcConfigX.getOutputType(PortModelEnum.type); 418 } 419 newLcConfigX(lcConfigX, lcType); 420 } 421 422 @Override 423 public void lcStat(byte[] address, int messageNum, BidibPort bidibPort, int portStatus) { 424 BiDiBAddress addr = nb.getAddr(); 425 //log.trace("lcStat: node UID: {}, node addr: {}, address: {}, port: {}, stat: {}", addr.getNodeUID(), addr.getNodeAddr(), address, bidibPort, portStatus); 426 if (addr.isPortAddr() && NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.isAddressEqual(bidibPort)) { 427 log.info("{} LC status was signalled, state: {}, type: {}, node: {}", type, portStatus, lcType, addr); 428 newOutputState(portStatus); 429 } 430 } 431 @Override 432 public void lcWait(byte[] address, int messageNum, BidibPort bidibPort, int time) { 433 BiDiBAddress addr = nb.getAddr(); 434 //log.trace("lcStat: node UID: {}, node addr: {}, address: {}, port: {}, time: {}", addr.getNodeUID(), addr.getNodeAddr(), address, bidibPort, time); 435 if (addr.isPortAddr() && NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.isAddressEqual(bidibPort)) { 436 log.info("{} LC Wait was signalled, wait: {}, type: {}, node: {}", type, time, lcType, addr); 437 outputWait(time); 438 } 439 } 440 @Override 441 public void lcNa(byte[] address, int messageNum, BidibPort bidibPort, Integer errorCode) { 442 BiDiBAddress addr = nb.getAddr(); 443 //log.trace("lcNa: node UID: {}, node addr: {}, address: {}, port: {}, errorCode: {}", addr.getNodeUID(), addr.getNodeAddr(), address, bidibPort, errorCode); 444 if (addr.isPortAddr() && NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.isAddressEqual(bidibPort)) { 445 log.info("{} LC NA was signalled, error: {}, type: {}, node: {}", type, errorCode, lcType, addr); 446 errorState(errorCode); 447 } 448 } 449 @Override 450 public void lcConfig(byte[] address, int messageNum, LcConfig lcConfig) { 451 BiDiBAddress addr = nb.getAddr(); 452 //log.trace("lcConfig: node addr: {}, config: {}", address, lcConfig); 453 if (addr.isPortAddr() && NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.isAddressEqual(lcConfig)) { 454 log.info("{} LC Config was signalled, config: {}, node: {}", type, lcConfig, addr); 455 synchronized (portConfigLock) { 456 portConfigx = tc.convertConfig2ConfigX(addr.getNode(), lcConfig); 457 notifyLcConfigX(portConfigx); 458 portConfigLock.notifyAll(); 459 } 460 } 461 } 462 @Override 463 public void lcConfigX(byte[] address, int messageNum, LcConfigX lcConfigX) { 464 BiDiBAddress addr = nb.getAddr(); 465 //log.trace("lcConfigX: node addr: {}, configx: {}", address, lcConfigX); 466 if (addr.isPortAddr() && NodeUtils.isAddressEqual(addr.getNodeAddr(), address) && addr.isAddressEqual(lcConfigX)) { 467 log.info("{} LC ConfigX was signalled, configx: {}, node: {}", type, lcConfigX, addr); 468 synchronized (portConfigLock) { 469 portConfigx = new LcConfigX(addr.makeBidibPort(), new LinkedHashMap<>() ); 470 portConfigx.getPortConfig().putAll(lcConfigX.getPortConfig()); 471 notifyLcConfigX(portConfigx); 472 portConfigLock.notifyAll(); 473 } 474 } 475 } 476 477 private final static Logger log = LoggerFactory.getLogger(BiDiBOutputMessageHandler.class); 478}