001 002package jmri.jmrix.loconet; 003 004import java.awt.event.ActionEvent; 005import javax.annotation.Nonnull; 006import javax.swing.Timer; 007import jmri.ProgListener; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010import jmri.ProgrammerException; 011 012/** 013 * 014 * @author given 015 */ 016public class CsOpSwAccess implements LocoNetListener { 017 018 private Timer csOpSwAccessTimer; 019 private Timer csOpSwValidTimer; 020 private CmdStnOpSwStateType cmdStnOpSwState; 021 private int cmdStnOpSwNum; 022 private boolean cmdStnOpSwVal; 023 private LocoNetSystemConnectionMemo memo; 024 private ProgListener p; 025 private boolean doingWrite; 026 private int[] opSwBytes; 027 private boolean haveValidLowBytes; 028 private boolean haveValidHighBytes; 029 030 public CsOpSwAccess(@Nonnull LocoNetSystemConnectionMemo memo, @Nonnull ProgListener p) { 031 this.memo = memo; 032 this.p = p; 033 // listen to the LocoNet 034 memo.getLnTrafficController().addLocoNetListener(~0, this); 035 csOpSwAccessTimer = null; 036 csOpSwValidTimer = null; 037 cmdStnOpSwState = CmdStnOpSwStateType.IDLE; 038 opSwBytes = new int[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 039 haveValidLowBytes = false; 040 haveValidHighBytes = false; 041 log.debug("csOpSwAccess constructor"); 042 043 } 044 045 public void setProgrammerListener(@Nonnull ProgListener p) { 046 this.p = p; 047 } 048 049 public void readCsOpSw(String opSw, @Nonnull ProgListener pL) throws ProgrammerException { 050 log.debug("reading a cs opsw : {}", opSw); 051 // Note: Only get here if decoder xml specifies LocoNetCsOpSwMode. 052 053 if (csOpSwAccessTimer == null) { 054 log.debug("initializing timers"); 055 initializeCsOpSwAccessTimer(); 056 initializeCsOpSwValidTimer(); 057 } 058 059 p = pL; 060 doingWrite = false; 061 log.debug("read Command Station OpSw{}", opSw); 062 063 String[] parts; 064 parts = opSw.split("\\."); 065 ProgListener temp = pL; 066 if ((parts.length == 2) && 067 (parts[0].equals("csOpSw")) && 068 ((Integer.parseInt(parts[1])) >= 1) && 069 (Integer.parseInt(parts[1]) <= 128)) { 070 log.trace("splitting CV: {} becomes {} and {}", opSw, parts[0], parts[1]); 071 // a valid command station OpSw identifier was found 072 log.trace("Valid typeWord = 1; attempting to read OpSw{}.", Integer.parseInt(parts[1])); 073 log.trace("starting from state {}", cmdStnOpSwState); 074 readCmdStationOpSw(Integer.parseInt(parts[1])); 075 return; 076 } else { 077 log.warn("Cannot perform Cs OpSw access: parts.length={}, parts[]={}",parts.length, parts); 078 p = null; 079 if (temp != null) { 080 temp.programmingOpReply(0,ProgListener.NotImplemented); 081 } 082 return; 083 } 084 } 085 086 public void writeCsOpSw(String opSw, int val, @Nonnull ProgListener pL) throws ProgrammerException { 087 p = null; 088 String[] parts = opSw.split("\\."); 089 if (((val != 0) && (val != 1)) || 090 (parts.length != 2) || 091 (!parts[0].equals("csOpSw")) || 092 (Integer.parseInt(parts[1]) <= 0) || 093 (Integer.parseInt(parts[1]) >= 129)) { 094 // invalid request - signal it to the programmer 095 log.warn("Cannot perform Cs OpSw access: parts.length={}, parts[]={}, val={}",parts.length, parts, val); 096 if (pL != null) { 097 pL.programmingOpReply(0,ProgListener.NotImplemented); 098 } 099 return; 100 } 101 102 if (csOpSwAccessTimer == null) { 103 initializeCsOpSwAccessTimer(); 104 initializeCsOpSwValidTimer(); 105 } 106 107 // Command Station OpSws are handled via slot 0x7f. 108 p = pL; 109 doingWrite = true; 110 log.debug("write Command Station OpSw{} as {}", opSw, (val>0)?"c":"t"); 111 int opSwNum = Integer.parseInt(parts[1]); 112 log.debug("CS OpSw number {}", opSwNum); 113 if (!updateCmdStnOpSw(opSwNum, 114 (val==1))) { 115 sendFinalProgrammerReply(-1, ProgListener.ProgrammerBusy); 116 } 117 } 118 119 @Override 120 public void message(LocoNetMessage m) { 121 if (cmdStnOpSwState == CmdStnOpSwStateType.IDLE) { 122 return; 123 } 124 boolean value; 125 if ((m.getOpCode() == LnConstants.OPC_SL_RD_DATA) && 126 (m.getElement(1) == 0x0E) && 127 ((m.getElement(2) & 0x7E) == 0x7E) && 128 ((cmdStnOpSwState == CmdStnOpSwStateType.QUERY) || 129 ((cmdStnOpSwState == CmdStnOpSwStateType.QUERY_ENHANCED)))) { 130 log.debug("got slot {} read data", m.getElement(2)); 131 updateStoredOpSwsFromRead(m); 132 if ((cmdStnOpSwState == CmdStnOpSwStateType.QUERY) || 133 (cmdStnOpSwState == CmdStnOpSwStateType.QUERY_ENHANCED)) { 134 log.debug("got slot {} read data in response to OpSw query", m.getElement(2)); 135 if (((m.getElement(7) & 0x40) == 0x40) && 136 (cmdStnOpSwState == CmdStnOpSwStateType.QUERY)){ 137 // attempt to get extended OpSw info 138 csOpSwAccessTimer.restart(); 139 LocoNetMessage m2 = new LocoNetMessage(new int[] {0xbb, 0x7e, 0x00, 0x00}); 140 cmdStnOpSwState = CmdStnOpSwStateType.QUERY_ENHANCED; 141 memo.getLnTrafficController().sendLocoNetMessage(m2); 142 csOpSwAccessTimer.start(); 143 return; 144 } 145 csOpSwAccessTimer.stop(); 146 cmdStnOpSwState = CmdStnOpSwStateType.HAS_STATE; 147 log.debug("starting valid timer"); 148 csOpSwValidTimer.start(); // start the "valid data" timer 149 if (doingWrite == true) { 150 log.debug("now can finish the write by updating the correct bit..."); 151 finishTheWrite(); 152 } else { 153 if (!(((cmdStnOpSwNum > 0) && (cmdStnOpSwNum < 65) && (haveValidLowBytes)) || 154 ((cmdStnOpSwNum > 64) && (cmdStnOpSwNum < 129) && (haveValidHighBytes)))) { 155 ProgListener temp = p; 156 p = null; 157 if (temp != null) { 158 log.debug("Aborting - OpSw {} beyond allowed range", cmdStnOpSwNum); 159 temp.programmingOpReply(0, ProgListener.NotImplemented); 160 } else { 161 log.warn("no programmer to which the error condition can be returned."); 162 } 163 } else { 164 value = extractCmdStnOpSw(cmdStnOpSwNum); 165 log.debug("now can return the extracted OpSw{} read data ({}) to the programmer", cmdStnOpSwNum, value); 166 sendFinalProgrammerReply(value?1:0, ProgListener.OK); 167 } 168 } 169 } else if ((cmdStnOpSwState == CmdStnOpSwStateType.QUERY_BEFORE_WRITE) || 170 (cmdStnOpSwState == CmdStnOpSwStateType.QUERY_ENHANCED_BEFORE_WRITE)){ 171 if (((m.getElement(7) & 0x40) == 0x40) && 172 (cmdStnOpSwState == CmdStnOpSwStateType.QUERY_BEFORE_WRITE)) { 173 csOpSwAccessTimer.restart(); 174 LocoNetMessage m2 = new LocoNetMessage(new int[] {0xbb, 0x7e, 0x00, 0x00}); 175 cmdStnOpSwState = CmdStnOpSwStateType.QUERY_ENHANCED_BEFORE_WRITE; 176 memo.getLnTrafficController().sendLocoNetMessage(m2); 177 csOpSwAccessTimer.start(); 178 return; 179 } 180 log.debug("have received OpSw query before a write; now can process the data modification"); 181 csOpSwAccessTimer.stop(); 182 cmdStnOpSwState = CmdStnOpSwStateType.WRITE; 183 LocoNetMessage m2 = updateOpSwVal(cmdStnOpSwNum, cmdStnOpSwVal); 184 log.debug("performing enhanced opsw write: {}",m2.toString()); 185 log.debug("todo; uncomment the send?"); 186 //memo.getLnTrafficController().sendLocoNetMessage(m2); 187 csOpSwAccessTimer.start(); 188 } 189 } else if ((m.getOpCode() == LnConstants.OPC_LONG_ACK) && 190 (m.getElement(1) == 0x6f) && 191 (m.getElement(2) == 0x7f) && 192 (cmdStnOpSwState == CmdStnOpSwStateType.WRITE)) { 193 csOpSwAccessTimer.stop(); 194 cmdStnOpSwState = CmdStnOpSwStateType.HAS_STATE; 195 value = extractCmdStnOpSw(cmdStnOpSwNum); 196 sendFinalProgrammerReply(value?1:0,ProgListener.OK); 197 } 198 } 199 200 public void readCmdStationOpSw(int cv) { 201 log.debug("readCmdStationOpSw: state is {}, have lowvalid {}, have highvalid {}, asking for OpSw{}", 202 cmdStnOpSwState, haveValidLowBytes?"true":"false", 203 haveValidHighBytes?"true":"false", cv); 204 if (cmdStnOpSwState == CmdStnOpSwStateType.HAS_STATE) { 205 if ((((cv > 0) && (cv < 65) && haveValidLowBytes)) || 206 (((cv > 64) && (cv < 129) && haveValidHighBytes))) { 207 // can re-use previous state - it has not "expired" due to time since read. 208 log.debug("readCmdStationOpSw: returning state from previously-stored state for OpSw{}", cv); 209 returnCmdStationOpSwVal(cv); 210 } else { 211 log.warn("Cannot perform Cs OpSw access of OpSw {} account out-of-range for this command station.",cv); 212 sendFinalProgrammerReply(-1,ProgListener.NotImplemented); 213 } 214 } else if ((cmdStnOpSwState == CmdStnOpSwStateType.IDLE) || 215 (cmdStnOpSwState == CmdStnOpSwStateType.HAS_STATE)) { 216 // do not have valid data or old data has "expired" due to time since read. 217 // Need to send a slot 127 (and 126, as appropriate) read to LocoNet 218 log.debug("readCmdStationOpSw: attempting to read some CVs"); 219 updateCmdStnOpSw(cv,false); 220 } else { 221 log.warn("readCmdStationOpSw: aborting - cmdStnOpSwState is odd: {}", cmdStnOpSwState); 222 sendFinalProgrammerReply(-1,ProgListener.UnknownError); 223 } 224 } 225 226 public void returnCmdStationOpSwVal(int cmdStnOpSwNum) { 227 boolean returnVal = extractCmdStnOpSw(cmdStnOpSwNum); 228 if (p != null) { 229 // extractCmdStnOpSw did not find an erroneous condition 230 log.debug("returnCmdStationOpSwVal: Returning OpSw{} value of {}", cmdStnOpSwNum, returnVal); 231 p.programmingOpReply(returnVal?1:0, ProgListener.OK); 232 } 233 } 234 235 public boolean updateCmdStnOpSw(int opSwNum, boolean val) { 236 if (cmdStnOpSwState == CmdStnOpSwStateType.HAS_STATE) { 237 if (!doingWrite) { 238 log.debug("updateCmdStnOpSw: should already have OpSw values from previous read."); 239 return false; 240 } else { 241 cmdStnOpSwVal = val; 242 cmdStnOpSwNum = opSwNum; 243 finishTheWrite(); 244 return true; 245 } 246 } 247 if (cmdStnOpSwState != CmdStnOpSwStateType.IDLE) { 248 log.debug("updateCmdStnOpSw: cannot query OpSw values from state {}", cmdStnOpSwState); 249 return false; 250 } 251 log.debug("updateCmdStnOpSw: attempting to query the OpSws when state = {}", cmdStnOpSwState); 252 cmdStnOpSwState = CmdStnOpSwStateType.QUERY; 253 cmdStnOpSwNum = opSwNum; 254 cmdStnOpSwVal = val; 255 int[] contents = {LnConstants.OPC_RQ_SL_DATA, 0x7F, 0x0, 0x0}; 256 memo.getLnTrafficController().sendLocoNetMessage(new LocoNetMessage(contents)); 257 csOpSwAccessTimer.start(); 258 259 return true; 260 } 261 262 public boolean extractCmdStnOpSw(int cmdStnOpSwNum) { 263 264 if (((cmdStnOpSwNum > 0) && (cmdStnOpSwNum < 65) && (haveValidLowBytes)) || 265 ((cmdStnOpSwNum > 64) && (cmdStnOpSwNum < 129) && (haveValidHighBytes))){ 266 267 log.debug("attempting to extract value for OpSw {} with haveValidLowBytes {} and haveValidHighBytes {}", 268 cmdStnOpSwNum, haveValidLowBytes, haveValidHighBytes); 269 int msgByte = (cmdStnOpSwNum-1) /8; 270 int bitpos = (cmdStnOpSwNum-1)-(8*msgByte); 271 boolean retval = (((opSwBytes[msgByte] >> bitpos) & 1) == 1); 272 log.debug("extractCmdStnOpSw: opsw{} from bit {} of opSwByte[{}]={} gives {}", cmdStnOpSwNum, bitpos, msgByte, opSwBytes[msgByte], retval); 273 return retval; 274 } else { 275 log.debug("failing extract account problem with cmdStnOpSwNum={}, haveValidLowBytes {} and haveValidHighBytes {}", 276 cmdStnOpSwNum, haveValidLowBytes, haveValidHighBytes); 277 csOpSwAccessTimer.stop(); 278 csOpSwValidTimer.stop(); 279 sendFinalProgrammerReply(-1,ProgListener.UnknownError); 280 return false; 281 } 282 } 283 284 public LocoNetMessage updateOpSwVal(int cmdStnOpSwNum, boolean cmdStnOpSwVal) { 285 if (((cmdStnOpSwNum -1) & 0x07) == 7) { 286 log.warn("Cannot program OpSw{} account LocoNet encoding limitations.",cmdStnOpSwNum); 287 sendFinalProgrammerReply(-1,ProgListener.UnknownError); 288 return new LocoNetMessage(new int[] {LnConstants.OPC_GPBUSY, 0x0}); 289 } else if ((cmdStnOpSwNum < 1) || (cmdStnOpSwNum > 128)) { 290 log.warn("Cannot program OpSw{} account OpSw number out of range.",cmdStnOpSwNum); 291 sendFinalProgrammerReply(-1, ProgListener.NotImplemented); 292 return new LocoNetMessage(new int[] {LnConstants.OPC_GPBUSY, 0x0}); 293 } 294 295 log.debug("updateOpSwVal: OpSw{} = {}", cmdStnOpSwNum, cmdStnOpSwVal); 296 changeOpSwBytes(cmdStnOpSwNum, cmdStnOpSwVal); 297 LocoNetMessage m = new LocoNetMessage(14); 298 m.setOpCode(0xEF); 299 m.setElement(1, 0x0e); 300 m.setElement(2, (cmdStnOpSwNum >= 65)?0x7E:0x7F); 301 302 m.setElement(3, opSwBytes[0+(8*(cmdStnOpSwNum>64?1:0))]); 303 m.setElement(4, opSwBytes[1+(8*(cmdStnOpSwNum>64?1:0))]); 304 m.setElement(5, opSwBytes[2+(8*(cmdStnOpSwNum>64?1:0))]); 305 m.setElement(6, opSwBytes[3+(8*(cmdStnOpSwNum>64?1:0))]); 306 m.setElement(7, 0); 307 m.setElement(8, opSwBytes[4+(8*(cmdStnOpSwNum>64?1:0))]); 308 m.setElement(9, opSwBytes[5+(8*(cmdStnOpSwNum>64?1:0))]); 309 m.setElement(10, opSwBytes[6+(8*(cmdStnOpSwNum>64?1:0))]); 310 m.setElement(11, opSwBytes[7+(8*(cmdStnOpSwNum>64?1:0))]); 311 m.setElement(12, 0); 312 m.setElement(13, 0); 313 return m; 314 } 315 316 private void finishTheWrite() { 317 cmdStnOpSwState = CmdStnOpSwStateType.WRITE; 318 LocoNetMessage m2 = updateOpSwVal(cmdStnOpSwNum, 319 cmdStnOpSwVal); 320 if (m2.getNumDataElements() == 2) { 321 // failure - no message provided - must be out-of-range opsw number 322 sendFinalProgrammerReply(-1, ProgListener.UnknownError); 323 return; 324 } 325 326 m2.setOpCode(LnConstants.OPC_WR_SL_DATA); 327 log.debug("finish the write sending LocoNet cmd stn opsw write message {}, length={}", m2.toString(), m2.getNumDataElements()); 328 memo.getLnTrafficController().sendLocoNetMessage(m2); 329 csOpSwAccessTimer.start(); 330 } 331 332 private void sendFinalProgrammerReply(int val, int response) { 333 log.debug("returning response {} with value {} to programmer", response, val); 334 ProgListener temp = p; 335 p = null; 336 if (temp != null) { 337 temp.programmingOpReply(val, response); 338 } 339 340 } 341 342 enum CmdStnOpSwStateType { 343 IDLE, 344 QUERY, 345 QUERY_ENHANCED, 346 QUERY_BEFORE_WRITE, 347 QUERY_ENHANCED_BEFORE_WRITE, 348 WRITE, 349 HAS_STATE} 350 351 void initializeCsOpSwAccessTimer() { 352 if (csOpSwAccessTimer == null) { 353 csOpSwAccessTimer = new Timer(500, (ActionEvent e) -> { 354 log.debug("csOpSwAccessTimer timed out!"); 355 ProgListener temp = p; 356 p = null; 357 if (temp != null) { 358 temp.programmingOpReply(0, ProgListener.FailedTimeout); 359 } 360 }); 361 csOpSwAccessTimer.setRepeats(false); 362 } 363 } 364 365 void initializeCsOpSwValidTimer() { 366 if (csOpSwValidTimer == null) { 367 csOpSwValidTimer = new Timer(1000, (ActionEvent e) -> { 368 log.debug("csOpSwValidTimer timed out; invalidating held data!"); 369 haveValidLowBytes = false; 370 haveValidHighBytes = false; 371 cmdStnOpSwState = CmdStnOpSwStateType.IDLE; 372 }); 373 csOpSwValidTimer.setRepeats(false); 374 } 375 } 376 private void updateStoredOpSwsFromRead(LocoNetMessage m) { 377 if ((m.getOpCode() == LnConstants.OPC_SL_RD_DATA) && 378 (m.getElement(1) == 0x0e) && 379 (m.getElement(2) == 0x7f)) { 380 opSwBytes[0] = m.getElement(3); 381 opSwBytes[1] = m.getElement(4); 382 opSwBytes[2] = m.getElement(5); 383 opSwBytes[3] = m.getElement(6); 384 opSwBytes[4] = m.getElement(8); 385 opSwBytes[5] = m.getElement(9); 386 opSwBytes[6] = m.getElement(10); 387 opSwBytes[7] = m.getElement(11); 388 opSwBytes[8] = 0; 389 opSwBytes[10] = 0; 390 opSwBytes[11] = 0; 391 opSwBytes[12] = 0; 392 opSwBytes[13] = 0; 393 opSwBytes[14] = 0; 394 opSwBytes[15] = 0; 395 haveValidLowBytes = true; 396 haveValidHighBytes = false; 397 } else if ((m.getOpCode() == LnConstants.OPC_SL_RD_DATA) && 398 (m.getElement(1) == 0x0e) && 399 (m.getElement(2) == 0x7e)) { 400 opSwBytes[8] = m.getElement(3); 401 opSwBytes[9] = m.getElement(4); 402 opSwBytes[10] = m.getElement(5); 403 opSwBytes[11] = m.getElement(6); 404 opSwBytes[12] = m.getElement(8); 405 opSwBytes[13] = m.getElement(9); 406 opSwBytes[14] = m.getElement(10); 407 opSwBytes[15] = m.getElement(11); 408 haveValidHighBytes = true; 409 } 410 } 411 private void changeOpSwBytes(int cmdStnOpSwNum, boolean cmdStnOpSwVal) { 412 log.debug("updating OpSw{} to {}", cmdStnOpSwNum, cmdStnOpSwVal); 413 int msgByte = (cmdStnOpSwNum-1) / 8; 414 int bitpos = (cmdStnOpSwNum-1)-(8*msgByte); 415 int newVal = (opSwBytes[msgByte] & (~(1<<bitpos))) | ((cmdStnOpSwVal?1:0)<<bitpos); 416 log.debug("updating OpSwBytes[{}] from {} to {}", msgByte, opSwBytes[msgByte], newVal); 417 opSwBytes[msgByte] = newVal; 418 } 419 420 // accessor 421 public CmdStnOpSwStateType getState() { 422 return cmdStnOpSwState; 423 } 424 425 /** 426 * Dispose of object's helper objects 427 * 428 * Stops the timers 429 * 430 */ 431 public void dispose() { 432 if (csOpSwAccessTimer != null) { 433 csOpSwAccessTimer.stop(); 434 } 435 if (csOpSwValidTimer != null) { 436 csOpSwValidTimer.stop(); 437 } 438 } 439 440 // initialize logging 441 private final static Logger log = LoggerFactory.getLogger(CsOpSwAccess.class); 442 443}