001package jmri.jmrix.can.cbus;
002
003import javax.annotation.CheckForNull;
004import javax.annotation.Nonnull;
005import jmri.jmrix.can.CanMessage;
006import jmri.jmrix.can.CanReply;
007import jmri.jmrix.can.CanSystemConnectionMemo;
008import jmri.jmrix.can.TrafficController;
009import jmri.jmrix.can.cbus.node.CbusNodeTableDataModel;
010import jmri.util.swing.TextAreaFIFO;
011import jmri.util.ThreadingUtil;
012
013/**
014 * Class to send CAN Frames.
015 * <p>
016 * Auto adds CBUS priority.
017 * 
018 * @author Steve Young (C) 2019
019 */
020public class CbusSend {
021    
022    private TrafficController tc;
023    private final CanSystemConnectionMemo memo;
024    private final TextAreaFIFO ta;
025    private final String newLine = System.getProperty("line.separator");
026    
027    /**
028     * Constructor
029     * @param systemMemo System Connection
030     * @param txta a Text Area for any feedback messages
031     */
032    public CbusSend( @Nonnull CanSystemConnectionMemo systemMemo, TextAreaFIFO txta){
033        memo = systemMemo;
034        tc = memo.getTrafficController();
035        ta = txta;
036    }
037
038    /**
039     * Constructor
040     * @param systemMemo System Connection
041     */
042    public CbusSend(CanSystemConnectionMemo systemMemo){
043        memo = systemMemo;
044        if (memo!=null) {
045            tc = memo.getTrafficController();
046        }
047        ta = null;
048    }
049    
050    /**
051     * Sends an outgoing CanMessage or incoming CanReply from a CanReply with a specified delay
052     * @param r A CanReply Can Frame which will be sent
053     * @param sendReply true to send as incoming CcanReply
054     * @param sendMessage true to send as outgoing CanMessage
055     * @param delay delay in ms
056     */
057    public void sendWithDelay( CanReply r, Boolean sendReply, Boolean sendMessage, int delay ){
058        CbusMessage.setId(r, tc.getCanid() );
059        CbusMessage.setPri(r, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
060        ThreadingUtil.runOnLayoutDelayed( () -> {
061            if (sendReply) {
062                tc.sendCanReply(r, null);
063            }
064            if (sendMessage) {
065                CanMessage m = new CanMessage(r);
066                tc.sendCanMessage(m, null);
067            }
068        },delay );
069    }
070
071    /**
072     * Sends NNULN OPC , node exit learn mode
073     * @param nn Node Number
074     */
075    public void nodeExitLearnEvMode( int nn ) {
076        CanMessage m = new CanMessage(tc.getCanid());
077        m.setNumDataElements(3);
078        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
079        m.setElement(0, CbusConstants.CBUS_NNULN);
080        m.setElement(1, nn >> 8);
081        m.setElement(2, nn & 0xff);
082        tc.sendCanMessage(m, null);
083        if (ta != null){
084            ta.append(newLine + Bundle.getMessage("NdReqExitLearn", nn)); // future lookup from node table
085        }
086    }
087    
088    /**
089     * Sends NNLRN OPC , node enter learn mode.
090     * @param nn Node Number
091     */
092    public void nodeEnterLearnEvMode( int nn ) {
093        CanMessage m = new CanMessage(tc.getCanid());
094        m.setNumDataElements(3);
095        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
096        m.setElement(0, CbusConstants.CBUS_NNLRN);
097        m.setElement(1, nn >> 8);
098        m.setElement(2, nn & 0xff);
099        tc.sendCanMessage(m, null);
100        if (ta != null){
101            ta.append(newLine + Bundle.getMessage("NdReqEnterLearn", nn));
102        }
103    }
104    
105    /**
106     * Sends SNN OPC , Set Node Number to Node in Setup.
107     * @param nn Node Number
108     */
109    public void nodeSetNodeNumber( int nn ) {
110        CanMessage mn = new CanMessage(tc.getCanid());
111        mn.setNumDataElements(3);
112        CbusMessage.setPri(mn, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
113        mn.setElement(0, CbusConstants.CBUS_SNN);
114        mn.setElement(1, nn >> 8);
115        mn.setElement(2, nn & 0xff);
116        tc.sendCanMessage(mn, null);
117    }
118    
119    /**
120     * Sends RQNP OPC , Request Parameters from node in setup.
121     */
122    public void nodeRequestParamSetup() {
123        CanMessage m = new CanMessage(tc.getCanid());
124        m.setNumDataElements(1);
125        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
126        m.setElement(0, CbusConstants.CBUS_RQNP);
127        tc.sendCanMessage(m, null);
128    }
129
130    /**
131     * Sends EVLRN OPC , Event Learn
132     * when a node is in learn mode.
133     * @param newvalnd event variable node
134     * @param newevent event variable event
135     * @param varindex event variable index
136     * @param newval  event variable value
137     */
138    public void nodeTeachEventLearnMode(int newvalnd, int newevent, int varindex, int newval) {
139        CanMessage m = new CanMessage(tc.getCanid());
140        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
141        m.setNumDataElements(7);
142        m.setElement(0, CbusConstants.CBUS_EVLRN);
143        m.setElement(1, newvalnd >> 8);
144        m.setElement(2, newvalnd & 0xff);
145        m.setElement(3, newevent >> 8);
146        m.setElement(4, newevent & 0xff);
147        m.setElement(5, varindex);
148        m.setElement(6, newval);
149        tc.sendCanMessage(m, null);
150    }
151    
152    /**
153     * Sends EVULN OPC , Event Unlearn.
154     * when a node is in learn mode
155     * @param newvalnd event variable node
156     * @param newevent event variable event
157     */
158    public void nodeUnlearnEvent( int newvalnd, int newevent ){
159        CanMessage m = new CanMessage(tc.getCanid());
160        m.setNumDataElements(5);
161        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
162        m.setElement(0, CbusConstants.CBUS_EVULN);
163        m.setElement(1, newvalnd >> 8);
164        m.setElement(2, newvalnd & 0xff);
165        m.setElement(3, newevent >> 8);
166        m.setElement(4, newevent & 0xff);
167        tc.sendCanMessage(m, null);
168    }
169    
170    /**
171     * Sends REVAL OPC , Request for read of an event variable.
172     * @param nodeinsetup Node Number
173     * @param nextev event index number
174     * @param nextevvar event variable number
175     */
176    public void rEVAL( int nodeinsetup, int nextev, int nextevvar ){
177        CanMessage m = new CanMessage(tc.getCanid());
178        m.setNumDataElements(5);
179        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
180        m.setElement(0, CbusConstants.CBUS_REVAL);
181        m.setElement(1, nodeinsetup >> 8);
182        m.setElement(2, nodeinsetup & 0xff);
183        m.setElement(3, nextev);
184        m.setElement(4, nextevvar);
185        tc.sendCanMessage(m, null);
186    }    
187
188    /**
189     * Sends RQNPN OPC , Request read of a node parameter by index.
190     * @param nodeinsetup Node Number
191     * @param nextnodeparam parameter index number
192     */
193    public void rQNPN( int nodeinsetup, int nextnodeparam ) {
194        CanMessage m = new CanMessage(tc.getCanid());
195        m.setNumDataElements(4);
196        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
197        m.setElement(0, CbusConstants.CBUS_RQNPN);
198        m.setElement(1, nodeinsetup >> 8);
199        m.setElement(2, nodeinsetup & 0xff);
200        m.setElement(3, nextnodeparam); // 0 gets total parameters for this module
201        tc.sendCanMessage(m, null);
202    }
203
204    /**
205     * Sends CanMessage QNN OPC to get all nodes.
206     */
207     public void searchForNodes() {
208        CanMessage m = new CanMessage(tc.getCanid());
209        m.setNumDataElements(1);
210        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
211        m.setElement(0, CbusConstants.CBUS_QNN);
212        tc.sendCanMessage(m, null);
213    }
214    
215    /**
216     * Sends an RSTAT message to request details of any connected command stations.
217     * Responses are received by the CBUS node table.
218     */
219    public void searchForCommandStations(){
220        CanMessage m = new CanMessage(tc.getCanid());
221        m.setNumDataElements(1);
222        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
223        m.setElement(0, CbusConstants.CBUS_RSTAT);
224        tc.sendCanMessage(m, null);
225    }
226    
227    /**
228     * Sends NVRD OPC , Request read of a node variable.
229     * @param nodeinsetup Node Number
230     * @param nextnodenv variable number
231     */
232    public void nVRD( int nodeinsetup, int nextnodenv ) {
233        CanMessage m = new CanMessage(tc.getCanid());
234        m.setNumDataElements(4);
235        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
236        m.setElement(0, CbusConstants.CBUS_NVRD);
237        m.setElement(1, nodeinsetup >> 8);
238        m.setElement(2, nodeinsetup & 0xff);
239        m.setElement(3, nextnodenv); // get total parameters for this module
240        tc.sendCanMessage(m, null);
241    }
242
243    /**
244     * Sends NVSET OPC , Node set individual NV.
245     * If (contrary to CBUS spec), the node is required to be in Event Learn Mode
246     * before setting a NV, this will be done within this function,
247     * assuming that the node is visible to the memo CbusNodeTableDataModel .
248     * @param nodeinsetup Node Number
249     * @param nv Node variable number
250     * @param newval Node variable number value
251     */
252    public void nVSET(int nodeinsetup,int nv,int newval ) {
253
254        if ( getNvWriteLearnMode(nodeinsetup) ) {
255            nodeEnterLearnEvMode(nodeinsetup);
256        }
257
258        CanMessage m = new CanMessage(tc.getCanid());
259        m.setNumDataElements(5);
260        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
261        m.setElement(0, CbusConstants.CBUS_NVSET);
262        m.setElement(1, nodeinsetup >> 8);
263        m.setElement(2, nodeinsetup & 0xff);
264        m.setElement(3, nv);
265        m.setElement(4, newval);
266        tc.sendCanMessage(m, null);
267
268        if ( getNvWriteLearnMode(nodeinsetup) ) {
269            nodeExitLearnEvMode(nodeinsetup);
270        }
271    }
272
273    /**
274     * Test if Node needs to be in learn mode for teaching NVs.
275     * @param nodeinsetup node number
276     * @return if required, else false.
277     */
278    private boolean getNvWriteLearnMode(int nodeinsetup) {
279        CbusNodeTableDataModel model = getNodeModel();
280        if ( model !=null ) {
281            jmri.jmrix.can.cbus.node.CbusNode nd = model.getNodeByNodeNum(nodeinsetup);
282            if ( nd !=null ) {
283                return nd.getnvWriteInLearnOnly();
284            }
285        }
286        return false;
287    }
288
289    @CheckForNull
290    private CbusNodeTableDataModel getNodeModel(){
291        if ( memo != null ) {
292            return memo.getFromMap(CbusNodeTableDataModel.class);
293        }
294        return null;
295    }
296
297    /**
298     * Sends RQEVN OPC , Read number of stored events in node.
299     * <p>
300     * nb, NOT max events capable
301     * @param nodeinsetup Node Number
302     */
303    public void rQEVN( int nodeinsetup ) {
304        CanMessage m = new CanMessage(tc.getCanid());
305        m.setNumDataElements(3);
306        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
307        m.setElement(0, CbusConstants.CBUS_RQEVN);
308        m.setElement(1, nodeinsetup >> 8);
309        m.setElement(2, nodeinsetup & 0xff);
310        tc.sendCanMessage(m, null);
311    }
312
313    /**
314     * Sends NERD OPC , Request to read all node events.
315     * @param nodeinsetup Node Number
316     */
317    public void nERD(int nodeinsetup ){
318        CanMessage m = new CanMessage(tc.getCanid());
319        m.setNumDataElements(3);
320        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
321        m.setElement(0, CbusConstants.CBUS_NERD);
322        m.setElement(1, nodeinsetup >> 8);
323        m.setElement(2, nodeinsetup & 0xff);
324        tc.sendCanMessage(m, null);  
325    }
326
327    /**
328     * Sends a System Reset ARST OPC.
329     * Full system reset
330     */
331    public void aRST(){
332        CanMessage m = new CanMessage(tc.getCanid());
333        m.setNumDataElements(1);
334        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
335        m.setElement(0, CbusConstants.CBUS_ARST);
336        tc.sendCanMessage(m, null);  
337    }
338
339    /**
340     * Sends ENUM OPC , Force a self enumeration cycle for use with CAN.
341     * @param nodeinsetup Node Number
342     */
343    public void eNUM(int nodeinsetup ){
344        CanMessage m = new CanMessage(tc.getCanid());
345        m.setNumDataElements(3);
346        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
347        m.setElement(0, CbusConstants.CBUS_ENUM);
348        m.setElement(1, nodeinsetup >> 8);
349        m.setElement(2, nodeinsetup & 0xff);
350        tc.sendCanMessage(m, null);  
351    }
352
353    /**
354     * Sends CANID OPC , Teach node a specific CANID.
355     * @param nodeinsetup Node Number
356     * @param canid new CAN ID ( min 1, max 99 )
357     */
358    public void cANID(int nodeinsetup, int canid ){
359        CanMessage m = new CanMessage(tc.getCanid());
360        m.setNumDataElements(4);
361        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
362        m.setElement(0, CbusConstants.CBUS_CANID);
363        m.setElement(1, nodeinsetup >> 8);
364        m.setElement(2, nodeinsetup & 0xff);
365        m.setElement(3, canid & 0xff);
366        tc.sendCanMessage(m, null);  
367    }
368    
369    
370    /**
371     * Sends NNCLR OPC , Clear all events from a node.
372     * <p>
373     * Node must be in Learn Mode to take effect
374     * @param nodeinsetup Node Number
375     *
376     */
377    public void nNCLR(int nodeinsetup ){
378        CanMessage m = new CanMessage(tc.getCanid());
379        m.setNumDataElements(3);
380        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
381        m.setElement(0, CbusConstants.CBUS_NNCLR);
382        m.setElement(1, nodeinsetup >> 8);
383        m.setElement(2, nodeinsetup & 0xff);
384        tc.sendCanMessage(m, null);  
385    }
386    
387    /**
388     * Sends RQMN OPC , Request name from node.
389     * <p>
390     * Node must be in Setup Mode to take effect
391     */
392    public void rQmn(){
393        CanMessage m = new CanMessage(tc.getCanid());
394        m.setNumDataElements(1);
395        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
396        m.setElement(0, CbusConstants.CBUS_RQMN);
397        tc.sendCanMessage(m, null);  
398    }
399    
400    // private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusSend.class);
401}