001package jmri.jmrix.zimo; 002 003import java.util.ArrayList; 004import java.util.List; 005import javax.annotation.Nonnull; 006 007import jmri.ProgrammingMode; 008import jmri.jmrix.AbstractProgrammer; 009 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Programmer support for Zimo Mx-1. Currently paged mode is implemented. 015 * <p> 016 * The read operation state sequence is: 017 * <ul> 018 * <li>Reset Mx-1 019 * <li>Send paged mode read/write request 020 * <li>Wait for results reply, interpret 021 * <li>Send Resume Operations request 022 * <li>Wait for Normal Operations Resumed broadcast 023 * </ul> 024 * 025 * @author Bob Jacobsen Copyright (c) 2002 026 * 027 * Adapted by Sip Bosch for use with zimo Mx-1 028 * 029 */ 030public class Mx1Programmer extends AbstractProgrammer implements Mx1Listener { 031 032 protected Mx1TrafficController tc; 033 034 protected Mx1Programmer(Mx1TrafficController _tc) { 035 this.tc = _tc; 036 SHORT_TIMEOUT = 4000; // length default timeout 037 // connect to listen 038 log.info("Mx1TrafficController: {}", this.tc); 039 if(this.tc!=null) 040 this.tc.addMx1Listener(~0, this); 041 } 042 043 /** 044 * {@inheritDoc} 045 * 046 * Types implemented here. 047 */ 048 @Override 049 @Nonnull 050 public List<ProgrammingMode> getSupportedModes() { 051 List<ProgrammingMode> ret = new ArrayList<ProgrammingMode>(); 052 ret.add(ProgrammingMode.PAGEMODE); 053 return ret; 054 } 055 056 // members for handling the programmer interface 057 int progState = 0; 058 boolean firstTime = true; 059 static final int NOTPROGRAMMING = 0; // is notProgramming 060 static final int INQUIRESENT = 2; // read/write command sent, waiting reply 061 boolean _progRead = false; 062 int _val; // remember the value being read/written for confirmative reply 063 int _cv; // remember the cv being read/written 064 065 /** 066 * {@inheritDoc} 067 */ 068 @Override 069 synchronized public void writeCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 070 final int CV = Integer.parseInt(CVname); 071 if (log.isDebugEnabled()) { 072 log.debug("writeCV {} listens {}", CV, p); 073 } 074 useProgrammer(p); 075 _progRead = false; 076 // set new state & save values 077 progState = INQUIRESENT; 078 _val = val; 079 _cv = CV; 080 // start the error timer 081 startShortTimer(); 082 // format and send message to go to program mode 083 if (getMode() == ProgrammingMode.PAGEMODE) { 084 if (tc.getProtocol() == Mx1Packetizer.ASCII) { 085 if (firstTime) { 086 tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), this); 087 firstTime = false; 088 } 089 tc.sendMx1Message(tc.getCommandStation().getWritePagedCVMsg(CV, val), this); 090 } else { 091 tc.sendMx1Message(Mx1Message.getDecProgCmd(0, _cv, val, true), this); 092 } 093 } 094 } 095 096 /** 097 * {@inheritDoc} 098 */ 099 @Override 100 public void confirmCV(String CV, int val, jmri.ProgListener p) throws jmri.ProgrammerException { 101 readCV(CV, p); 102 } 103 104 /** 105 * {@inheritDoc} 106 */ 107 @Override 108 synchronized public void readCV(String CVname, jmri.ProgListener p) throws jmri.ProgrammerException { 109 final int CV = Integer.parseInt(CVname); 110 if (log.isDebugEnabled()) { 111 log.debug("readCV {} listens {}", CV, p); 112 } 113 useProgrammer(p); 114 _progRead = true; 115 // set new state 116 progState = INQUIRESENT; 117 _cv = CV; 118 // start the error timer 119 startShortTimer(); 120 // format and send message to go to program mode 121 if (getMode() == ProgrammingMode.PAGEMODE) { 122 if (tc.getProtocol() == Mx1Packetizer.ASCII) { 123 if (firstTime) { 124 tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), this); 125 firstTime = false; 126 } 127 tc.sendMx1Message(tc.getCommandStation().getReadPagedCVMsg(CV), this); 128 } else { 129 tc.sendMx1Message(Mx1Message.getDecProgCmd(0, _cv, -1, true), this); 130 } 131 } 132 } 133 134 private jmri.ProgListener _usingProgrammer = null; 135 136 // internal method to remember who's using the programmer 137 protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException { 138 // test for only one! 139 if (_usingProgrammer != null && _usingProgrammer != p) { 140 if (log.isInfoEnabled()) { 141 log.info("programmer already in use by {}", _usingProgrammer); 142 } 143 throw new jmri.ProgrammerException("programmer in use"); 144 } else { 145 _usingProgrammer = p; 146 return; 147 } 148 } 149 150 /** 151 * {@inheritDoc} 152 */ 153 @Override 154 synchronized public void message(Mx1Message m) { 155 if (progState == NOTPROGRAMMING) { 156 // we get the complete set of replies now, so ignore these 157 return; 158 } else if (progState == INQUIRESENT) { 159 if (log.isDebugEnabled()) { 160 log.debug("reply in INQUIRESENT state"); 161 } 162 if (tc.getProtocol() == Mx1Packetizer.ASCII) { 163 //check for right message, else return 164 if (m.getElement(0) == 0x51 && m.getElement(1) == 0x4E 165 && m.getElement(2) == 0x30 && m.getElement(3) == 0x30) { 166 // valid operation response 167 // see why waiting 168 if (_progRead) { 169 // read was in progress - get return value 170 // convert asci into ebcdic 171 int highVal = ascToBcd(m.getElement(6)); 172 highVal = highVal * 16 & 0xF0; 173 int lowVal = ascToBcd(m.getElement(7)); 174 _val = (highVal | lowVal); 175 } 176 progState = NOTPROGRAMMING; 177 stopTimer(); 178 // if this was a read, we cached the value earlier. If its a 179 // write, we're to return the original write value 180 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 181 tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), this); 182 return; 183 // faulty message 184 } else { 185 progState = NOTPROGRAMMING; 186 stopTimer(); 187 tc.sendMx1Message(tc 188 .getCommandStation().resetModeMsg(), this); 189 notifyProgListenerEnd(_val, jmri.ProgListener.NoLocoDetected); 190 return; 191 } 192 } else { 193 if (m.getPrimaryMessage() == Mx1Message.PROGCMD && m.getMessageType() == Mx1Message.REPLY2) { 194 if (_progRead) { 195 _val = m.getCvValue(); 196 } 197 progState = NOTPROGRAMMING; 198 stopTimer(); 199 // if this was a read, we cached the value earlier. If its a 200 // write, we're to return the original write value 201 notifyProgListenerEnd(_val, jmri.ProgListener.OK); 202 /*tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), this);*/ 203 return; 204 } 205 } 206 } 207 } 208 209 /** 210 * {@inheritDoc} 211 * 212 * Internal routine to handle a timeout 213 */ 214 @Override 215 synchronized protected void timeout() { 216 if (progState != NOTPROGRAMMING) { 217 // we're programming, time to stop 218 if (log.isDebugEnabled()) { 219 log.debug("timeout!"); 220 } 221 // perhaps no loco present? Fail back to end of programming 222 progState = NOTPROGRAMMING; 223 if (tc.getProtocol() == Mx1Packetizer.ASCII) { 224 tc.sendMx1Message(tc.getCommandStation().resetModeMsg(), 225 this); 226 } 227 notifyProgListenerEnd(_val, jmri.ProgListener.FailedTimeout); 228 } 229 } 230 231 // internal method to notify of the final result 232 protected void notifyProgListenerEnd(int value, int status) { 233 if (log.isDebugEnabled()) { 234 log.debug("notifyProgListenerEnd value {} status {}", value, status); 235 } 236 // the programmingOpReply handler might send an immediate reply, so 237 // clear the current listener _first_ 238 jmri.ProgListener temp = _usingProgrammer; 239 _usingProgrammer = null; 240 notifyProgListenerEnd(temp, value, status); 241 } 242 243 public int ascToBcd(int hex) { 244 switch (hex) { 245 case 0x46: 246 return 0x0F; 247 case 0x45: 248 return 0x0E; 249 case 0x65: 250 return 0x0E; 251 case 0x44: 252 return 0x0D; 253 case 0x43: 254 return 0x0C; 255 case 0x42: 256 return 0x0B; 257 case 0x41: 258 return 0x0A; 259 case 0x39: 260 return 0x09; 261 case 0x38: 262 return 0x08; 263 case 0x37: 264 return 0x07; 265 case 0x36: 266 return 0x06; 267 case 0x35: 268 return 0x05; 269 case 0x34: 270 return 0x04; 271 case 0x33: 272 return 0x03; 273 case 0x32: 274 return 0x02; 275 case 0x31: 276 return 0x01; 277 default: 278 return 0x00; 279 } 280 } 281 282 private final static Logger log = LoggerFactory.getLogger(Mx1Programmer.class); 283 284}