001package jmri.jmrit.withrottle; 002 003import java.io.IOException; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.List; 007import jmri.AddressedProgrammer; 008import jmri.AddressedProgrammerManager; 009import jmri.Consist; 010import jmri.ConsistManager; 011import jmri.DccLocoAddress; 012import jmri.InstanceManager; 013import jmri.LocoAddress; 014import jmri.ProgListener; 015import jmri.ProgrammerException; 016import jmri.jmrit.consisttool.ConsistFile; 017import org.jdom2.JDOMException; 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021/** 022 * @author Brett Hoffman Copyright (C) 2010 023 */ 024public class ConsistController extends AbstractController implements ProgListener { 025 026 private ConsistManager manager; 027 private ConsistFile file; 028 private boolean isConsistAllowed; 029 030 public ConsistController() { 031 // writeFile needs to be separate method 032 if (InstanceManager.getDefault(WiThrottlePreferences.class).isUseWiFiConsist()) { 033 try { 034 manager = new WiFiConsistManager(); 035 InstanceManager.store(manager, ConsistManager.class); 036 log.debug("Using WiFiConsisting"); 037 } catch (NullPointerException npe) { 038 log.error("Attempting to use WiFiConsisting, but no Command Station available"); 039 manager = null; 040 } 041 } else { 042 manager = InstanceManager.getNullableDefault(ConsistManager.class); 043 log.debug("Using JMRIConsisting"); 044 } 045 046 if (manager == null) { 047 log.info("No consist manager instance."); 048 isValid = false; 049 } else { 050 if (InstanceManager.getDefault(WiThrottlePreferences.class).isUseWiFiConsist()) { 051 file = new WiFiConsistFile(manager); 052 } else { 053 file = new ConsistFile(); 054 try { 055 file.readFile(); 056 } catch (IOException | JDOMException e) { 057 log.warn("error reading consist file", (Object) e); 058 } 059 } 060 isValid = true; 061 } 062 } 063 064 /** 065 * Allows device to decide how to handle consisting. Just selection or 066 * selection and Make and Break. .size() indicates how many 067 * consists are being sent so the device can wait before displaying them 068 */ 069 public void sendConsistListType() { 070 if (listeners == null) { 071 return; 072 } 073 String message; 074 075 int numConsists = manager.getConsistList().size(); //number of JMRI consists found 076 if (log.isDebugEnabled()) { 077 log.debug("{} consists found.", numConsists); 078 } 079 080 if (isConsistAllowed) { // Allow Make & Break consists 081 message = ("RCC" + numConsists); // Roster Consist Controller 082 } else { // Just allow selection list 083 message = ("RCL" + numConsists); // Roster Consist List 084 } 085 086 for (ControllerInterface listener : listeners) { 087 listener.sendPacketToDevice(message); 088 } 089 } 090 091 public void sendAllConsistData() { 092 // Loop thru JMRI consists and send consist detail for each 093 for (LocoAddress conAddr : manager.getConsistList()) { 094 sendDataForConsist(manager.getConsist(conAddr)); 095 } 096 } 097 098 public void sendDataForConsist(Consist con) { 099 if (listeners == null) { 100 return; 101 } 102 StringBuilder list = new StringBuilder("RCD"); // Roster Consist Data 103 list.append("}|{"); 104 list.append(con.getConsistAddress()); 105 list.append("}|{"); 106 if (con.getConsistID().length() > 0) { 107 list.append(con.getConsistID()); 108 } 109 110 for (DccLocoAddress loco : con.getConsistList()) { 111 list.append("]\\["); 112 list.append(loco.toString()); 113 list.append("}|{"); 114 list.append(con.getLocoDirection(loco)); 115 } 116 117 String message = list.toString(); 118 119 for (ControllerInterface listener : listeners) { 120 listener.sendPacketToDevice(message); 121 } 122 } 123 124 public void setIsConsistAllowed(boolean b) { 125 isConsistAllowed = b; 126 } 127 128 @Override 129 boolean verifyCreation() { 130 return isValid; 131 } 132 133 /** 134 * 135 * @param message string containing new consist information 136 */ 137 @Override 138 void handleMessage(String message, DeviceServer deviceServer) { 139 try { 140 if (message.charAt(0) == 'P') { // Change consist 'P'ositions 141 reorderConsist(message); 142 143 } 144 if (message.charAt(0) == 'R') { // 'R'emove consist 145 removeConsist(message); 146 147 } 148 if (message.charAt(0) == '+') { // Add loco to consist and/or set relative direction 149 addLoco(message); 150 151 } 152 if (message.charAt(0) == '-') { // remove loco from consist 153 removeLoco(message); 154 155 } 156 if (message.charAt(0) == 'F') { // program CV 21 & 22 'F'unctions 157 setConsistCVs(message); 158 } 159 } catch (NullPointerException exb) { 160 log.warn("Message \"{}\" does not match a consist command.", message); 161 } 162 } 163 164 /** 165 * Change the sequence of locos in this consist. Reorders the consistList, 166 * instead of setting the 'position' value. Lead and Trail are set on first 167 * and last locos by DccConsist. 168 * 169 * @param message RCP<;>consistAddress<:>leadLoco<;>nextLoco<;>... 170 * ...<;>nextLoco<;>trailLoco 171 */ 172 private void reorderConsist(String message) { 173 Consist consist; 174 List<String> headerAndLocos = Arrays.asList(message.split("<:>")); 175 176 if (headerAndLocos.size() < 2) { 177 log.warn("reorderConsist missing data in message: {}", message); 178 return; 179 } 180 181 try { 182 List<String> headerData = Arrays.asList(headerAndLocos.get(0).split("<;>")); 183 // 184 consist = manager.getConsist(stringToDcc(headerData.get(1))); 185 186 List<String> locoData = Arrays.asList(headerAndLocos.get(1).split("<;>")); 187 /* 188 * Reorder the consistList: 189 * For each loco sent, remove it from the consistList 190 * and reinsert it at the front of the list. 191 */ 192 for (String loco : locoData) { 193 ArrayList<DccLocoAddress> conList = consist.getConsistList(); 194 int index = conList.indexOf(stringToDcc(loco)); 195 if (index != -1) { 196 conList.add(conList.remove(index)); 197 } 198 199 } 200 201 } catch (NullPointerException e) { 202 log.warn("reorderConsist error for message: {}", message); 203 return; 204 } 205 206 writeFile(); 207 208 } 209 210 /** 211 * remove a consist by it's Dcc address. Wiil remove all locos in the 212 * process. 213 * 214 * @param message RCR<;>consistAddress 215 */ 216 private void removeConsist(String message) { 217 List<String> header = Arrays.asList(message.split("<;>")); 218 try { 219 Consist consist = manager.getConsist(stringToDcc(header.get(1))); 220 while (!consist.getConsistList().isEmpty()) { 221 DccLocoAddress loco = consist.getConsistList().get(0); 222 log.debug("Remove loco: {}, from consist: {}", loco, consist.getConsistAddress()); 223 consist.remove(loco); 224 } 225 } catch (NullPointerException noCon) { 226 log.warn("Consist: {} not found. Cannot delete.", header.get(1)); 227 return; 228 } 229 230 try { 231 manager.delConsist(stringToDcc(header.get(1))); 232 } catch (NullPointerException noCon) { 233 log.warn("Consist: {} not found. Cannot delete.", header.get(1)); 234 return; 235 } 236 237 writeFile(); 238 239 } 240 241 /** 242 * Add a loco or change it's direction. Creates a new consist if one does 243 * not already exist 244 * 245 * @param message RC+<;>consistAddress<;>ID<:>locoAddress<;>directionNormal 246 */ 247 private void addLoco(String message) { 248 Consist consist; 249 250 List<String> headerAndLoco = Arrays.asList(message.split("<:>")); 251 252 try { 253 // Break out header and either get existing consist or create new 254 List<String> headerData = Arrays.asList(headerAndLoco.get(0).split("<;>")); 255 256 consist = manager.getConsist(stringToDcc(headerData.get(1))); 257 consist.setConsistID(headerData.get(2)); 258 259 List<String> locoData = Arrays.asList(headerAndLoco.get(1).split("<;>")); 260 261 if (consist.isAddressAllowed(stringToDcc(locoData.get(0)))) { 262 consist.add(stringToDcc(locoData.get(0)), Boolean.valueOf(locoData.get(1))); 263 if (log.isDebugEnabled()) { 264 log.debug("add loco: {}, to consist: {}", locoData.get(0), headerData.get(1)); 265 } 266 } 267 268 } catch (NullPointerException e) { 269 log.warn("addLoco error for message: {}", message); 270 return; 271 } 272 273 writeFile(); 274 } 275 276 /** 277 * remove a loco if it exist in this consist. 278 * 279 * @param message RC-<;>consistAddress<:>locoAddress 280 */ 281 private void removeLoco(String message) { 282 Consist consist; 283 284 List<String> headerAndLoco = Arrays.asList(message.split("<:>")); 285 286 log.debug("remove loco string: {}", message); 287 288 try { 289 List<String> headerData = Arrays.asList(headerAndLoco.get(0).split("<;>")); 290 291 consist = manager.getConsist(stringToDcc(headerData.get(1))); 292 293 List<String> locoData = Arrays.asList(headerAndLoco.get(1).split("<;>")); 294 295 DccLocoAddress loco = stringToDcc(locoData.get(0)); 296 if (checkForBroadcastAddress(loco)) { 297 return; 298 } 299 300 if (consist.contains(loco)) { 301 consist.remove(loco); 302 if (log.isDebugEnabled()) { 303 log.debug("Remove loco: {}, from consist: {}", loco, headerData.get(1)); 304 } 305 } 306 } catch (NullPointerException e) { 307 log.warn("removeLoco error for message: {}", message); 308 return; 309 } 310 311 writeFile(); 312 } 313 314 private void writeFile() { 315 try { 316 if (InstanceManager.getDefault(WiThrottlePreferences.class).isUseWiFiConsist()) { 317 file.writeFile(manager.getConsistList(), WiFiConsistFile.getFileLocation() + "wifiConsist.xml"); 318 } else { 319 file.writeFile(manager.getConsistList()); 320 } 321 } catch (IOException e) { 322 log.warn("Consist file could not be written!"); 323 } 324 } 325 326 /** 327 * set CV 21&22 for consist functions send each CV individually 328 * 329 * @param message RCF<;> locoAddress <:> CV# <;> value 330 */ 331 private void setConsistCVs(String message) { 332 333 DccLocoAddress loco; 334 335 List<String> headerAndCVs = Arrays.asList(message.split("<:>")); 336 337 log.debug("setConsistCVs string: {}", message); 338 339 try { 340 List<String> headerData = Arrays.asList(headerAndCVs.get(0).split("<;>")); 341 342 loco = stringToDcc(headerData.get(1)); 343 if (checkForBroadcastAddress(loco)) { 344 return; 345 } 346 347 } catch (NullPointerException e) { 348 log.warn("setConsistCVs error for message: {}", message); 349 return; 350 } 351 AddressedProgrammer pom = InstanceManager.getDefault(AddressedProgrammerManager.class).getAddressedProgrammer(loco); 352 if (pom != null) { 353 // loco done, now get CVs 354 for (int i = 1; i < headerAndCVs.size(); i++) { 355 List<String> CVData = Arrays.asList(headerAndCVs.get(i).split("<;>")); 356 357 try { 358 try { 359 pom.writeCV(CVData.get(0), Integer.parseInt(CVData.get(1)), this); 360 } catch (ProgrammerException e) { 361 log.debug("Unable to communicate with programmer manager."); 362 } 363 } catch (NumberFormatException nfe) { 364 log.warn("Error in setting CVs", nfe); 365 } 366 } 367 InstanceManager.getDefault(AddressedProgrammerManager.class).releaseAddressedProgrammer(pom); 368 } 369 } 370 371 @Override 372 public void programmingOpReply(int value, int status) { 373 374 } 375 376 // this method may belong somewhere else. 377 static public DccLocoAddress stringToDcc(String s) { 378 int num = Integer.parseInt(s.substring(1)); 379 boolean isLong = (s.charAt(0) == 'L'); 380 return (new DccLocoAddress(num, isLong)); 381 } 382 383 /** 384 * Check to see if an address will try to broadcast (0) a programming 385 * message. 386 * 387 * @param addr The address to check 388 * @return true if address is no good, otherwise false 389 */ 390 public boolean checkForBroadcastAddress(DccLocoAddress addr) { 391 if (addr.getNumber() < 1) { 392 log.warn("Trying to use broadcast address!"); 393 return true; 394 } 395 return false; 396 } 397 398 @Override 399 void register() { 400 throw new UnsupportedOperationException("Not used."); 401 } 402 403 @Override 404 void deregister() { 405 throw new UnsupportedOperationException("Not used."); 406 } 407 408 private final static Logger log = LoggerFactory.getLogger(ConsistController.class); 409 410}