001package jmri.jmrix.tmcc; 002 003import javax.annotation.Nonnull; 004 005import jmri.Consist; 006import jmri.ConsistListener; 007import jmri.LocoAddress; 008import jmri.DccLocoAddress; 009 010/** 011 * This is the Consist definition for a consist on a TMCC system. 012 * 013 * Based on MqttConsist by 014 * @author Dean Cording Copyright (C) 2023 015 * with edits/additions by 016 * @author Timothy Jump (C) 2025 017 */ 018 019public class TmccConsist extends jmri.implementation.DccConsist { 020 021 @Nonnull 022 public String sendTopicPrefix = "cab/{0}/consist"; 023 private boolean active = false; 024 025 // Initialize a consist for the specific address. 026 // The Default consist type is controller consist 027 public TmccConsist(int address, TmccSystemConnectionMemo memo, String sendTopicPrefix) { 028 super(address); 029 tc = memo.getTrafficController(); 030 this.sendTopicPrefix = sendTopicPrefix; 031 consistType = Consist.CS_CONSIST; 032 log.debug("Consist {} created.", this.getConsistAddress()); 033 } 034 035 // Initialize a consist for the specific address. 036 // The Default consist type is controller consist 037 public TmccConsist(DccLocoAddress address, TmccSystemConnectionMemo memo, String sendTopicPrefix) { 038 super(address); 039 tc = memo.getTrafficController(); 040 this.sendTopicPrefix = sendTopicPrefix; 041 consistType = Consist.CS_CONSIST; 042 log.debug("Consist {} created.", this.getConsistAddress()); 043 } 044 045 // Clean Up local storage. 046 @Override 047 public void dispose() { 048 super.dispose(); 049 log.debug("Consist {} disposed.", this.getConsistAddress()); 050 } 051 052 // Set the Consist Type. 053 @Override 054 public void setConsistType(int consist_type) { 055 log.debug("Set Consist Type {}", consist_type); 056 if (consist_type == Consist.CS_CONSIST) { 057 consistType = consist_type; 058 } else { 059 log.error("Consist Type Not Supported"); 060 notifyConsistListeners(new DccLocoAddress(0, false), ConsistListener.NotImplemented); 061 } 062 } 063 064 protected SerialTrafficController tc = null; 065 066 067 /** 068 * Is this TMCC ENG (loco ID#) address allowed? 069 * TMCC systems only use ENG (loco ID#) addresses 1-98. 070 * {@inheritDoc} 071 */ 072 @Override 073 public boolean isAddressAllowed(DccLocoAddress address) { 074 return address.getNumber() > 0 && address.getNumber() < 99; 075 } 076 077 /** 078 * Is there a size limit for this consist? 079 * 080 * @return -1 for Controller Consists (no limit), 081 * 0 for any other consist type 082 */ 083 @Override 084 public int sizeLimit() { 085 if (consistType == CS_CONSIST) { 086 return -1; 087 } else { 088 return 0; 089 } 090 } 091 092 /** 093 * Does the consist contain the specified address? 094 * {@inheritDoc} 095 */ 096 @Override 097 public boolean contains(DccLocoAddress address) { 098 if (consistType == CS_CONSIST) { 099 return consistList.contains(address); 100 } else { 101 log.error("Consist Type Not Supported"); 102 notifyConsistListeners(address, ConsistListener.NotImplemented); 103 } 104 return false; 105 } 106 107 /** 108 * Get the relative direction setting for a specific 109 * locomotive in the consist. 110 * {@inheritDoc} 111 */ 112 @Override 113 public boolean getLocoDirection(DccLocoAddress address) { 114 if (consistType == CS_CONSIST) { 115 return consistDir.getOrDefault(address, false); 116 } else { 117 log.error("Consist Type Not Supported"); 118 notifyConsistListeners(address, ConsistListener.NotImplemented); 119 } 120 return false; 121 } 122 123 /** 124 * Add an Address to the internal consist list object. 125 */ 126 private synchronized void addToConsistList(DccLocoAddress locoAddress, boolean directionNormal) { 127 128 log.debug("Add to consist list address {} direction {}", locoAddress, directionNormal); 129 if (!(consistList.contains(locoAddress))) { 130 consistList.add(locoAddress); 131 } 132 consistDir.put(locoAddress, directionNormal); 133 notifyConsistListeners(locoAddress, ConsistListener.OPERATION_SUCCESS); 134 } 135 136 /** 137 * Remove an address from the internal consist list object. 138 */ 139 private synchronized void removeFromConsistList(DccLocoAddress locoAddress) { 140 log.debug("Remove from consist list address {}", locoAddress); 141 consistDir.remove(locoAddress); 142 consistList.remove(locoAddress); 143 notifyConsistListeners(locoAddress, ConsistListener.OPERATION_SUCCESS); 144 } 145 146 /** 147 * Add a Locomotive to a Consist 148 * 149 * @param locoAddress - is the Locomotive address to add to the consist. 150 * @param directionNormal - is True if the locomotive is traveling the same direction as the consist, or false otherwise. 151 */ 152 @Override 153 public synchronized void add(DccLocoAddress locoAddress, boolean directionNormal) { 154 155 // TMCC1 Consist Build 156 if (locoAddress.getProtocol() == LocoAddress.Protocol.TMCC1) { 157 SerialMessage c = new SerialMessage(); 158 SerialMessage m = new SerialMessage(); 159 SerialMessage n = new SerialMessage(); 160 c.setOpCode(0xFE); 161 m.setOpCode(0xFE); 162 n.setOpCode(0xFE); 163 164 // Set range on TMCC1 Consist ID (1-15) 165 if (consistAddress.getNumber() < 16) { 166 167 // TMCC has 6 commands for adding a loco to a consist: head, rear, and mid, plus direction 168 if (!contains(locoAddress)) { 169 // First loco to consist 170 if (consistList.isEmpty()) { 171 // add head loco 172 if (!directionNormal) { 173 // TMCC1 - Assign as Head Unit/Reverse Direction 174 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 175 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 176 n.putAsWord(0x0025 + locoAddress.getNumber() * 128); 177 } else { 178 // TMCC1 - Assign as Head Unit/Forward Direction 179 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 180 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 181 n.putAsWord(0x0021 + locoAddress.getNumber() * 128); 182 } 183 // send to command station (send once (each) is set, but number of sends may need to be adjusted depending on efficiency) 184 tc.sendSerialMessage(c, null); 185 tc.sendSerialMessage(m, null); 186 tc.sendSerialMessage(n, null); 187 188 // Second loco to consist 189 } else if (consistList.size() == 1) { 190 // add rear loco 191 if (!directionNormal) { 192 // TMCC1 - Assign as Rear Unit/Reverse Direction 193 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 194 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 195 n.putAsWord(0x0027 + locoAddress.getNumber() * 128); 196 } else { 197 // TMCC1 - Assign as Rear Unit/Forward Direction 198 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 199 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 200 n.putAsWord(0x0023 + locoAddress.getNumber() * 128); 201 } 202 // send to command station (send once (each) is set, but number of sends may need to be adjusted depending on efficiency) 203 tc.sendSerialMessage(c, null); 204 tc.sendSerialMessage(m, null); 205 tc.sendSerialMessage(n, null); 206 207 // Additional loco(s) to consist 208 } else { 209 // add mid loco 210 if (!directionNormal) { 211 // TMCC1 - Assign as Mid Unit/Reverse Direction 212 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 213 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 214 n.putAsWord(0x0026 + locoAddress.getNumber() * 128); 215 } else { 216 // TMCC1 - Assign as Mid Unit/Forward Direction 217 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 218 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 219 n.putAsWord(0x0022 + locoAddress.getNumber() * 128); 220 } 221 // send to command station (send once (each) is set, but number of sends may need to be adjusted depending on efficiency) 222 tc.sendSerialMessage(c, null); 223 tc.sendSerialMessage(m, null); 224 tc.sendSerialMessage(n, null); 225 226 } 227 228 // Add Loco to Consist List 229 addToConsistList(locoAddress, directionNormal); 230 231 } else { 232 log.error("Loco {} is already part of this consist {}", locoAddress, getConsistAddress()); 233 } 234 235 } else { 236 m.putAsWord(0x002F); 237 tc.sendSerialMessage(m, null); 238 log.error("TMCC1 Consist ID out of Range (must be between 1-15)"); 239 } 240 } 241 242 // TMCC2 Consist Build 243 if (locoAddress.getProtocol() == LocoAddress.Protocol.TMCC2) { 244 SerialMessage c = new SerialMessage(); 245 SerialMessage m = new SerialMessage(); 246 SerialMessage n = new SerialMessage(); 247 c.setOpCode(0xF8); 248 m.setOpCode(0xF8); 249 n.setOpCode(0xF8); 250 251 // Set range on TMCC2 Consist ID (1-15) 252 if (consistAddress.getNumber() < 16) { 253 254 // TMCC has 6 commands for adding a loco to a consist: head, rear, and mid, plus direction 255 if (!contains(locoAddress)) { 256 257 // First loco to consist 258 if (consistList.isEmpty()) { 259 // add head loco 260 if (!directionNormal) { 261 // TMCC1 - Assign as Head Unit/Reverse Direction 262 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 263 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 264 n.putAsWord(0x0123 + locoAddress.getNumber() * 512); 265 } else { 266 // TMCC1 - Assign as Head Unit/Forward Direction 267 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 268 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 269 n.putAsWord(0x0122 + locoAddress.getNumber() * 512); 270 } 271 // send to command station (send once (each) is set, but number of sends may need to be adjusted depending on efficiency) 272 tc.sendSerialMessage(c, null); 273 tc.sendSerialMessage(m, null); 274 tc.sendSerialMessage(n, null); 275 276 // Second loco to consist 277 } else if (consistList.size() == 1) { 278 // add rear loco 279 if (!directionNormal) { 280 // TMCC1 - Assign as Rear Unit/Reverse Direction 281 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 282 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 283 n.putAsWord(0x0127 + locoAddress.getNumber() * 512); 284 } else { 285 // TMCC1 - Assign as Rear Unit/Forward Direction 286 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 287 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 288 n.putAsWord(0x0126 + locoAddress.getNumber() * 512); 289 } 290 // send to command station (send once (each) is set, but number of sends may need to be adjusted depending on efficiency) 291 tc.sendSerialMessage(c, null); 292 tc.sendSerialMessage(m, null); 293 tc.sendSerialMessage(n, null); 294 295 // Additional loco(s) to consist 296 } else { 297 // add mid loco 298 if (!directionNormal) { 299 // TMCC1 - Assign as Mid Unit/Reverse Direction 300 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 301 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 302 n.putAsWord(0x0125 + locoAddress.getNumber() * 512); 303 } else { 304 // TMCC1 - Assign as Mid Unit/Forward Direction 305 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 306 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 307 n.putAsWord(0x0124 + locoAddress.getNumber() * 512); 308 } 309 // send to command station (send once (each) is set, but number of sends may need to be adjusted depending on efficiency) 310 tc.sendSerialMessage(c, null); 311 tc.sendSerialMessage(m, null); 312 tc.sendSerialMessage(n, null); 313 314 } 315 316 // Add Loco to Consist List 317 addToConsistList(locoAddress, directionNormal); 318 319 } else { 320 log.error("Loco {} is already part of this consist {}", locoAddress, getConsistAddress()); 321 } 322 323 } else { 324 m.putAsWord(0x012F); 325 tc.sendSerialMessage(m, null); 326 log.error("TMCC2 Consist ID out of Range (must be between 1-15)"); 327 } 328 } 329 } 330 331 /** 332 * Restore a Locomotive to Consist, but don't write to 333 * the command station. This is used for restoring the consist 334 * from a file or adding a consist read from the command station. 335 * 336 * @param locoAddress is the Locomotive address to add to the locomotive 337 * @param directionNormal is True if the locomotive is traveling 338 * the same direction as the consist, or false otherwise. 339 */ 340 @Override 341 public synchronized void restore(DccLocoAddress locoAddress, boolean directionNormal) { 342 log.debug("Restore to consist address {} direction {}", locoAddress, directionNormal); 343 344 if (consistType == CS_CONSIST) { 345 addToConsistList(locoAddress, directionNormal); 346 } else { 347 log.error("Consist Type Not Supported"); 348 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 349 } 350 } 351 352 /** 353 * Remove a Locomotive from this Consist 354 * Clear Consist ID from Locomotive 355 * @param locoAddress is the Locomotive address to add to the locomotive 356 */ 357 @Override 358 public synchronized void remove(DccLocoAddress locoAddress) { 359 log.debug("Remove from consist address {}", locoAddress); 360 361 // TMCC1 - Clear Consist ID from Locomotive 362 if (locoAddress.getProtocol() == LocoAddress.Protocol.TMCC1) { 363 SerialMessage c = new SerialMessage(); 364 c.setOpCode(0xFE); 365 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); 366 tc.sendSerialMessage(c, null); 367 } 368 369 // TMCC2 - Clear Consist ID from Locomotive 370 if (locoAddress.getProtocol() == LocoAddress.Protocol.TMCC2) { 371 SerialMessage c = new SerialMessage(); 372 c.setOpCode(0xF8); 373 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); 374 tc.sendSerialMessage(c, null); 375 } 376 377 // Remove Locomotive from this Consist 378 if (consistType == CS_CONSIST) { 379 removeFromConsistList(locoAddress); 380 if (active) { 381 publish(); 382 } 383 } else { 384 log.error("Consist Type Not Supported"); 385 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 386 } 387 } 388 389 /** 390 * Activates the consist for use with a throttle 391 */ 392 public void activate(){ 393 log.info("Activating consist {}", consistID); 394 active = true; 395 publish(); 396 } 397 398 /** 399 * Deactivates and removes the consist from a throttle 400 */ 401 public void deactivate() { 402 403 log.info("Deactivating consist {}", consistID); 404 active = false; 405 } 406 407 /** 408 * Publish the consist details to the controller 409 */ 410 private void publish(){ 411 } 412 413 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TmccConsist.class); 414 415}