001package jmri.jmrix.mqtt; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import javax.annotation.Nonnull; 006 007import jmri.Consist; 008import jmri.ConsistListener; 009import jmri.DccLocoAddress; 010 011/** 012 * This is the Consist definition for a consist on an MQTT system. 013 * 014 * @author Dean Cording Copyright (C) 2023 015 */ 016public class MqttConsist extends jmri.implementation.DccConsist { 017 018 private final MqttAdapter mqttAdapter; 019 @Nonnull 020 public String sendTopicPrefix = "cab/{0}/consist"; 021 private boolean active = false; 022 023 // Initialize a consist for the specific address. 024 // The Default consist type is controller consist 025 public MqttConsist(int address, MqttSystemConnectionMemo memo, String sendTopicPrefix) { 026 super(address); 027 mqttAdapter = memo.getMqttAdapter(); 028 this.sendTopicPrefix = sendTopicPrefix; 029 consistType = Consist.CS_CONSIST; 030 log.debug("Consist {} created.", this.getConsistAddress()); 031 } 032 033 // Initialize a consist for the specific address. 034 // The Default consist type is controller consist 035 public MqttConsist(DccLocoAddress address, MqttSystemConnectionMemo memo, String sendTopicPrefix) { 036 super(address); 037 mqttAdapter = memo.getMqttAdapter(); 038 this.sendTopicPrefix = sendTopicPrefix; 039 consistType = Consist.CS_CONSIST; 040 log.debug("Consist {} created.", this.getConsistAddress()); 041 } 042 043 // Clean Up local storage. 044 @Override 045 public void dispose() { 046 super.dispose(); 047 log.debug("Consist {} disposed.", this.getConsistAddress()); 048 } 049 050 // Set the Consist Type. 051 @Override 052 public void setConsistType(int consist_type) { 053 log.debug("Set Consist Type {}", consist_type); 054 if (consist_type == Consist.CS_CONSIST) { 055 consistType = consist_type; 056 } else { 057 log.error("Consist Type Not Supported"); 058 notifyConsistListeners(new DccLocoAddress(0, false), ConsistListener.NotImplemented); 059 } 060 } 061 062 /** 063 * Is this address allowed? 064 * On MQTT systems, all addresses but 0 can be used in a consist. 065 * {@inheritDoc} 066 */ 067 @Override 068 public boolean isAddressAllowed(DccLocoAddress address) { 069 return address.getNumber() != 0; 070 } 071 072 /** 073 * Is there a size limit for this consist? 074 * 075 * @return -1 for Controller Consists (no limit), 076 * 0 for any other consist type 077 */ 078 @Override 079 public int sizeLimit() { 080 if (consistType == CS_CONSIST) { 081 return -1; 082 } else { 083 return 0; 084 } 085 } 086 087 /** 088 * Does the consist contain the specified address? 089 * {@inheritDoc} 090 */ 091 @Override 092 public boolean contains(DccLocoAddress address) { 093 if (consistType == CS_CONSIST) { 094 return consistList.contains(address); 095 } else { 096 log.error("Consist Type Not Supported"); 097 notifyConsistListeners(address, ConsistListener.NotImplemented); 098 } 099 return false; 100 } 101 102 /** 103 * Get the relative direction setting for a specific 104 * locomotive in the consist. 105 * {@inheritDoc} 106 */ 107 @Override 108 public boolean getLocoDirection(DccLocoAddress address) { 109 if (consistType == CS_CONSIST) { 110 return consistDir.getOrDefault(address, false); 111 } else { 112 log.error("Consist Type Not Supported"); 113 notifyConsistListeners(address, ConsistListener.NotImplemented); 114 } 115 return false; 116 } 117 118 /** 119 * Add an Address to the internal consist list object. 120 */ 121 private synchronized void addToConsistList(DccLocoAddress locoAddress, boolean directionNormal) { 122 123 log.debug("Add to consist list address {} direction {}", locoAddress, directionNormal); 124 if (!(consistList.contains(locoAddress))) { 125 consistList.add(locoAddress); 126 } 127 consistDir.put(locoAddress, directionNormal); 128 notifyConsistListeners(locoAddress, ConsistListener.OPERATION_SUCCESS); 129 } 130 131 /** 132 * Remove an address from the internal consist list object. 133 */ 134 private synchronized void removeFromConsistList(DccLocoAddress locoAddress) { 135 log.debug("Remove from consist list address {}", locoAddress); 136 consistDir.remove(locoAddress); 137 consistList.remove(locoAddress); 138 notifyConsistListeners(locoAddress, ConsistListener.OPERATION_SUCCESS); 139 } 140 141 /** 142 * Add a Locomotive to a Consist. 143 * 144 * @param locoAddress is the Locomotive address to add to the locomotive 145 * @param directionNormal is True if the locomotive is traveling 146 * the same direction as the consist, or false otherwise. 147 */ 148 @Override 149 public synchronized void add(DccLocoAddress locoAddress, boolean directionNormal) { 150 log.debug("Add to consist address {} direction {}", locoAddress, directionNormal); 151 if (consistType == CS_CONSIST) { 152 addToConsistList(locoAddress, directionNormal); 153 if (active) { 154 publish(); 155 } 156 } else { 157 log.error("Consist Type Not Supported"); 158 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 159 } 160 } 161 162 /** 163 * Restore a Locomotive to Consist, but don't write to 164 * the command station. This is used for restoring the consist 165 * from a file or adding a consist read from the command station. 166 * 167 * @param locoAddress is the Locomotive address to add to the locomotive 168 * @param directionNormal is True if the locomotive is traveling 169 * the same direction as the consist, or false otherwise. 170 */ 171 @Override 172 public synchronized void restore(DccLocoAddress locoAddress, boolean directionNormal) { 173 log.debug("Restore to consist address {} direction {}", locoAddress, directionNormal); 174 175 if (consistType == CS_CONSIST) { 176 addToConsistList(locoAddress, directionNormal); 177 } else { 178 log.error("Consist Type Not Supported"); 179 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 180 } 181 } 182 183 /** 184 * Remove a Locomotive from this Consist. 185 * 186 * @param locoAddress is the Locomotive address to add to the locomotive 187 */ 188 @Override 189 public synchronized void remove(DccLocoAddress locoAddress) { 190 log.debug("Remove from consist address {}", locoAddress); 191 192 if (consistType == CS_CONSIST) { 193 removeFromConsistList(locoAddress); 194 if (active) { 195 publish(); 196 } 197 } else { 198 log.error("Consist Type Not Supported"); 199 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 200 } 201 } 202 203 /** 204 * Activates the consist for use with a throttle 205 */ 206 public void activate(){ 207 208 log.info("Activating consist {}", consistID); 209 active = true; 210 publish(); 211 } 212 213 /** 214 * Deactivates and removes the consist from a throttle 215 */ 216 public void deactivate() { 217 218 log.info("Deactivating consist {}", consistID); 219 active = false; 220 // Clear MQTT message 221 jmri.util.ThreadingUtil.runOnLayoutEventually(() -> 222 mqttAdapter.publish(this.sendTopicPrefix.replaceFirst("\\{0\\}", 223 String.valueOf(consistAddress.getNumber())), "")); 224 225 } 226 227 @SuppressFBWarnings(value = "WMI_WRONG_MAP_ITERATOR", justification = "false positive") 228 private String getConsistMakeup() { 229 230 String consistMakeup = ""; 231 232 for (DccLocoAddress address : consistDir.keySet()) { 233 consistMakeup = consistMakeup.concat(consistDir.get(address) ? "":"-") 234 .concat(String.valueOf(address.getNumber())).concat(" "); 235 } 236 237 return consistMakeup.trim(); 238 239 } 240 241 /** 242 * Publish the consist details to the controller 243 */ 244 private void publish(){ 245 // Send MQTT message 246 jmri.util.ThreadingUtil.runOnLayout(() -> 247 mqttAdapter.publish(this.sendTopicPrefix.replaceFirst("\\{0\\}", 248 String.valueOf(consistAddress.getNumber())), getConsistMakeup())); 249 250 } 251 252 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MqttConsist.class); 253 254}