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}