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 // TMCC has 6 commands for adding a loco to a consist: head, rear, and mid, plus direction 165 if (!contains(locoAddress)) { 166 // First loco to consist 167 if (consistList.isEmpty()) { 168 // add head loco 169 if (!directionNormal) { 170 // TMCC1 - Assign as Head Unit/Reverse Direction 171 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 172 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 173 n.putAsWord(0x0025 + locoAddress.getNumber() * 128); 174 } else { 175 // TMCC1 - Assign as Head Unit/Forward Direction 176 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 177 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 178 n.putAsWord(0x0021 + locoAddress.getNumber() * 128); 179 } 180 // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency) 181 tc.sendSerialMessage(c, null); 182 tc.sendSerialMessage(m, null); 183 tc.sendSerialMessage(n, null); 184 185 // Second loco to consist 186 } else if (consistList.size() == 1) { 187 // add rear loco 188 if (!directionNormal) { 189 // TMCC1 - Assign as Rear Unit/Reverse Direction 190 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 191 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 192 n.putAsWord(0x0027 + locoAddress.getNumber() * 128); 193 } else { 194 // TMCC1 - Assign as Rear Unit/Forward Direction 195 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 196 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 197 n.putAsWord(0x0023 + locoAddress.getNumber() * 128); 198 } 199 // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency) 200 tc.sendSerialMessage(c, null); 201 tc.sendSerialMessage(m, null); 202 tc.sendSerialMessage(n, null); 203 204 // Additional loco(s) to consist 205 } else { 206 // add mid loco 207 if (!directionNormal) { 208 // TMCC1 - Assign as Mid Unit/Reverse Direction 209 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 210 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 211 n.putAsWord(0x0026 + locoAddress.getNumber() * 128); 212 } else { 213 // TMCC1 - Assign as Mid Unit/Forward Direction 214 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); // Clear residual consist ID from locomotive 215 m.putAsWord(0x0030 + (locoAddress.getNumber() * 128) + consistAddress.getNumber()); 216 n.putAsWord(0x0022 + locoAddress.getNumber() * 128); 217 } 218 // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency) 219 tc.sendSerialMessage(c, null); 220 tc.sendSerialMessage(m, null); 221 tc.sendSerialMessage(n, null); 222 223 } 224 225 // Add Loco to Consist List 226 addToConsistList(locoAddress, directionNormal); 227 228 } else { 229 log.error("Loco {} is already part of this consist {}", locoAddress, getConsistAddress()); 230 } 231 } 232 233 // TMCC2 Consist Build 234 if (locoAddress.getProtocol() == LocoAddress.Protocol.TMCC2) { 235 SerialMessage c = new SerialMessage(); 236 SerialMessage m = new SerialMessage(); 237 SerialMessage n = new SerialMessage(); 238 c.setOpCode(0xF8); 239 m.setOpCode(0xF8); 240 n.setOpCode(0xF8); 241 242 // TMCC has 6 commands for adding a loco to a consist: head, rear, and mid, plus direction 243 if (!contains(locoAddress)) { 244 // First loco to consist 245 if (consistList.isEmpty()) { 246 // add head loco 247 if (!directionNormal) { 248 // TMCC1 - Assign as Head Unit/Reverse Direction 249 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 250 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 251 n.putAsWord(0x0123 + locoAddress.getNumber() * 512); 252 } else { 253 // TMCC1 - Assign as Head Unit/Forward Direction 254 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 255 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 256 n.putAsWord(0x0122 + locoAddress.getNumber() * 512); 257 } 258 // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency) 259 tc.sendSerialMessage(c, null); 260 tc.sendSerialMessage(m, null); 261 tc.sendSerialMessage(n, null); 262 263 // Second loco to consist 264 } else if (consistList.size() == 1) { 265 // add rear loco 266 if (!directionNormal) { 267 // TMCC1 - Assign as Rear Unit/Reverse Direction 268 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 269 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 270 n.putAsWord(0x0127 + locoAddress.getNumber() * 512); 271 } else { 272 // TMCC1 - Assign as Rear Unit/Forward Direction 273 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 274 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 275 n.putAsWord(0x0126 + locoAddress.getNumber() * 512); 276 } 277 // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency) 278 tc.sendSerialMessage(c, null); 279 tc.sendSerialMessage(m, null); 280 tc.sendSerialMessage(n, null); 281 282 // Additional loco(s) to consist 283 } else { 284 // add mid loco 285 if (!directionNormal) { 286 // TMCC1 - Assign as Mid Unit/Reverse Direction 287 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 288 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 289 n.putAsWord(0x0125 + locoAddress.getNumber() * 512); 290 } else { 291 // TMCC1 - Assign as Mid Unit/Forward Direction 292 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); // Clear residual consist ID from locomotive 293 m.putAsWord(0x0130 + (locoAddress.getNumber() * 512) + consistAddress.getNumber()); 294 n.putAsWord(0x0124 + locoAddress.getNumber() * 512); 295 } 296 // send to command station (send twice is set, but number of sends may need to be adjusted depending on efficiency) 297 tc.sendSerialMessage(c, null); 298 tc.sendSerialMessage(m, null); 299 tc.sendSerialMessage(n, null); 300 301 } 302 303 // Add Loco to Consist List 304 addToConsistList(locoAddress, directionNormal); 305 306 } else { 307 log.error("Loco {} is already part of this consist {}", locoAddress, getConsistAddress()); 308 } 309 } 310 } 311 312 /** 313 * Restore a Locomotive to Consist, but don't write to 314 * the command station. This is used for restoring the consist 315 * from a file or adding a consist read from the command station. 316 * 317 * @param locoAddress is the Locomotive address to add to the locomotive 318 * @param directionNormal is True if the locomotive is traveling 319 * the same direction as the consist, or false otherwise. 320 */ 321 @Override 322 public synchronized void restore(DccLocoAddress locoAddress, boolean directionNormal) { 323 log.debug("Restore to consist address {} direction {}", locoAddress, directionNormal); 324 325 if (consistType == CS_CONSIST) { 326 addToConsistList(locoAddress, directionNormal); 327 } else { 328 log.error("Consist Type Not Supported"); 329 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 330 } 331 } 332 333 /** 334 * Remove a Locomotive from this Consist 335 * Clear Consist ID from Locomotive 336 * @param locoAddress is the Locomotive address to add to the locomotive 337 */ 338 @Override 339 public synchronized void remove(DccLocoAddress locoAddress) { 340 log.debug("Remove from consist address {}", locoAddress); 341 342 // TMCC1 - Clear Consist ID from Locomotive 343 if (locoAddress.getProtocol() == LocoAddress.Protocol.TMCC1) { 344 SerialMessage c = new SerialMessage(); 345 c.setOpCode(0xFE); 346 c.putAsWord(0x0030 + (locoAddress.getNumber() * 128)); 347 tc.sendSerialMessage(c, null); 348 } 349 350 // TMCC2 - Clear Consist ID from Locomotive 351 if (locoAddress.getProtocol() == LocoAddress.Protocol.TMCC2) { 352 SerialMessage c = new SerialMessage(); 353 c.setOpCode(0xF8); 354 c.putAsWord(0x0130 + (locoAddress.getNumber() * 512)); 355 tc.sendSerialMessage(c, null); 356 } 357 358 // Remove Locomotive from this Consist 359 if (consistType == CS_CONSIST) { 360 removeFromConsistList(locoAddress); 361 if (active) { 362 publish(); 363 } 364 } else { 365 log.error("Consist Type Not Supported"); 366 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 367 } 368 } 369 370 /** 371 * Activates the consist for use with a throttle 372 */ 373 public void activate(){ 374 log.info("Activating consist {}", consistID); 375 active = true; 376 publish(); 377 } 378 379 /** 380 * Deactivates and removes the consist from a throttle 381 */ 382 public void deactivate() { 383 384 log.info("Deactivating consist {}", consistID); 385 active = false; 386 } 387 388 /** 389 * Publish the consist details to the controller 390 */ 391 private void publish(){ 392 } 393 394 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TmccConsist.class); 395 396}