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 >= 0 && index < _parameters.length ) {
098
099            _parameters[index] = newval;
100            _node.notifyPropertyChangeListener("PARAMETER", null, null);
101        } else {
102            log.debug("Node {} parameter index {} outside of {} parameters, not set", _node, index, _parameters.length - 1 );
103        }
104    }
105
106    /**
107     * Get Number of outstanding unknown Parameters to be fetched from a CbusNode
108     *
109     * @return Number of outstanding Parameters, else 8
110     */
111    public int getOutstandingParams(){
112
113        if (_parameters == null){
114            return 8; // CBUS Spec minimum 8 parameters, likely value 20
115        }
116
117        int count = 0;
118        for (int i = 1; i < _parameters.length; i++) {
119            if ( _parameters[i] == -1 ) {
120                count++;
121            }
122        }
123        return count;
124    }
125
126    /**
127     * Get a Single Parameter value
128     * <p>
129     * eg. for param. array [3,1,2,3] index 2 returns 2
130     * <p>
131     * Para 0 Number of parameters
132     * <p>
133     * Para 1 The manufacturer ID
134     * <p>
135     * Para 2 Minor code version as an alphabetic character (ASCII)
136     * <p>
137     * Para 3 Manufacturer module identifier as a HEX numeric
138     * <p>
139     * Para 4 Number of supported events as a HEX numeric
140     * <p>
141     * Para 5 Number of Event Variables per event as a HEX numeric
142     * <p>
143     * Para 6 Number of supported Node Variables as a HEX numeric
144     * <p>
145     * Para 7 Major version
146     * <p>
147     * Para 8 Node flags
148     * <p>
149     * Para 9 Processor type
150     * <p>
151     * Para 10 Bus type
152     * <p>
153     * Para 11-14 load address, 4 bytes
154     * <p>
155     * Para 15-18 CPU manufacturer's id as read from the chip config space, 4 bytes
156     * <p>
157     * Para 19 CPU manufacturer code
158     * <p>
159     * Para 20 Beta revision (numeric), or 0 if release
160     *
161     * @param index of which parameter, 0 gives the total parameters
162     * @return Full Parameter value for a particular index, -1 if unknown
163     */
164    public int getParameter(int index) {
165        if ( _parameters == null ) {
166            return -1;
167        }
168        try  {
169            return _parameters[index];
170        }
171        catch (java.lang.ArrayIndexOutOfBoundsException e) {
172            return -1;
173        }
174    }
175
176    /**
177     * Get array of All parameters
178     *
179     * @return Full Parameter array, index 0 is total parameters
180     */
181    public int[] getParameters() {
182        return _parameters;
183    }
184
185    /**
186     * Get the Parameter String in Hex Byte Format
187     * <p>
188     * eg. for param. array [3,1,2,3] returns "010203"
189     *
190     * @return Full Parameter String WITHOUT leading number of parameters
191     */
192    public String getParameterHexString() {
193        if (getParameters()==null) {
194            return "";
195        } else {
196            return jmri.util.StringUtil.hexStringFromInts(getParameters()).replaceAll("\\s","").substring(2);
197        }
198    }
199
200    protected void clearParameters() {
201        _parameters = null;
202        _nodeTraitsSet = false;
203    }
204
205    /**
206     * Get the Node Type
207     *
208     * @return eg. MERG Command Station CANCMD Firmware 4d Node 65534
209     */
210    public String getNodeTypeString(){
211        StringBuilder n = new StringBuilder(100);
212        n.append (CbusNodeConstants.getManu(getParameter(1)))
213        .append (" ")
214        .append( CbusNodeConstants.getModuleTypeExtra(getParameter(1),getParameter(3)))
215        .append(" ")
216        .append( CbusNodeConstants.getModuleType(getParameter(1),getParameter(3)))
217        .append (" ")
218        .append (Bundle.getMessage("FirmwareVer",getParameter(7),Character.toString((char) getParameter(2) )));
219        if ((getParameter(0)>19) && (getParameter(20)>0) ){
220            n.append (Bundle.getMessage("FWBeta"))
221            .append (getParameter(20))
222            .append (" ");
223        }
224        n.append (Bundle.getMessage("CbusNode"))
225        .append (_node.getNodeNumber());
226        return n.toString();
227    }
228
229    public void requestEventTot() {
230        if ( _node.getNodeTimerManager().hasActiveTimers() ){
231            return;
232        }
233        _node.getNodeTimerManager().setNumEvTimeout();
234        _node.send.rQEVN( _node.getNodeNumber() );
235    }
236
237    /**
238     * Request a single Parameter from a Physical Node
239     * <p>
240     * Will not send the request if there are existing active timers.
241     * Starts Parameter timeout
242     *
243     * @param param Parameter Index Number, Index 0 is total parameters
244     */
245    public void requestParam(int param){
246        if ( _node.getNodeTimerManager().hasActiveTimers() ){
247            return;
248        }
249        _node.getNodeTimerManager().setAllParamTimeout(param);
250        _node.send.rQNPN( _node.getNodeNumber(), param );
251    }
252
253    /**
254     * Check if current node firmware is equal to or newer than provided version
255     * 
256     * Note that CBUS firmware start with beta builds, followed by a release
257     * with the beta build set to 0, e.g.:
258     * major.minor Beta 1
259     * major.minor Beta 2
260     * ...
261     * major.minor Beta x
262     * major.minor Beta 0
263     * 
264     * @param major New FW major version
265     * @param minor New FW minor version
266     * @param beta  New FW beta build number
267     * @return      true if current node firmware is equal to or newer than provided version
268     */
269    public boolean isFwEqualOrNewer(int major, int minor, int beta) {
270        if (major > getParameter(MAJOR_VER_IDX)) {
271            return false;
272        } else if (major < getParameter(MAJOR_VER_IDX)) {
273            return true;
274        } else {
275            // Major ver is equal, test minor
276            if (minor > getParameter(MINOR_VER_IDX)) {
277                return false;
278            } else if (minor < getParameter(MINOR_VER_IDX)) {
279                return true;
280            } else {
281                // Major and minor are equal, test beta
282                if (beta == 0) {
283                    // Release is always newer than any beta
284                    return true;
285                } else if (beta > getParameter(BETA_REV_IDX)) {
286                    return false;
287                } else {
288                    return true;
289                }
290            }
291        }
292    }
293    
294    private boolean sentParamRequest(int paramToCheck){
295        if ( getParameter(paramToCheck) < 0 ) {
296            requestParam(paramToCheck);
297            return true;
298        }
299        return false;
300    }
301
302    /**
303     * Send a request for the next unknown parameter to the physical node
304     *
305     */
306    protected void sendRequestNextParam(){
307        if ( _parameters == null ) {
308            requestParam(0);
309            return;
310        }
311        if ( sentParamRequest(1)  // Manufacturer ID
312            || ( sentParamRequest(3) )  // Module ID
313            || ( sentParamRequest(6) ) ) { // initialise NV's
314            return;
315        }
316
317        if ( sentParamRequest(5) // get number event variables per event
318            || ( sentParamRequest(7) ) // get firmware pt1
319            || ( sentParamRequest(2) ) ) { // get firmware pt2
320            return;
321        }
322
323        finishedWhenGotMainParams();
324
325        for (int i = 1; i < _parameters.length; i++) {
326            if ( sentParamRequest(i) ) {
327                return;
328            }
329        }
330    }
331
332    private void finishedWhenGotMainParams(){
333
334        if (!( _node instanceof CbusNode)){
335            return;
336        }
337
338        if (( ( (CbusNode) _node).getCsNum() > -1 ) && ( _commandStationIdentified == false ) ) {
339            // notify command station located
340            log.info("Node type: {}",getNodeTypeString() );
341            _commandStationIdentified = true;
342        }
343
344        // set node traits, eg CANPAN v1 send wrack on nv set, CANCMD v4 numevents 0
345        // only do this once
346        if (!_nodeTraitsSet ) {
347            CbusNodeConstants.setTraits((CbusNode) _node);
348            _nodeTraitsSet = true;
349        }
350
351        // now traits are known request num. of events
352        if ( _node.getNodeEventManager().getTotalNodeEvents()<0 ){
353            requestEventTot();
354        }
355    }
356
357    private static final Logger log = LoggerFactory.getLogger(CbusNodeParameterManager.class);
358
359}