001package jmri.jmrix.ecos; 002 003import java.util.ArrayList; 004import java.util.List; 005import javax.annotation.Nonnull; 006 007import jmri.ProgrammingMode; 008import jmri.jmrix.AbstractProgrammer; 009import jmri.jmrix.ecos.utilities.GetEcosObjectNumber; 010 011/** 012 * Implements the jmri.Programmer interface via commands for the ECoS 013 * programmer. This provides a service mode programmer. 014 * 015 * @author Karl Johan Lisby Copyright (C) 2015 and 2018 016 */ 017public class EcosProgrammer extends AbstractProgrammer implements EcosListener { 018 019 public EcosProgrammer(EcosTrafficController etc) { 020 tc = etc; 021 } 022 023 EcosTrafficController tc; 024 int ecosObject = 5; 025 String readCommand = "mode[readdccdirect]"; 026 String writeCommand = "mode[writedccdirect]"; 027 028 /** 029 * {@inheritDoc} 030 * 031 * @return list of programming modes implemented for ECoS 032 */ 033 @Override 034 @Nonnull 035 public List<ProgrammingMode> getSupportedModes() { 036 List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>(); 037 ret.add(ProgrammingMode.DIRECTBYTEMODE); 038 return ret; 039 } 040 041 // members for handling the programmer interface 042 int progState = 0; 043 static final int NOTPROGRAMMING = 0; // is notProgramming 044 static final int MODESENT = 1; // waiting reply to command to go into programming mode 045 static final int COMMANDSENT = 2; // read/write command sent, waiting reply 046 boolean _progRead = false; 047 int _val; // remember the value being read/written for confirmative reply 048 int _cv; // remember the cv being read/written 049 050 // programming interface 051 052 /** 053 * {@inheritDoc} 054 */ 055 @Override 056 synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 057 final int CV = Integer.parseInt(CVname); 058 if (log.isDebugEnabled()) { 059 log.debug("writeCV {} listens {}", CV, p); 060 } 061 useProgrammer(p); 062 _progRead = false; 063 // set commandPending state 064 progState = MODESENT; 065 _val = val; 066 _cv = CV; 067 068 // start the error timer 069 startShortTimer(); 070 071 // format and send message to go to program mode 072 // ECOS is in program mode by default but we need to subscribe to events 073 EcosMessage m; 074 m = new EcosMessage("request("+ecosObject+",view)"); 075 tc.sendEcosMessage(m, this); 076 } 077 078 /** 079 * {@inheritDoc} 080 */ 081 @Override 082 synchronized public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 083 readCV(CV, p); 084 } 085 086 /** 087 * {@inheritDoc} 088 */ 089 @Override 090 synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 091 final int CV = Integer.parseInt(CVname); 092 if (log.isDebugEnabled()) { 093 log.debug("readCV {} listens {}", CV, p); 094 } 095 useProgrammer(p); 096 _progRead = true; 097 // set commandPending state 098 // set commandPending state 099 progState = MODESENT; 100 _cv = CV; 101 102 // start the error timer 103 startShortTimer(); 104 105 // format and send message to go to program mode 106 // ECOS is in program mode by default but we need to subscribe to events 107 EcosMessage m; 108 m = new EcosMessage("request("+ecosObject+",view)"); 109 tc.sendEcosMessage(m, this); 110 } 111 112 private jmri.ProgListener _usingProgrammer = null; 113 114 // internal method to remember who's using the programmer 115 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 116 // test for only one! 117 if (_usingProgrammer != null && _usingProgrammer != p) { 118 if (log.isInfoEnabled()) { 119 log.info("programmer already in use by {}", _usingProgrammer); 120 } 121 throw new jmri.ProgrammerException("programmer in use"); 122 } else { 123 _usingProgrammer = p; 124 return; 125 } 126 } 127 128 /** 129 * {@inheritDoc} 130 */ 131 @Override 132 public void message(EcosMessage m) { 133 log.info("message: {}", m); 134 } 135 136 /** 137 * {@inheritDoc} 138 */ 139 @Override 140 synchronized public void reply(EcosReply reply) { 141 log.info("reply: {}", reply); 142 if (progState == NOTPROGRAMMING) { 143 // we get the complete set of replies now, so ignore these 144 if (log.isDebugEnabled()) { 145 log.debug("reply in NOTPROGRAMMING state"); 146 } 147 return; 148 } else if (progState == MODESENT) { 149 log.debug("reply in MODESENT state"); 150 // see if reply is the acknowledge of requesting view of events; if not, wait 151 if (reply.match("<REPLY request("+ecosObject+",view)>") == -1) { 152 return; 153 } 154 if (reply.match("<END 0 (OK)>") == -1) { 155 return; 156 } 157 // here ready to send the read/write command 158 progState = COMMANDSENT; 159 // send the command for reading or writing CV 160 try { 161 startLongTimer(); 162 EcosMessage m; 163 if (_progRead) { 164 // read was in progress - send read command 165 m = new EcosMessage("set(" + ecosObject + "," + readCommand + ",cv[" + _cv + "])"); 166 } else { 167 // write was in progress - send write command 168 m = new EcosMessage("set(" + ecosObject + "," + writeCommand + ",cv[" + _cv + "," + _val + "])"); 169 } 170 tc.sendEcosMessage(m, this); 171 } catch (Exception e) { 172 // program op failed, go straight to end 173 log.error("program operation failed, exception", e); 174 progState = NOTPROGRAMMING; 175 EcosMessage m; 176 m = new EcosMessage("release("+ecosObject+",view)"); 177 tc.sendEcosMessage(m, this); 178 notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected); 179 return; 180 } 181 } else if (progState == COMMANDSENT) { 182 if (log.isDebugEnabled()) { 183 log.debug("reply in COMMANDSENT state"); 184 } 185 // The real reply comes in an event; if this is not that event, wait 186 if (reply.match("<EVENT "+ecosObject+">") == -1) { 187 return; 188 } 189 // operation done, capture result, then leave programming mode 190 progState = NOTPROGRAMMING; 191 stopTimer(); 192 EcosMessage m; 193 m = new EcosMessage("release("+ecosObject+",view)"); 194 tc.sendEcosMessage(m, this); 195 // check for errors 196 if (reply.match("error") >= 0 || reply.match(",ok]") == -1) { 197 log.debug("ERROR during programming {}", reply); 198 // ECOS is not very informative about the precise nature of errors. 199 // We might guess that there is no loco present 200 notifyProgListenerEnd(-1, jmri.ProgListener.NoLocoDetected); 201 return; 202 } 203 // Get the CV value from the reply if reading 204 if (_progRead) { 205 // read was in progress - get return value 206 _val = GetEcosObjectNumber.getEcosObjectNumber(reply.toString(), ",", ",ok]"); 207 log.debug("read CV {} value: {}", _cv, _val); 208 } 209 210 // if this was a read, we cached the value earlier. If its a 211 // write, we're to return the original write value 212 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 213 214 } else { 215 log.debug("reply in un-decoded state"); 216 } 217 } 218 219 /** 220 * {@inheritDoc} 221 * 222 * Internal routine to handle a timeout. 223 */ 224 @Override 225 synchronized protected void timeout() { 226 if (progState != NOTPROGRAMMING) { 227 // we're programming, time to stop 228 if (log.isDebugEnabled()) { 229 log.debug("timeout!"); 230 } 231 // perhaps no loco present? Fail back to end of programming 232 progState = NOTPROGRAMMING; 233 EcosMessage m; 234 m = new EcosMessage("release("+ecosObject+",view)"); 235 tc.sendEcosMessage(m, this); 236 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 237 } 238 } 239 240 /** 241 * Internal method to notify of the final result. 242 * @param value Value transferred, particularly if a read operation 243 * @param status Status of completed operation 244 */ 245 protected void notifyProgListenerEnd(int value, int status) { 246 if (log.isDebugEnabled()) { 247 log.debug("notifyProgListenerEnd value {} status {}", value, status); 248 } 249 // the programmingOpReply handler might send an immediate reply, so 250 // clear the current listener _first_ 251 jmri.ProgListener temp = _usingProgrammer; 252 _usingProgrammer = null; 253 notifyProgListenerEnd(temp,value,status); 254 } 255 256 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EcosProgrammer.class); 257 258}