001package jmri.jmris.srcp; 002 003import java.beans.PropertyChangeListener; 004import java.io.DataInputStream; 005import java.io.IOException; 006import java.io.OutputStream; 007import java.util.ArrayList; 008 009import jmri.*; 010import jmri.jmris.AbstractThrottleServer; 011import jmri.SystemConnectionMemo; 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015/** 016 * Interface between the JMRI Throttles and an SRCP network connection 017 * 018 * @author Paul Bender Copyright (C) 2016 019 */ 020public class JmriSRCPThrottleServer extends AbstractThrottleServer { 021 022 private static final Logger log = LoggerFactory.getLogger(JmriSRCPThrottleServer.class); 023 024 private final OutputStream output; 025 026 private final ArrayList<Integer> busList; 027 private final ArrayList<LocoAddress> addressList; 028 029 public JmriSRCPThrottleServer(DataInputStream inStream, OutputStream outStream) { 030 super(); 031 busList = new ArrayList<>(); 032 addressList = new ArrayList<>(); 033 output = outStream; 034 } 035 036 037 /* 038 * Protocol Specific Functions 039 */ 040 @Override 041 public void sendStatus(LocoAddress l) throws IOException { 042 output.write( Bundle.getMessage("Error499").getBytes()); 043 } 044 045 /* 046 * send the status of the specified throttle address on the specified bus 047 * @param bus bus number. 048 * @param address locomoitve address. 049 */ 050 public void sendStatus(int bus, int address) throws IOException { 051 log.debug("send Status called with bus {} and address {}", bus, address); 052 053 /* translate the bus into a system connection memo */ 054 java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class); 055 SystemConnectionMemo memo; 056 try { 057 memo = list.get(bus - 1); 058 } catch (java.lang.IndexOutOfBoundsException obe) { 059 output.write(Bundle.getMessage("Error412").getBytes()); 060 return; 061 } 062 063 /* request the throttle for this particular locomotive address */ 064 if (memo.provides(jmri.ThrottleManager.class)) { 065 ThrottleManager t = memo.get(jmri.ThrottleManager.class); 066 // we will use getThrottleInfo to request information about the 067 // address, so we need to convert the address to a DccLocoAddress 068 // object first. 069 DccLocoAddress addr = new DccLocoAddress(address, !(t.canBeShortAddress(address))); 070 Boolean isForward = (Boolean) t.getThrottleInfo(addr, Throttle.ISFORWARD); 071 Float speedSetting = (Float) t.getThrottleInfo(addr, Throttle.SPEEDSETTING); 072 SpeedStepMode speedStepMode = (SpeedStepMode) t.getThrottleInfo(addr, Throttle.SPEEDSTEPMODE); 073 Boolean f0 = (Boolean) t.getThrottleInfo(addr, "F0"); 074 Boolean f1 = (Boolean) t.getThrottleInfo(addr, "F1"); 075 Boolean f2 = (Boolean) t.getThrottleInfo(addr, "F2"); 076 Boolean f3 = (Boolean) t.getThrottleInfo(addr, "F3"); 077 Boolean f4 = (Boolean) t.getThrottleInfo(addr, "F4"); 078 Boolean f5 = (Boolean) t.getThrottleInfo(addr, "F5"); 079 Boolean f6 = (Boolean) t.getThrottleInfo(addr, "F6"); 080 Boolean f7 = (Boolean) t.getThrottleInfo(addr, "F7"); 081 Boolean f8 = (Boolean) t.getThrottleInfo(addr, "F8"); 082 Boolean f9 = (Boolean) t.getThrottleInfo(addr, "F9"); 083 Boolean f10 = (Boolean) t.getThrottleInfo(addr, "F10"); 084 Boolean f11 = (Boolean) t.getThrottleInfo(addr, "F11"); 085 Boolean f12 = (Boolean) t.getThrottleInfo(addr, "F12"); 086 Boolean f13 = (Boolean) t.getThrottleInfo(addr, "F13"); 087 Boolean f14 = (Boolean) t.getThrottleInfo(addr, "F14"); 088 Boolean f15 = (Boolean) t.getThrottleInfo(addr, "F15"); 089 Boolean f16 = (Boolean) t.getThrottleInfo(addr, "F16"); 090 Boolean f17 = (Boolean) t.getThrottleInfo(addr, "F17"); 091 Boolean f18 = (Boolean) t.getThrottleInfo(addr, "F18"); 092 Boolean f19 = (Boolean) t.getThrottleInfo(addr, "F19"); 093 Boolean f20 = (Boolean) t.getThrottleInfo(addr, "F20"); 094 Boolean f21 = (Boolean) t.getThrottleInfo(addr, "F21"); 095 Boolean f22 = (Boolean) t.getThrottleInfo(addr, "F22"); 096 Boolean f23 = (Boolean) t.getThrottleInfo(addr, "F23"); 097 Boolean f24 = (Boolean) t.getThrottleInfo(addr, "F24"); 098 Boolean f25 = (Boolean) t.getThrottleInfo(addr, "F25"); 099 Boolean f26 = (Boolean) t.getThrottleInfo(addr, "F26"); 100 Boolean f27 = (Boolean) t.getThrottleInfo(addr, "F27"); 101 Boolean f28 = (Boolean) t.getThrottleInfo(addr, "F28"); 102 // and now build the output string to send 103 String StatusString = "100 INFO " + bus + " GL " + address + " "; 104 StatusString += isForward ? "1 " : "0 "; 105 { 106 int numSteps = 100; 107 // For NMRA DCC speed step modes, we use the number of steps from that mode. 108 // Non-NMRA modes are not supported, so for those, we fall back to 100 steps. 109 switch(speedStepMode) { 110 case NMRA_DCC_128: 111 case NMRA_DCC_28: 112 case NMRA_DCC_27: 113 case NMRA_DCC_14: 114 numSteps = speedStepMode.numSteps; 115 break; 116 default: 117 numSteps = 100; 118 break; 119 } 120 StatusString += (int) java.lang.Math.ceil(speedSetting * numSteps) + " " + numSteps; 121 } 122 StatusString += f0 ? " 1" : " 0"; 123 StatusString += f1 ? " 1" : " 0"; 124 StatusString += f2 ? " 1" : " 0"; 125 StatusString += f3 ? " 1" : " 0"; 126 StatusString += f4 ? " 1" : " 0"; 127 StatusString += f5 ? " 1" : " 0"; 128 StatusString += f6 ? " 1" : " 0"; 129 StatusString += f7 ? " 1" : " 0"; 130 StatusString += f8 ? " 1" : " 0"; 131 StatusString += f9 ? " 1" : " 0"; 132 StatusString += f10 ? " 1" : " 0"; 133 StatusString += f11 ? " 1" : " 0"; 134 StatusString += f12 ? " 1" : " 0"; 135 StatusString += f13 ? " 1" : " 0"; 136 StatusString += f14 ? " 1" : " 0"; 137 StatusString += f15 ? " 1" : " 0"; 138 StatusString += f16 ? " 1" : " 0"; 139 StatusString += f17 ? " 1" : " 0"; 140 StatusString += f18 ? " 1" : " 0"; 141 StatusString += f19 ? " 1" : " 0"; 142 StatusString += f20 ? " 1" : " 0"; 143 StatusString += f21 ? " 1" : " 0"; 144 StatusString += f22 ? " 1" : " 0"; 145 StatusString += f23 ? " 1" : " 0"; 146 StatusString += f24 ? " 1" : " 0"; 147 StatusString += f25 ? " 1" : " 0"; 148 StatusString += f26 ? " 1" : " 0"; 149 StatusString += f27 ? " 1" : " 0"; 150 StatusString += f28 ? " 1" : " 0"; 151 StatusString += "\n\r"; 152 output.write(StatusString.getBytes()); 153 } else { 154 output.write(Bundle.getMessage("Error412").getBytes()); 155 } 156 } 157 158 @Override 159 public void sendErrorStatus() throws IOException { 160 output.write(Bundle.getMessage("Error499").getBytes()); 161 } 162 163 @Override 164 public void parsecommand(String statusString) throws JmriException, IOException { 165 } 166 167 @Override 168 public void sendThrottleFound(jmri.LocoAddress address) throws IOException { 169 Integer bus; 170 if (addressList.contains(address)) { 171 bus = busList.get(addressList.indexOf(address)); 172 } else { 173 // we didn't request this address. 174 return; 175 } 176 177 // Build the output string to send 178 String StatusString = "101 INFO " + bus + " GL " + address.getNumber() + " "; // assume DCC for now. 179 StatusString += address.getProtocol() == jmri.LocoAddress.Protocol.DCC_SHORT ? "N 1 28" : "N 2 28"; 180 StatusString += "\n\r"; 181 output.write(StatusString.getBytes()); 182 } 183 184 @Override 185 public void sendThrottleReleased(jmri.LocoAddress address) throws IOException { 186 Integer bus; 187 if (addressList.contains(address)) { 188 bus = busList.get(addressList.indexOf(address)); 189 } else { 190 // we didn't request this address. 191 return; 192 } 193 194 // Build the output string to send 195 String StatusString = "102 INFO " + bus + " GL " + address.getNumber(); // assume DCC for now. 196 StatusString += address.getProtocol() == jmri.LocoAddress.Protocol.DCC_SHORT ? "N 1 28" : "N 2 28"; 197 StatusString += "\n\r"; 198 output.write(StatusString.getBytes()); 199 } 200 201 public void initThrottle(int bus, int address, boolean isLong, 202 int speedsteps, int functions) throws IOException { 203 log.debug("initThrottle called with bus {} and address {}", bus, address); 204 205 /* translate the bus into a system connection memo */ 206 java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class); 207 SystemConnectionMemo memo; 208 try { 209 memo = list.get(bus - 1); 210 } catch (java.lang.IndexOutOfBoundsException obe) { 211 output.write(Bundle.getMessage("Error412").getBytes()); 212 return; 213 } 214 215 /* request the throttle for this particular locomotive address */ 216 if (memo.provides(jmri.ThrottleManager.class)) { 217 ThrottleManager t = memo.get(jmri.ThrottleManager.class); 218 // we will use getThrottleInfo to request information about the 219 // address, so we need to convert the address to a DccLocoAddress 220 // object first. 221 DccLocoAddress addr = new DccLocoAddress(address, isLong); 222 busList.add(bus); 223 addressList.add(addr); 224 t.requestThrottle(addr, this, false); 225 } 226 } 227 228 public void releaseThrottle(int bus, int address) throws IOException { 229 log.debug("releaseThrottle called with bus {} and address {}", bus, address); 230 231 /* translate the bus into a system connection memo */ 232 java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class); 233 SystemConnectionMemo memo; 234 try { 235 memo = list.get(bus - 1); 236 } catch (java.lang.IndexOutOfBoundsException obe) { 237 output.write(Bundle.getMessage("Error412").getBytes()); 238 return; 239 } 240 241 /* release the throttle for this particular locomotive address */ 242 if (memo.provides(jmri.ThrottleManager.class)) { 243 ThrottleManager t = memo.get(jmri.ThrottleManager.class); 244 DccLocoAddress addr = new DccLocoAddress(address, t.canBeLongAddress(address)); 245 t.releaseThrottle((DccThrottle) throttleList.get(addressList.indexOf(addr)), this); 246 throttleList.remove(addressList.indexOf(addr)); 247 sendThrottleReleased(addr); 248 busList.remove(addressList.indexOf(addr)); 249 addressList.remove(addr); 250 } 251 } 252 253 /* 254 * Set Throttle Speed and Direction 255 * 256 * @param bus, bus the throttle is on. 257 * @param l address of the locomotive to change speed of. 258 * @param speed float representing the speed, -1 for emergency stop. 259 * @param isForward boolean, true if forward, false if reverse or 260 * undefined. 261 */ 262 public void setThrottleSpeedAndDirection(int bus, int address, float speed, boolean isForward) { 263 log.debug("Setting Speed for address {} bus {} to {} with direction {}", 264 address, bus, speed, isForward ? "forward" : "reverse"); 265 java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class); 266 SystemConnectionMemo memo; 267 try { 268 memo = list.get(bus - 1); 269 } catch (java.lang.IndexOutOfBoundsException obe) { 270 try { 271 output.write(Bundle.getMessage("Error412").getBytes()); 272 } catch (IOException ioe) { 273 log.error("Error writing to network port"); 274 } 275 return; 276 } 277 278 /* request the throttle for this particular locomotive address */ 279 if (memo.provides(jmri.ThrottleManager.class)) { 280 ThrottleManager tm = memo.get(jmri.ThrottleManager.class); 281 // we will use getThrottleInfo to request information about the 282 // address, so we need to convert the address to a DccLocoAddress 283 // object first. 284 DccLocoAddress addr = new DccLocoAddress(address, tm.canBeLongAddress(address)); 285 286 // get the throttle for the address. 287 if (addressList.contains(addr)) { 288 log.debug("Throttle in throttle list"); 289 Throttle t = throttleList.get(addressList.indexOf(addr)); 290 // set the speed and direction. 291 t.setSpeedSetting(speed); 292 t.setIsForward(isForward); 293 } 294 } 295 } 296 297 /* 298 * Set Throttle Functions on/off 299 * 300 * @param bus, bus the throttle is on. 301 * @param l address of the locomotive to change speed of. 302 * @param fList an ArrayList of boolean values indicating whether the 303 * function is active or not. 304 */ 305 public void setThrottleFunctions(int bus, int address, ArrayList<Boolean> fList) { 306 log.debug("Setting Functions for address {} bus {}", 307 address, bus); 308 java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class); 309 SystemConnectionMemo memo; 310 try { 311 memo = list.get(bus - 1); 312 } catch (java.lang.IndexOutOfBoundsException obe) { 313 try { 314 output.write(Bundle.getMessage("Error412").getBytes()); 315 } catch (IOException ioe) { 316 log.error("Error writing to network port"); 317 } 318 return; 319 } 320 321 /* request the throttle for this particular locomotive address */ 322 if (memo.provides(jmri.ThrottleManager.class)) { 323 ThrottleManager tm = memo.get(jmri.ThrottleManager.class); 324 // we will use getThrottleInfo to request information about the 325 // address, so we need to convert the address to a DccLocoAddress 326 // object first. 327 DccLocoAddress addr = new DccLocoAddress(address, tm.canBeLongAddress(address)); 328 329 // get the throttle for the address. 330 if (addressList.contains(addr)) { 331 log.debug("Throttle in throttle list"); 332 Throttle t = throttleList.get(addressList.indexOf(addr)); 333 setFunctionsByThrottle(t,fList); 334 335 } 336 } 337 } 338 339 // implementation of ThrottleListener 340 @Override 341 public void notifyThrottleFound(DccThrottle t) { 342 log.debug("notified throttle found"); 343 throttleList.add(t); 344 try { 345 sendThrottleFound(t.getLocoAddress()); 346 t.addPropertyChangeListener(new SRCPThrottlePropertyChangeListener(this, t, busList.get(addressList.indexOf(t.getLocoAddress())))); 347 } catch (java.io.IOException ioe) { 348 //Something failed writing data to the port. 349 } 350 } 351 352 static class SRCPThrottlePropertyChangeListener implements PropertyChangeListener { 353 354 int bus; 355 int address; 356 JmriSRCPThrottleServer clientServer = null; 357 358 SRCPThrottlePropertyChangeListener(JmriSRCPThrottleServer ts, Throttle t, 359 int bus) { 360 log.debug("property change listener created"); 361 clientServer = ts; 362 this.bus = bus; 363 address = t.getLocoAddress().getNumber(); 364 } 365 366 // update the state of this throttle if any of the properties change 367 @Override 368 public void propertyChange(java.beans.PropertyChangeEvent e) { 369 if (log.isDebugEnabled()) { 370 log.debug("Property change event received {} / {}", e.getPropertyName(), e.getNewValue()); 371 } 372 switch (e.getPropertyName()) { 373 case Throttle.SPEEDSETTING: 374 case Throttle.SPEEDSTEPS: 375 case Throttle.ISFORWARD: 376 try { 377 clientServer.sendStatus(bus, address); 378 } catch (IOException ioe) { 379 log.error("Error writing to network port"); 380 } 381 break; 382 default: 383 for (int i = 0; i <= 28; i++) { 384 if (e.getPropertyName().equals("F" + i)) { 385 try { 386 clientServer.sendStatus(bus, address); 387 } catch (IOException ioe) { 388 log.error("Error writing to network port"); 389 } 390 break; // stop the loop, only one function property 391 // will be matched. 392 } else if (e.getPropertyName().equals("F" + i + "Momentary")) { 393 try { 394 clientServer.sendStatus(bus, address); 395 } catch (IOException ioe) { 396 log.error("Error writing to network port"); 397 } 398 break; // stop the loop, only one function property 399 // will be matched. 400 } 401 } 402 break; 403 } 404 405 } 406 407 } 408 409}