001package jmri.jmrix.can.cbus.node;
002
003import static jmri.jmrix.can.cbus.node.CbusNodeConstants.*;
004
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Class to represent a Processing of CAN Frames for a CbusNode.
010 *
011 * @author Steve Young Copyright (C) 2019,2020
012 */
013public class CbusNodeParameterManager {
014    private final CbusBasicNodeWithManagers _node;
015    private int[] _parameters;
016    private boolean _commandStationIdentified;
017    private boolean _nodeTraitsSet;
018
019    /**
020     * Create a new CbusNodeCanListener
021     *
022     * @param node The Node
023     */
024    public CbusNodeParameterManager ( CbusBasicNodeWithManagers node ){
025        _node = node;
026        _parameters = null;
027        _commandStationIdentified = false;
028        _nodeTraitsSet = false;
029    }
030
031    /**
032     * Set Node Parameters.
033     * <p>
034     * Para 0 Number of parameters
035     * <p>
036     * Para 1 The manufacturer ID
037     * <p>
038     * Para 2 Minor code version as an alphabetic character (ASCII)
039     * <p>
040     * Para 3 Manufacturer module identifier as a HEX numeric
041     * <p>
042     * Para 4 Number of supported events as a HEX numeric
043     * <p>
044     * Para 5 Number of Event Variables per event as a HEX numeric
045     * <p>
046     * Para 6 Number of supported Node Variables as a HEX numeric
047     * <p>
048     * Para 7 Major version
049     * <p>
050     * Para 8 Node flags
051     * <p>
052     * Para 9 Processor type
053     * <p>
054     * Para 10 Bus type
055     * <p>
056     * Para 11-14 load address, 4 bytes
057     * <p>
058     * Para 15-18 CPU manufacturer's id as read from the chip config space, 4 bytes
059     * <p>
060     * Para 19 CPU manufacturer code
061     * <p>
062     * Para 20 Beta revision (numeric), or 0 if release
063     *
064     * @param newparams set the node parameters
065     *
066     */
067    public void setParameters( int[] newparams ) {
068
069        //  log.warn("new params {}",newparams);
070        _parameters = new int [(newparams[0]+1)];
071        for (int i = 0; i < _parameters.length; i++) {
072            setParameter(i,newparams[i]);
073        }
074
075        if ( getParameter(6) > -1 ) {
076            int [] myarray = new int[(getParameter(6)+1)]; // +1 to account for index 0 being the NV count
077            java.util.Arrays.fill(myarray, -1);
078            myarray[0] = getParameter(6);
079            _node.getNodeNvManager().setNVs(myarray);
080        }
081    }
082
083    /**
084     * Set a Single Node Parameter.
085     * Parameter array should be initialised before calling.
086     * Notifies PropertyChangeListener "PARAMETER"
087     *
088     * @param index Parameter Index,
089     * @param newval New Parameter Value, 0-255
090     */
091    public void setParameter( int index, int newval ) {
092        if ( _parameters == null ){
093            log.error("Parameter set before array initiaised");
094            return;
095        }
096        log.debug("set parameter tot:{} index:{} newval:{}",_parameters.length,index,newval);
097        if ( index <= _parameters.length ) {
098
099            _parameters[index] = newval;
100            _node.notifyPropertyChangeListener("PARAMETER", null, null);
101        }
102    }
103
104    /**
105     * Get Number of outstanding unknown Parameters to be fetched from a CbusNode
106     *
107     * @return Number of outstanding Parameters, else 8
108     */
109    public int getOutstandingParams(){
110
111        if (_parameters == null){
112            return 8; // CBUS Spec minimum 8 parameters, likely value 20
113        }
114
115        int count = 0;
116        for (int i = 1; i < _parameters.length; i++) {
117            if ( _parameters[i] == -1 ) {
118                count++;
119            }
120        }
121        return count;
122    }
123
124    /**
125     * Get a Single Parameter value
126     * <p>
127     * eg. for param. array [3,1,2,3] index 2 returns 2
128     * <p>
129     * Para 0 Number of parameters
130     * <p>
131     * Para 1 The manufacturer ID
132     * <p>
133     * Para 2 Minor code version as an alphabetic character (ASCII)
134     * <p>
135     * Para 3 Manufacturer module identifier as a HEX numeric
136     * <p>
137     * Para 4 Number of supported events as a HEX numeric
138     * <p>
139     * Para 5 Number of Event Variables per event as a HEX numeric
140     * <p>
141     * Para 6 Number of supported Node Variables as a HEX numeric
142     * <p>
143     * Para 7 Major version
144     * <p>
145     * Para 8 Node flags
146     * <p>
147     * Para 9 Processor type
148     * <p>
149     * Para 10 Bus type
150     * <p>
151     * Para 11-14 load address, 4 bytes
152     * <p>
153     * Para 15-18 CPU manufacturer's id as read from the chip config space, 4 bytes
154     * <p>
155     * Para 19 CPU manufacturer code
156     * <p>
157     * Para 20 Beta revision (numeric), or 0 if release
158     *
159     * @param index of which parameter, 0 gives the total parameters
160     * @return Full Parameter value for a particular index, -1 if unknown
161     */
162    public int getParameter(int index) {
163        if ( _parameters == null ) {
164            return -1;
165        }
166        try  {
167            return _parameters[index];
168        }
169        catch (java.lang.ArrayIndexOutOfBoundsException e) {
170            return -1;
171        }
172    }
173
174    /**
175     * Get array of All parameters
176     *
177     * @return Full Parameter array, index 0 is total parameters
178     */
179    public int[] getParameters() {
180        return _parameters;
181    }
182
183    /**
184     * Get the Parameter String in Hex Byte Format
185     * <p>
186     * eg. for param. array [3,1,2,3] returns "010203"
187     *
188     * @return Full Parameter String WITHOUT leading number of parameters
189     */
190    public String getParameterHexString() {
191        if (getParameters()==null) {
192            return "";
193        } else {
194            return jmri.util.StringUtil.hexStringFromInts(getParameters()).replaceAll("\\s","").substring(2);
195        }
196    }
197
198    protected void clearParameters() {
199        _parameters = null;
200        _nodeTraitsSet = false;
201    }
202
203    /**
204     * Get the Node Type
205     *
206     * @return eg. MERG Command Station CANCMD Firmware 4d Node 65534
207     */
208    public String getNodeTypeString(){
209        StringBuilder n = new StringBuilder(100);
210        n.append (CbusNodeConstants.getManu(getParameter(1)))
211        .append (" ")
212        .append( CbusNodeConstants.getModuleTypeExtra(getParameter(1),getParameter(3)))
213        .append(" ")
214        .append( CbusNodeConstants.getModuleType(getParameter(1),getParameter(3)))
215        .append (" ")
216        .append (Bundle.getMessage("FirmwareVer",getParameter(7),Character.toString((char) getParameter(2) )));
217        if ((getParameter(0)>19) && (getParameter(20)>0) ){
218            n.append (Bundle.getMessage("FWBeta"))
219            .append (getParameter(20))
220            .append (" ");
221        }
222        n.append (Bundle.getMessage("CbusNode"))
223        .append (_node.getNodeNumber());
224        return n.toString();
225    }
226
227    public void requestEventTot() {
228        if ( _node.getNodeTimerManager().hasActiveTimers() ){
229            return;
230        }
231        _node.getNodeTimerManager().setNumEvTimeout();
232        _node.send.rQEVN( _node.getNodeNumber() );
233    }
234
235    /**
236     * Request a single Parameter from a Physical Node
237     * <p>
238     * Will not send the request if there are existing active timers.
239     * Starts Parameter timeout
240     *
241     * @param param Parameter Index Number, Index 0 is total parameters
242     */
243    public void requestParam(int param){
244        if ( _node.getNodeTimerManager().hasActiveTimers() ){
245            return;
246        }
247        _node.getNodeTimerManager().setAllParamTimeout(param);
248        _node.send.rQNPN( _node.getNodeNumber(), param );
249    }
250
251    /**
252     * Check if current node firmware is equal to or newer than provided version
253     * 
254     * Note that CBUS firmware start with beta builds, followed by a release
255     * with the beta build set to 0, e.g.:
256     * major.minor Beta 1
257     * major.minor Beta 2
258     * ...
259     * major.minor Beta x
260     * major.minor Beta 0
261     * 
262     * @param major New FW major version
263     * @param minor New FW minor version
264     * @param beta  New FW beta build number
265     * @return      true if current node firmware is equal to or newer than provided version
266     */
267    public boolean isFwEqualOrNewer(int major, int minor, int beta) {
268        if (major > getParameter(MAJOR_VER_IDX)) {
269            return false;
270        } else if (major < getParameter(MAJOR_VER_IDX)) {
271            return true;
272        } else {
273            // Major ver is equal, test minor
274            if (minor > getParameter(MINOR_VER_IDX)) {
275                return false;
276            } else if (minor < getParameter(MINOR_VER_IDX)) {
277                return true;
278            } else {
279                // Major and minor are equal, test beta
280                if (beta == 0) {
281                    // Release is always newer than any beta
282                    return true;
283                } else if (beta > getParameter(BETA_REV_IDX)) {
284                    return false;
285                } else {
286                    return true;
287                }
288            }
289        }
290    }
291    
292    private boolean sentParamRequest(int paramToCheck){
293        if ( getParameter(paramToCheck) < 0 ) {
294            requestParam(paramToCheck);
295            return true;
296        }
297        return false;
298    }
299
300    /**
301     * Send a request for the next unknown parameter to the physical node
302     *
303     */
304    protected void sendRequestNextParam(){
305        if ( _parameters == null ) {
306            requestParam(0);
307            return;
308        }
309        if ( sentParamRequest(1)  // Manufacturer ID
310            || ( sentParamRequest(3) )  // Module ID
311            || ( sentParamRequest(6) ) ) { // initialise NV's
312            return;
313        }
314
315        if ( sentParamRequest(5) // get number event variables per event
316            || ( sentParamRequest(7) ) // get firmware pt1
317            || ( sentParamRequest(2) ) ) { // get firmware pt2
318            return;
319        }
320
321        finishedWhenGotMainParams();
322
323        for (int i = 1; i < _parameters.length; i++) {
324            if ( sentParamRequest(i) ) {
325                return;
326            }
327        }
328    }
329
330    private void finishedWhenGotMainParams(){
331
332        if (!( _node instanceof CbusNode)){
333            return;
334        }
335
336        if (( ( (CbusNode) _node).getCsNum() > -1 ) && ( _commandStationIdentified == false ) ) {
337            // notify command station located
338            log.info("Node type: {}",getNodeTypeString() );
339            _commandStationIdentified = true;
340        }
341
342        // set node traits, eg CANPAN v1 send wrack on nv set, CANCMD v4 numevents 0
343        // only do this once
344        if (!_nodeTraitsSet ) {
345            CbusNodeConstants.setTraits((CbusNode) _node);
346            _nodeTraitsSet = true;
347        }
348
349        // now traits are known request num. of events
350        if ( _node.getNodeEventManager().getTotalNodeEvents()<0 ){
351            requestEventTot();
352        }
353    }
354
355    private static final Logger log = LoggerFactory.getLogger(CbusNodeParameterManager.class);
356
357}