001package jmri.jmrix.mqtt; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.Consist; 005import jmri.ConsistListener; 006import jmri.DccLocoAddress; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009import javax.annotation.Nonnull; 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 */ 066 @Override 067 public boolean isAddressAllowed(DccLocoAddress address) { 068 if (address.getNumber() != 0) { 069 return (true); 070 } else { 071 return (false); 072 } 073 } 074 075 /** 076 * Is there a size limit for this consist? 077 * 078 * @return -1 for Controller Consists (no limit), 079 * 0 for any other consist type 080 */ 081 @Override 082 public int sizeLimit() { 083 if (consistType == CS_CONSIST) { 084 return -1; 085 } else { 086 return 0; 087 } 088 } 089 090 /** 091 * Does the consist contain the specified address? 092 */ 093 @Override 094 public boolean contains(DccLocoAddress address) { 095 if (consistType == CS_CONSIST) { 096 return consistList.contains(address); 097 } else { 098 log.error("Consist Type Not Supported"); 099 notifyConsistListeners(address, ConsistListener.NotImplemented); 100 } 101 return false; 102 } 103 104 /** 105 * Get the relative direction setting for a specific 106 * locomotive in the consist. 107 */ 108 @Override 109 public boolean getLocoDirection(DccLocoAddress address) { 110 if (consistType == CS_CONSIST) { 111 Boolean Direction = consistDir.get(address); 112 return (Direction.booleanValue()); 113 } else { 114 log.error("Consist Type Not Supported"); 115 notifyConsistListeners(address, ConsistListener.NotImplemented); 116 } 117 return false; 118 } 119 120 /** 121 * Add an Address to the internal consist list object. 122 */ 123 private synchronized void addToConsistList(DccLocoAddress LocoAddress, boolean directionNormal) { 124 125 log.debug("Add to consist list address {} direction {}", LocoAddress, directionNormal); 126 Boolean Direction = Boolean.valueOf(directionNormal); 127 if (!(consistList.contains(LocoAddress))) { 128 consistList.add(LocoAddress); 129 } 130 consistDir.put(LocoAddress, Direction); 131 notifyConsistListeners(LocoAddress, ConsistListener.OPERATION_SUCCESS); 132 } 133 134 /** 135 * Remove an address from the internal consist list object. 136 */ 137 private synchronized void removeFromConsistList(DccLocoAddress LocoAddress) { 138 log.debug("Remove from consist list address {}", LocoAddress); 139 consistDir.remove(LocoAddress); 140 consistList.remove(LocoAddress); 141 notifyConsistListeners(LocoAddress, ConsistListener.OPERATION_SUCCESS); 142 } 143 144 /** 145 * Add a Locomotive to a Consist. 146 * 147 * @param LocoAddress is the Locomotive address to add to the locomotive 148 * @param directionNormal is True if the locomotive is traveling 149 * the same direction as the consist, or false otherwise. 150 */ 151 @Override 152 public synchronized void add(DccLocoAddress LocoAddress, boolean directionNormal) { 153 log.debug("Add to consist address {} direction {}", LocoAddress, directionNormal); 154 if (consistType == CS_CONSIST) { 155 addToConsistList(LocoAddress, directionNormal); 156 if (active) publish(); 157 } else { 158 log.error("Consist Type Not Supported"); 159 notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented); 160 } 161 } 162 163 /** 164 * Restore a Locomotive to Consist, but don't write to 165 * the command station. This is used for restoring the consist 166 * from a file or adding a consist read from the command station. 167 * 168 * @param LocoAddress is the Locomotive address to add to the locomotive 169 * @param directionNormal is True if the locomotive is traveling 170 * the same direction as the consist, or false otherwise. 171 */ 172 @Override 173 public synchronized void restore(DccLocoAddress LocoAddress, boolean directionNormal) { 174 log.debug("Restore to consist address {} direction {}", LocoAddress, directionNormal); 175 176 if (consistType == CS_CONSIST) { 177 addToConsistList(LocoAddress, directionNormal); 178 } else { 179 log.error("Consist Type Not Supported"); 180 notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented); 181 } 182 } 183 184 /** 185 * Remove a Locomotive from this Consist. 186 * 187 * @param LocoAddress is the Locomotive address to add to the locomotive 188 */ 189 @Override 190 public synchronized void remove(DccLocoAddress LocoAddress) { 191 log.debug("Remove from consist address {}", LocoAddress); 192 193 if (consistType == CS_CONSIST) { 194 removeFromConsistList(LocoAddress); 195 if (active) publish(); 196 } else { 197 log.error("Consist Type Not Supported"); 198 notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented); 199 } 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\\}", String.valueOf(consistAddress.getNumber())), ""); 223 }); 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) ? "":"-").concat(String.valueOf(address.getNumber())).concat(" "); 234 } 235 236 return consistMakeup.trim(); 237 238 } 239 240 /** 241 * Publish the consist details to the controller 242 */ 243 private void publish(){ 244 // Send MQTT message 245 jmri.util.ThreadingUtil.runOnLayout(() -> { 246 mqttAdapter.publish(this.sendTopicPrefix.replaceFirst("\\{0\\}", String.valueOf(consistAddress.getNumber())), getConsistMakeup()); 247 }); 248 249 } 250 251 private final static Logger log = LoggerFactory.getLogger(MqttConsist.class); 252 253}