001package jmri.jmrit.withrottle; 002 003import java.beans.PropertyChangeEvent; 004 005import jmri.*; 006import jmri.jmrit.roster.RosterEntry; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010/** 011 * @author Brett Hoffman Copyright (C) 2011 012 */ 013public class MultiThrottleController extends ThrottleController { 014 015 protected boolean isStealAddress; 016 017 public MultiThrottleController(char id, String key, ThrottleControllerListener tcl, ControllerInterface ci) { 018 super(id, tcl, ci); 019 log.debug("New MT controller"); 020 locoKey = key; 021 isStealAddress = false; 022 } 023 024 /** 025 * Builds a header to send to the wi-fi device for use in a message. 026 * Includes a separator - {@literal <;>} 027 * 028 * @param chr the character indicating what action is performed 029 * @return a pre-assembled header for this DccThrottle 030 */ 031 public String buildPacketWithChar(char chr) { 032 return ("M" + whichThrottle + chr + locoKey + "<;>"); 033 } 034 035 036 /* 037 * Send a message to the wi-fi device that a bound property of a DccThrottle 038 * has changed. Currently only handles function state. 039 * Current Format: Header + F(0 or 1) + function number 040 * 041 * Event may be from regular throttle or consist throttle, but is handled the same. 042 * 043 * Bound params: SpeedSteps, IsForward, SpeedSetting, F##, F##Momentary 044 */ 045 @Override 046 public void propertyChange(PropertyChangeEvent event) { 047 String eventName = event.getPropertyName(); 048 log.debug("property change: {}",eventName); 049 if (eventName.startsWith("F")) { 050 if (eventName.contains("Momentary")) { 051 return; 052 } 053 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 054 055 try { 056 if ((Boolean) event.getNewValue()) { 057 message.append("F1"); 058 } else { 059 message.append("F0"); 060 } 061 message.append(eventName.substring(1)); 062 } catch (ClassCastException cce) { 063 log.debug("Invalid event value. {}", cce.getMessage()); 064 } catch (IndexOutOfBoundsException oob) { 065 log.debug("Invalid event name. {}", oob.getMessage()); 066 } 067 068 for (ControllerInterface listener : controllerListeners) { 069 listener.sendPacketToDevice(message.toString()); 070 } 071 } 072 if (eventName.matches(Throttle.SPEEDSTEPS)) { 073 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 074 message.append("s"); 075 message.append(encodeSpeedStepMode((SpeedStepMode)event.getNewValue())); 076 for (ControllerInterface listener : controllerListeners) { 077 listener.sendPacketToDevice(message.toString()); 078 } 079 } 080 if (eventName.matches(Throttle.ISFORWARD)) { 081 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 082 message.append("R"); 083 message.append((Boolean) event.getNewValue() ? "1" : "0"); 084 for (ControllerInterface listener : controllerListeners) { 085 listener.sendPacketToDevice(message.toString()); 086 } 087 } 088 if (eventName.matches(Throttle.SPEEDSETTING)) { 089 float currentSpeed = ((Float) event.getNewValue()).floatValue(); 090 log.debug("Speed Setting: {} head of queue {}",currentSpeed, lastSentSpeed.peek()); 091 if(lastSentSpeed.isEmpty()) { 092 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 093 message.append("V"); 094 message.append(Math.round(currentSpeed / speedMultiplier)); 095 for (ControllerInterface listener : controllerListeners) { 096 listener.sendPacketToDevice(message.toString()); 097 } 098 } else { 099 if( Math.abs(lastSentSpeed.peek().floatValue()-currentSpeed)<0.0005 ) { 100 Float f = lastSentSpeed.poll(); // remove the value from the list. 101 log.debug("removed value {} from queue",f); 102 } 103 } 104 } 105 } 106 107 /** 108 * {@inheritDoc} 109 */ 110 @Override 111 public void sendFunctionLabels(RosterEntry re) { 112 113 if (re != null) { 114 StringBuilder functionString = new StringBuilder(buildPacketWithChar('L')); 115 116 int i; 117 for (i = 0; i < 29; i++) { 118 functionString.append("]\\["); 119 if ((re.getFunctionLabel(i) != null)) { 120 functionString.append(re.getFunctionLabel(i)); 121 } 122 } 123 for (ControllerInterface listener : controllerListeners) { 124 listener.sendPacketToDevice(functionString.toString()); 125 } 126 } 127 } 128 129 /** 130 * This replaces the previous method of sending a string of function states, 131 * and now sends them individually, the same as a property change would. 132 * 133 * @param t the throttle to send the states of. 134 */ 135 @Override 136 public void sendAllFunctionStates(DccThrottle t) { 137 log.debug("Sending state of all functions"); 138 for (int cnt = 0; cnt < 29; cnt++) { 139 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 140 message.append( t.getFunction(cnt) ? "F1" : "F0" ); 141 message.append(cnt); 142 controllerListeners.forEach(listener -> { 143 listener.sendPacketToDevice(message.toString()); 144 }); 145 } 146 } 147 148 /** 149 * {@inheritDoc} 150 */ 151 @Override 152 synchronized protected void sendCurrentSpeed(DccThrottle t) { 153 float currentSpeed = t.getSpeedSetting(); 154 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 155 message.append("V"); 156 message.append(Math.round(currentSpeed / speedMultiplier)); 157 for (ControllerInterface listener : controllerListeners) { 158 listener.sendPacketToDevice(message.toString()); 159 } 160 } 161 162 /** 163 * {@inheritDoc} 164 */ 165 @Override 166 protected void sendCurrentDirection(DccThrottle t) { 167 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 168 message.append("R"); 169 message.append(t.getIsForward() ? "1" : "0"); 170 for (ControllerInterface listener : controllerListeners) { 171 listener.sendPacketToDevice(message.toString()); 172 } 173 } 174 175 /** 176 * {@inheritDoc} 177 */ 178 @Override 179 protected void sendSpeedStepMode(DccThrottle t) { 180 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 181 message.append("s"); 182 message.append(encodeSpeedStepMode(throttle.getSpeedStepMode())); 183 for (ControllerInterface listener : controllerListeners) { 184 listener.sendPacketToDevice(message.toString()); 185 } 186 } 187 188 /** 189 * {@inheritDoc} 190 */ 191 @Override 192 protected void sendAllMomentaryStates(DccThrottle t) { 193 log.debug("Sending momentary state of all functions"); 194 for (int cnt = 0; cnt < 29; cnt++) { 195 StringBuilder message = new StringBuilder(buildPacketWithChar('A')); 196 message.append( t.getFunctionMomentary(cnt) ? "m1" : "m0" ); 197 message.append(cnt); 198 controllerListeners.forEach(listener -> { 199 listener.sendPacketToDevice(message.toString()); 200 }); 201 } 202 } 203 204 /** 205 * {@inheritDoc} A + indicates the address was acquired, - indicates 206 * released 207 */ 208 @Override 209 public void sendAddress() { 210 for (ControllerInterface listener : controllerListeners) { 211 if (isAddressSet) { 212 listener.sendPacketToDevice(buildPacketWithChar('+')); 213 } else { 214 listener.sendPacketToDevice(buildPacketWithChar('-')); 215 } 216 } 217 } 218 219 /** 220 * Send a message to a device that steal is needed. This message can be sent 221 * back to JMRI verbatim to complete a steal. 222 */ 223 public void sendStealAddress() { 224 StringBuilder message = new StringBuilder(buildPacketWithChar('S')); 225 message.append(locoKey); 226 for (ControllerInterface listener : controllerListeners) { 227 listener.sendPacketToDevice(message.toString()); 228 } 229 } 230 231 /** 232 * A decision is required for Throttle creation to continue. 233 * <p> 234 * Steal / Cancel, Share / Cancel, or Steal / Share Cancel 235 * <p> 236 * Callback of a request for an address that is in use. 237 * Will initiate a steal only if this MTC is flagged to do so. 238 * Otherwise, it will remove the request for the address. 239 * 240 * {@inheritDoc} 241 */ 242 @Override 243 public void notifyDecisionRequired(LocoAddress address, DecisionType question) { 244 if ( question == DecisionType.STEAL ){ 245 if (isStealAddress) { 246 // Address is now staged in ThrottleManager and has been requested as a steal 247 // Complete the process 248 InstanceManager.throttleManagerInstance().responseThrottleDecision(address, this, DecisionType.STEAL); 249 isStealAddress = false; 250 } else { 251 // Address has not been requested as a steal yet 252 sendStealAddress(); 253 notifyFailedThrottleRequest(address, "Steal Required"); 254 } 255 } 256 else if ( question == DecisionType.STEAL_OR_SHARE ){ // using the same process as a Steal 257 if (isStealAddress) { 258 // Address is now staged in ThrottleManager and has been requested as a steal 259 // Complete the process 260 InstanceManager.throttleManagerInstance().responseThrottleDecision(address, this, DecisionType.STEAL); 261 isStealAddress = false; 262 } else { 263 // Address has not been requested as a steal yet 264 sendStealAddress(); 265 notifyFailedThrottleRequest(address, "Steal Required"); 266 } 267 } 268 else { // if encountered likely to be DecisionType.SHARE 269 log.info("{} question not supported by WiThrottle.",question ); 270 } 271 272 273 } 274 275 // Encode a SpeedStepMode to a string. 276 private static String encodeSpeedStepMode(SpeedStepMode mode) { 277 switch(mode) { 278 // NOTE: old speed step modes use the original numeric values 279 // from when speed step modes were in DccThrottle. New speed step 280 // modes use the mode name. 281 case NMRA_DCC_128: 282 return "1"; 283 case NMRA_DCC_28: 284 return "2"; 285 case NMRA_DCC_27: 286 return "4"; 287 case NMRA_DCC_14: 288 return "8"; 289 case MOTOROLA_28: 290 return "16"; 291 default: 292 return mode.name; 293 } 294 } 295 296 private final static Logger log = LoggerFactory.getLogger(MultiThrottleController.class); 297 298}