001package jmri.jmrix.can.cbus; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import javax.annotation.Nonnull; 007 008import jmri.ProgrammingMode; 009import jmri.jmrix.AbstractProgrammer; 010import jmri.jmrix.can.CanListener; 011import jmri.jmrix.can.CanMessage; 012import jmri.jmrix.can.CanReply; 013import jmri.jmrix.can.cbus.node.CbusNode; 014 015/** 016 * Implements the jmri.Programmer interface via commands for the CBUS 017 * programmer. 018 * 019 * @author Andrew Crosland Copyright (C) 2009 020 */ 021public class CbusDccProgrammer extends AbstractProgrammer implements CanListener { 022 023 public CbusDccProgrammer(jmri.jmrix.can.TrafficController tc) { 024 this.tc = tc; 025 addTc(tc); 026 } 027 028 private jmri.jmrix.can.CanSystemConnectionMemo _memo; 029 030 public CbusDccProgrammer(jmri.jmrix.can.CanSystemConnectionMemo m) { 031 this.tc = m.getTrafficController(); 032 _memo = m; 033 addTc(tc); 034 } 035 036 jmri.jmrix.can.TrafficController tc; 037 038 /** 039 * {@inheritDoc} 040 * Types implemented here. 041 */ 042 @Override 043 @Nonnull 044 public List<ProgrammingMode> getSupportedModes() { 045 List<ProgrammingMode> ret = new ArrayList<>(4); 046 ret.add(ProgrammingMode.DIRECTBITMODE); 047 ret.add(ProgrammingMode.DIRECTBYTEMODE); 048 ret.add(ProgrammingMode.PAGEMODE); 049 ret.add(ProgrammingMode.REGISTERMODE); 050 return ret; 051 } 052 053 // members for handling the programmer interface 054 int progState = 0; 055 static final int NOTPROGRAMMING = 0;// is notProgramming 056 static final int MODESENT = 1; // waiting reply to command to go into programming mode 057 static final int COMMANDSENT = 2; // read/write command sent, waiting reply 058 static final int RETURNSENT = 4; // waiting reply to go back to ops mode 059 static final int NVCOMMANDSENT = 8; // read/write command sent, waiting reply 060 boolean _progRead = false; 061 int _val; // remember the value being read/written for confirmative reply 062 int _cv; // remember the cv being read/written 063 static final int _nvOffset = 10000; // Offset to acces CBUS node NVs rather than DCC decoder CVs 064 private CbusNode _nodeOfInterest; // Sets the node to be used for CBUS module programming 065 066 /** 067 * Set the CBUS Node to be used for NV programming 068 * 069 * @param n a CBUS node 070 */ 071 public synchronized void setNodeOfInterest(CbusNode n) { 072 _nodeOfInterest = n; 073 } 074 075 /** 076 * {@inheritDoc} 077 */ 078 @Override 079 synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 080 final int CV = Integer.parseInt(CVname); 081 log.debug("writeCV {} listener {}",CV, p); 082 useProgrammer(p); 083 _progRead = false; 084 progState = COMMANDSENT; 085 _val = val; 086 _cv = CV; 087 // see why waiting 088 try { 089 startLongTimer(); 090 // write was in progress - send write command 091 if (_cv < _nvOffset) { 092 progState = COMMANDSENT; 093 tc.sendCanMessage(CbusMessage.getWriteCV(_cv, _val, getMode(), tc.getCanid()), this); 094 } else { 095 progState = NVCOMMANDSENT; 096 _nodeOfInterest.send.nVSET(_nodeOfInterest.getNodeNumber(), CV - _nvOffset, _val); 097 } 098 } catch (Exception e) { 099 // program op failed, go straight to end 100 log.error("Write operation failed",e); 101 progState = RETURNSENT; 102 //controller().sendCanMessage(CbusMessage.getExitProgMode(), this); 103 } 104 } 105 106 /** 107 * {@inheritDoc} 108 */ 109 @Override 110 synchronized public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 111 readCV(CV, p); 112 } 113 114 /** 115 * {@inheritDoc} 116 */ 117 @Override 118 synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 119 readCV(CVname, p, 0); 120 } 121 122 /** 123 * {@inheritDoc} 124 */ 125 @Override 126 synchronized public void readCV(String CVname, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException { 127 final int CV = Integer.parseInt(CVname); 128 log.debug("readCV {} listens {}",CV, p); 129 useProgrammer(p); 130 _progRead = true; 131 progState = COMMANDSENT; 132 _cv = CV; 133 // see why waiting 134 try { 135 startLongTimer(); 136 // read was in progress - send read command 137 if (_cv < _nvOffset) { 138 progState = COMMANDSENT; 139 if (_memo.supportsCVHints()) { 140 tc.sendCanMessage(CbusMessage.getVerifyCV(_cv, getMode(), startVal, tc.getCanid()), this); 141 } else { 142 tc.sendCanMessage(CbusMessage.getReadCV(_cv, getMode(), tc.getCanid()), this); 143 } 144 } else { 145 progState = NVCOMMANDSENT; 146 _nodeOfInterest.send.nVRD(_nodeOfInterest.getNodeNumber(), CV - _nvOffset); 147 } 148 } catch (Exception e) { 149 // program op failed, go straight to end 150 log.error("Read operation failed", e); 151 progState = RETURNSENT; 152 //controller().sendCanMessage(CbusMessage.getExitProgMode(), this); 153 } 154 } 155 156 private jmri.ProgListener _usingProgrammer = null; 157 158 // internal method to remember who's using the programmer 159 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 160 // test for only one! 161 if (_usingProgrammer != null && _usingProgrammer != p) { 162 log.info("programmer already in use by {}", _usingProgrammer); 163 throw new jmri.ProgrammerException("programmer in use"); 164 } else { 165 _usingProgrammer = p; 166 } 167 } 168 169 /** 170 * {@inheritDoc} 171 * Only listening for frames coming in to JMRI, see CanReply 172 */ 173 @Override 174 public void message(CanMessage m) { 175 } 176 177 /** 178 * {@inheritDoc} 179 */ 180 @Override 181 synchronized public void reply(CanReply m) { 182 if ( m.extendedOrRtr() ) { 183 return; 184 } 185 if (progState == COMMANDSENT) { 186 log.debug("reply in COMMANDSENT state"); 187 // operation done, capture result, then have to leave programming mode 188 // check for errors 189 if ((m.getElement(0) == CbusConstants.CBUS_SSTAT) 190 && (m.getElement(2) == CbusConstants.SSTAT_NO_ACK)) { 191 log.warn("handle error reply {}", m); 192 // perhaps no loco present? Fail back to end of programming 193 //controller().sendCanMessage(CbusMessage.getExitProgMode(), this); 194 stopTimer(); 195 notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected); 196 } else if ((m.getElement(0) == CbusConstants.CBUS_SSTAT) 197 && (m.getElement(2) == CbusConstants.SSTAT_OVLD)) { 198 log.warn("Programming overload {}", m); 199 // Overload. Fail back to end of programming 200 stopTimer(); 201 notifyProgListenerEnd(-1, jmri.ProgListener.ProgrammingShort); 202 } else { 203 // see why waiting 204 if (_progRead && (m.getElement(0) == CbusConstants.CBUS_PCVS)) { 205 // read was in progress - received report CV message 206 _val = m.getElement(4); 207 progState = NOTPROGRAMMING; 208 stopTimer(); 209 // if this was a read, we cached the value earlier. If its a 210 // write, we're to return the original write value 211 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 212 } else if ((!_progRead) && (m.getElement(0) == CbusConstants.CBUS_SSTAT) 213 && (m.getElement(2) == CbusConstants.SSTAT_WR_ACK)) { 214 // write was in progress - acknowledge received 215 progState = NOTPROGRAMMING; 216 stopTimer(); 217 // if this was a read, we cached the value earlier. If its a 218 // write, we're to return the original write value 219 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 220 } else { 221 // Carry on waiting 222 log.debug("Reply ignored: {}", m); 223 } 224 } 225 } else if (progState == NVCOMMANDSENT) { 226 log.debug("reply in NVCOMMANDSENT state"); 227 // operation done, capture result, then have to leave programming mode 228 // see why waiting 229 if (_progRead && (m.getElement(0) == CbusConstants.CBUS_NVANS)) { 230 // read was in progress - received report CV message 231 _val = m.getElement(4); 232 progState = NOTPROGRAMMING; 233 stopTimer(); 234 // if this was a read, we cached the value earlier. If its a 235 // write, we're to return the original write value 236 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 237 } else if ((!_progRead) && (m.getElement(0) == CbusConstants.CBUS_WRACK)) { 238 // write was in progress - acknowledge received 239 progState = NOTPROGRAMMING; 240 stopTimer(); 241 // if this was a read, we cached the value earlier. If its a 242 // write, we're to return the original write value 243 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 244 } else { 245 // Carry on waiting 246 log.debug("Reply ignored: {}", m); 247 } 248 } 249 } 250 251 /** 252 * {@inheritDoc} 253 * 254 * Internal routine to handle a timeout 255 */ 256 @Override 257 synchronized protected void timeout() { 258 if (progState != NOTPROGRAMMING) { 259 // we're programming, time to stop 260 log.debug("timeout!"); 261 // perhaps no loco present? Fail back to end of programming 262 progState = NOTPROGRAMMING; 263 //controller().sendCbusMessage(CbusMessage.getExitProgMode(), this); 264 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 265 } 266 } 267 268 // internal method to notify of the final result 269 protected void notifyProgListenerEnd(int value, int status) { 270 log.debug("notifyProgListenerEnd value {}, status {}", value, status); 271 // the programmingOpReply handler might send an immediate reply, so 272 // clear the current listener _first_ 273 jmri.ProgListener temp = _usingProgrammer; 274 _usingProgrammer = null; 275 notifyProgListenerEnd(temp,value,status); 276 } 277 278 @Override 279 public void dispose() { 280 removeTc(tc); 281 } 282 283 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusDccProgrammer.class); 284 285}