001package jmri.jmrix.bidib;
002
003import java.util.ArrayList;
004import java.util.List;
005import javax.annotation.Nonnull;
006
007import jmri.AddressedProgrammer;
008import jmri.ProgListener;
009import jmri.ProgrammerException;
010import jmri.ProgrammingMode;
011
012import org.bidib.jbidibc.messages.BidibLibrary; //new
013import org.bidib.jbidibc.messages.exception.ProtocolException; //new
014import org.bidib.jbidibc.messages.Node;
015import org.bidib.jbidibc.messages.AddressData;
016import org.bidib.jbidibc.messages.PomAddressData;
017import org.bidib.jbidibc.core.DefaultMessageListener;
018import org.bidib.jbidibc.core.MessageListener;
019import org.bidib.jbidibc.messages.enums.PomAddressTypeEnum;
020import org.bidib.jbidibc.messages.enums.CommandStationPom;
021import org.bidib.jbidibc.messages.enums.PomAcknowledge;
022import org.bidib.jbidibc.core.node.CommandStationNode;
023
024/**
025 * Provides an Ops mode programming interface for BiDiB Currently only Byte
026 * mode is implemented, though BiDiB also supports bit mode writes for POM
027 *
028 * @see jmri.Programmer
029 * @author Paul Bender Copyright (C) 2003-2010
030 * @author Eckart Meyer Copyright (C) 2019-2020
031 */
032public class BiDiBOpsModeProgrammer extends jmri.jmrix.AbstractProgrammer implements AddressedProgrammer {
033
034    protected int mAddress;
035    protected int progState = NOTPROGRAMMING;
036    protected int value;
037    protected int cv;
038    protected jmri.ProgListener progListener = null;
039  
040    // possible states.
041    static protected final int NOTPROGRAMMING = 0; // is notProgramming
042    static protected final int READREQUEST = 1; // read command sent, waiting for ack and reply
043    static protected final int WRITEREQUEST = 2; // read command sent, waiting for ack
044
045    protected BiDiBTrafficController tc = null;
046    MessageListener messageListener = null;
047    protected Node node = null;
048
049    public BiDiBOpsModeProgrammer(int pAddress, BiDiBTrafficController controller) {
050        tc = controller;
051        node = tc.getFirstCommandStationNode();
052        if (log.isDebugEnabled()) {
053            log.debug("Creating Ops Mode Programmer for Address {}", pAddress);
054        }
055        mAddress = pAddress;
056        // register as a listener
057        createOpsModeProgrammerListener();
058    }
059
060    /** 
061     * {@inheritDoc}
062     *
063     * Send an ops-mode write request to BiDiB.
064     */
065    @Override
066    synchronized public void writeCV(String CVname, int val, ProgListener p) throws ProgrammerException {
067        final int CV = Integer.parseInt(CVname);
068        log.info("write ops mode: {}, CV={}, val={}", getMode().getStandardName(), CV, val);
069        /* we need to save the programer and value so we can send messages 
070         back to the screen when the programming screen when we receive
071         something from the command station */
072        progListener = p;
073        cv = CV;
074        value = val;
075        progState = WRITEREQUEST;
076        PomAddressData decoderAddress = new PomAddressData(mAddress, PomAddressTypeEnum.LOCOMOTIVE);
077        // start the error timer
078        restartTimer(5000);
079        tc.addMessageListener(messageListener);   
080//TODO bit mode ??
081//XPom mode??
082
083// specialized BidibNode variant
084        CommandStationNode csNode = tc.getBidib().getCommandStationNode(node);
085        log.debug("node: {}, csNode: {}", node, csNode);
086        tc.checkProgMode(false, node); //request switch off progmode (PT or GlobalProgrammer!) if it is on
087
088// async operation - start write
089        try {
090            log.trace("start CS_POM asynchroneously, write value: {}", value);
091            csNode.writePom(false, decoderAddress, CommandStationPom.WR_BYTE, cv, value);
092        }
093        catch (ProtocolException ex) {
094            log.error("writePom async failed on node: {}, addr: {} - ", node, decoderAddress, ex);
095            progState = NOTPROGRAMMING;
096            notifyProgListenerEnd(p, 0, PomAcknowledge.NOT_ACKNOWLEDGED);
097        }
098        
099
100//// waits for acknowledge synchroneously
101//        try {
102//            tc.checkProgMode(false, node); //request switch off progmode (PT or GlobalProgrammer!) if it is on
103//            
104//            PomAcknowledge result = csNode.writePom(decoderAddress, CommandStationPom.WR_BYTE, cv, value);
105//            
106//            log.debug("writePom result: {}", result);
107//            if (result == null  ||  result == PomAcknowledge.NOT_ACKNOWLEDGED) {
108//                log.warn("writePom was not acknowledged on node: {}, addr: {}");
109//            }
110//            notifyProgListenerEnd(p,CV, result);
111//        }
112//        catch (ProtocolException ex) {
113//            log.error("writePom failed on node: {}, addr: {} - {}", node, decoderAddress, ex);
114//            notifyProgListenerEnd(p, value, ProgListener.CommError);
115//        }
116//        progState = NOTPROGRAMMING;
117
118    }
119    
120    /** 
121     * {@inheritDoc}
122     */
123    @Override
124    synchronized public void readCV(String CVname, ProgListener p) throws ProgrammerException {
125        final int CV = Integer.parseInt(CVname);
126        log.info("read ops mode: {}, CV={}", getMode().getStandardName(), CV);
127        progListener = p;
128        progState = READREQUEST;
129        cv = CV;
130        value = 42;//preset...
131        PomAddressData decoderAddress = new PomAddressData(mAddress, PomAddressTypeEnum.LOCOMOTIVE);
132        restartTimer(5000);
133        tc.addMessageListener(messageListener);        
134//TODO bit mode ??
135//XPom mode??
136
137// specialized BidibNode variant
138        CommandStationNode csNode = tc.getBidib().getCommandStationNode(node);
139        tc.checkProgMode(false, node); //request switch off progmode (PT or GlobalProgrammer!) if it is on
140
141
142// async operation - start read
143        try {
144            log.trace("start CS_POM asynchroneously");
145            csNode.readPom(false, decoderAddress, CommandStationPom.RD_BYTE, cv);
146        }
147        catch (ProtocolException ex) {
148            log.error("readPom async failed on node: {}, addr: {} - ", node, decoderAddress, ex);
149            progState = NOTPROGRAMMING;
150            notifyProgListenerEnd(p, 0, PomAcknowledge.NOT_ACKNOWLEDGED);
151        }
152        
153//// waits for acknowledge synchroneously
154//        try {
155//            tc.checkProgMode(false, node); //request switch off progmode (PT or GlobalProgrammer!) if it is on
156//            
157//            PomAcknowledge result = csNode.readPom(decoderAddress, CommandStationPom.RD_BYTE, cv);
158//            
159//            log.debug("readPom result: {}", result);
160//            if (result == null  ||  result == PomAcknowledge.NOT_ACKNOWLEDGED) {
161//                log.warn("readPom was not acknowledged on node: {}, addr: {}");
162//                progState = NOTPROGRAMMING;
163//                notifyProgListenerEnd(p,CV, result);
164//            }
165//        }
166//        catch (ProtocolException ex) {
167//            log.error("readPom failed on node: {}, addr: {} - {}", node, decoderAddress, ex);
168//            progState = NOTPROGRAMMING;
169//            notifyProgListenerEnd(p, value, ProgListener.CommError);
170//        }
171        log.trace("Return from readCV");
172    }
173
174    /** 
175     * {@inheritDoc}
176     */
177    @Override
178    public void confirmCV(String CVname, int val, ProgListener p) throws ProgrammerException {
179        int CV = Integer.parseInt(CVname);
180        log.info("confirmCV ops mode: {}, CV={}", getMode().getStandardName(), CV);
181        readCV(CVname, p);
182    }
183
184    public void notifyProgListenerEnd(ProgListener p, int value, PomAcknowledge result) {
185        if (log.isDebugEnabled()) {
186            log.debug("notifyProgListenerEnd value {}", value);
187        }
188        stopTimer();
189        tc.removeMessageListener(messageListener);        
190        progState = NOTPROGRAMMING;
191        try {
192            Thread.sleep(100);
193        }
194        catch (InterruptedException e) {
195            
196        }
197        if (result == PomAcknowledge.NOT_ACKNOWLEDGED) {
198            notifyProgListenerEnd(p, value, ProgListener.NoAck);
199        }
200        else {
201            notifyProgListenerEnd(p, value, ProgListener.OK);
202        }
203        //tc.removeMessageListener(messageListener);        
204        //progState = NOTPROGRAMMING;
205    }
206
207    /** 
208     * {@inheritDoc}
209     *
210     * Types implemented here.
211     */
212    @Override
213    @Nonnull
214    public List<ProgrammingMode> getSupportedModes() {
215        List<ProgrammingMode> ret = new ArrayList<>();
216        ret.add(ProgrammingMode.OPSBYTEMODE);
217        // BiDiB can use all of the following modes, but I'm not sure that JMRI does it right...
218//        ret.add(ProgrammingMode.OPSBITMODE);
219//        ret.add(ProgrammingMode.OPSACCBITMODE);
220//        ret.add(ProgrammingMode.OPSACCBYTEMODE);
221//        ret.add(ProgrammingMode.OPSACCEXTBITMODE);
222//        ret.add(ProgrammingMode.OPSACCEXTBYTEMODE);
223        return ret;
224    }
225
226    /** 
227     * {@inheritDoc}
228     *
229     * Can this ops-mode programmer read back values?
230     *
231     * @return true to allow us to trigger an ops mode read
232     */
233    @Override
234    public boolean getCanRead() {
235        log.debug("canRead");
236        //if (tc.getNodeFeature(node, BidibLibrary.FEATURE_BM_CV_AVAILABLE) != 0) {
237        if (tc.getNodeFeature(node, BidibLibrary.FEATURE_BM_CV_ON) != 0) {
238            return true;
239        }
240        else {
241            //return false;
242            return true;
243        }
244    }
245
246    /** {@inheritDoc} 
247     * Checks using the current default programming mode
248     */
249    @Override
250    public boolean getCanRead(String addr) {
251        log.debug("canRead addr: {}", addr);
252        if (!getCanRead()) {
253            return false; // check basic implementation first
254        }
255        //return Integer.parseInt(addr) <= 1024; //????
256        return true; //TODO validate CV address, depends on the mode
257    }
258
259
260
261    /** 
262     * {@inheritDoc}
263     */
264    @Override
265    public boolean getLongAddress() {
266        return true;
267    }
268
269    /** 
270     * {@inheritDoc}
271     */
272    @Override
273    public int getAddressNumber() {
274        return mAddress;
275    }
276
277    /** 
278     * {@inheritDoc}
279     */
280    @Override
281    public String getAddress() {
282        return "" + getAddressNumber() + " " + getLongAddress();
283    }
284
285
286    /** 
287     * {@inheritDoc}
288     */
289    @Override
290    synchronized protected void timeout() {
291        log.trace("** timeout **");
292        if (progState != NOTPROGRAMMING) {
293            // we're programming, time to stop
294            if (log.isDebugEnabled()) {
295                log.debug("timeout!");
296            }
297            // perhaps no loco present? Fail back to end of programming
298            progState = NOTPROGRAMMING;
299            if (getCanRead()) {
300               notifyProgListenerEnd(progListener,value,jmri.ProgListener.FailedTimeout);
301            } else {
302               notifyProgListenerEnd(progListener,value,jmri.ProgListener.OK);
303            }
304        }
305        tc.removeMessageListener(messageListener);        
306    }
307    
308    private void createOpsModeProgrammerListener() {
309        // to listen to messages related to POM
310        messageListener = new DefaultMessageListener() {
311            @Override
312            public void csPomAcknowledge(byte[] address, int messageNum, PomAddressData addressData, PomAcknowledge state) {
313                log.trace("csPomAcknowledge");
314                if (progState != NOTPROGRAMMING) {
315                    log.debug("loco addr: {}, msg loco addr: {}", mAddress, addressData.getAddress());
316                    if (mAddress ==  addressData.getAddress()) {
317                        log.info("OPS PROGRAMMER CS_POM_ACC was signalled, node addr: {}, decoderAddress: {} {}, state: {}",
318                                address, addressData.getAddress(), addressData.getType(), state);
319                    }
320                    if (state == PomAcknowledge.NOT_ACKNOWLEDGED) {
321                        log.warn("readPom was not acknowledged on node addr: {}, loco addr: {}", addressData.getAddress(), addressData.getAddress());
322                        stopTimer();
323                        progState = NOTPROGRAMMING;
324                        tc.removeMessageListener(messageListener);        
325                        //notifyProgListenerEnd(progListener, 0, ProgListener.NoAck);
326                        notifyProgListenerEnd(progListener, 0, PomAcknowledge.NOT_ACKNOWLEDGED);
327                    }
328                    else if (progState == WRITEREQUEST) {
329                        log.debug("writePom finished - value: {}", value);
330                        stopTimer();
331                        progState = NOTPROGRAMMING;
332                        tc.removeMessageListener(messageListener);        
333                        notifyProgListenerEnd(progListener, value, PomAcknowledge.ACKNOWLEDGED);
334                    }
335                }
336                log.trace("return from csPomAcknowledge");
337            }
338            @Override
339            public void feedbackCv(byte[] address, int messageNum, PomAddressData decoderAddress, int cvNumber, int dat) {
340                log.trace("feedbackCv");
341                if (progState != NOTPROGRAMMING) {
342                    //log.debug("node addr: {}, msg node addr: {}", node.getAddr(), address);
343                    log.debug("loco addr: {}, msg loco addr: {}", mAddress, decoderAddress.getAddress());
344                    //if (NodeUtils.isAddressEqual(node.getAddr(), address)  &&  cv == cvNumber) {
345                    if (mAddress ==  decoderAddress.getAddress()  &&  cv == cvNumber) {
346                        stopTimer();
347                        progState = NOTPROGRAMMING;
348                        tc.removeMessageListener(messageListener);        
349                        log.info("OPS PROGRAMMER BM_CV was signalled, node addr: {}, decoderAddress: {} {}, CV: {}, value: {}",
350                                address, decoderAddress.getAddress(), decoderAddress.getType(), cvNumber, dat);
351                        value = dat;
352                        //notifyProgListenerEnd(progListener, value, jmri.ProgListener.OK);
353                        notifyProgListenerEnd(progListener, value, PomAcknowledge.ACKNOWLEDGED);
354                    }
355                }
356                else {
357                    progState = NOTPROGRAMMING;
358                    tc.removeMessageListener(messageListener);        
359                }
360                log.trace("return from feedbackCv");
361            }
362            @Override
363            public void feedbackXPom(byte[] address, int messageNum, AddressData decoderAddress, int cvNumber, int[] data) {
364                log.trace("feedbackXPom");
365                if (progState != NOTPROGRAMMING) {
366                    //log.debug("node addr: {}, msg node addr: {}", node.getAddr(), address);
367                    log.debug("loco addr: {}, msg loco addr: {}", mAddress, decoderAddress.getAddress());
368                    //if (NodeUtils.isAddressEqual(node.getAddr(), address)  &&  cv == cvNumber) {
369                    if (mAddress ==  decoderAddress.getAddress()  &&  cv == cvNumber) {
370                        stopTimer();
371                        progState = NOTPROGRAMMING;
372                        tc.removeMessageListener(messageListener);        
373                        log.info("OPS PROGRAMMER BM_XCOM was signalled, node addr: {}, decoderAddress: {} {}, CV: {}, values: {}",
374                                address, decoderAddress.getAddress(), decoderAddress.getType(), cvNumber, data);
375                        value = data[0]; //????
376                        //notifyProgListenerEnd(progListener, value, jmri.ProgListener.OK);
377                        notifyProgListenerEnd(progListener, value, PomAcknowledge.ACKNOWLEDGED);
378                    }
379                }
380                else {
381                    progState = NOTPROGRAMMING;
382                    tc.removeMessageListener(messageListener);        
383                }
384            }
385        };
386        //tc.getBidib().getMessageReceiver().addMessageListener(messageListener);        
387    }
388    
389//    // dispose is not defined in superclass...
390//    //@Override
391//    public void dispose() {
392//        if (messageListener != null) {
393//            tc.removeMessageListener(messageListener);
394//            messageListener = null;
395//        }
396//        //super.dispose();
397//    }
398
399
400    // initialize logging
401    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BiDiBOpsModeProgrammer.class);
402
403}