001package jmri.jmrix.can.cbus.node;
002
003import java.beans.PropertyChangeListener;
004import java.util.concurrent.CopyOnWriteArraySet;
005// import javax.annotation.Nonnull;
006
007import javax.annotation.CheckForNull;
008
009import jmri.jmrix.can.CanSystemConnectionMemo;
010import jmri.jmrix.can.TrafficController;
011import jmri.jmrix.can.cbus.CbusSend;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Class to represent a node.
018 *
019 * @author Steve Young Copyright (C) 2019
020 */
021public class CbusBasicNode {
022    protected CanSystemConnectionMemo _memo;
023    private int _nodeNumber;    
024    
025    private int _canId;
026    private boolean _inSetupMode;
027    private boolean _inlearnMode;
028    private boolean _inFLiMMode;
029    
030    public CbusSend send;
031    
032    // data members to hold contact with the property listeners
033    protected final CopyOnWriteArraySet<PropertyChangeListener> _listeners;
034    
035    /**
036     * Create a new CbusBasicNodeWithChangeListener.
037     *
038     * @param connmemo The CAN Connection to use
039     * @param nodenumber The Node Number
040     */
041    public CbusBasicNode ( @CheckForNull CanSystemConnectionMemo connmemo, int nodenumber ){
042        _memo = connmemo;
043        _nodeNumber = nodenumber;
044        _canId = 1;
045        _inSetupMode = false;
046        _inlearnMode = false;
047        _inFLiMMode = true;
048        
049        send = new CbusSend(_memo);
050        _listeners = new CopyOnWriteArraySet<>();
051    }
052    
053    /**
054     * Register for notification if any of the properties change.
055     *
056     * @param l The Listener to attach to Node
057     */
058    public void addPropertyChangeListener(PropertyChangeListener l) {
059        // add only if not already registered
060        if (!_listeners.contains(l)) {
061            _listeners.add(l);
062            // log.info("Added listener {}, new size is {}", l,_listeners.size());
063        }
064    }
065    
066    /**
067     * Remove notification listener.
068     * @param l Listener to remove
069     */
070    public void removePropertyChangeListener(PropertyChangeListener l) {
071        if (_listeners.contains(l)) {
072            _listeners.remove(l);
073            // log.info("Removed listener {}, new size is {}", l,_listeners.size());
074        }
075    }
076
077    /**
078     * Trigger the notification of Node PropertyChangeListeners.
079     * 
080     * Properties include 
081     * PARAMETER, 
082     * BACKUPS, 
083     * SINGLENVUPDATE ( newValue NV index (0 is NV1, 5 is NV6) )
084     * ALLNVUPDATE
085     * SINGLEEVUPDATE ( newValue event row )
086     * ALLEVUPDATE
087     * DELETEEVCOMPLETE ( newValue Error String else empty String )
088     * ADDEVCOMPLETE ( newValue Error String else null )
089     * ADDALLEVCOMPLETE ( Event Teach Loop Completed, newValue error count )
090     * TEACHNVCOMPLETE ( newValue error count )
091     * NAMECHANGE
092     * 
093     * @param property Node property
094     * @param oldValue Old Value
095     * @param newValue New Value
096     */
097    protected void notifyPropertyChangeListener(String property, Object oldValue, Object newValue) {
098        if ((oldValue != null && oldValue.equals(newValue)) || (newValue != null && newValue.equals(oldValue))) {
099            log.error("notifyPropertyChangeListener without change");
100            return;
101        }
102        _listeners.forEach((listener) -> {
103            listener.propertyChange(new java.beans.PropertyChangeEvent(this, property, oldValue, newValue));
104        });
105    }
106    
107    /**
108     * Returns Node Number.
109     *
110     * @return Node Number,1-65535
111     */
112    public int getNodeNumber() {
113        return _nodeNumber;
114    }
115    
116    /**
117     * Set Node Number.
118     * @param newnumber Node Number, should be 1-65535
119     */
120    public void setNodeNumber ( int newnumber ) {
121        _nodeNumber = newnumber;
122        notifyPropertyChangeListener("PARAMETER", null, null);
123    }
124
125    /**
126     * Set Node CAN ID.
127     * <p>
128     * Notifies PropertyChangeListener "CANID" only if the CAN ID has changed,
129     * as this is called for every CAN Frame received from the node.
130     * @param newcanid CAN ID of the node
131     */
132    public final void setCanId ( int newcanid ) {
133        if ( _canId == newcanid ) {
134            return;
135        }
136        int oldCanId = _canId;
137        _canId = newcanid;
138        notifyPropertyChangeListener("CANID", oldCanId, _canId);
139    }
140    
141    /**
142     * Set CAN ID by System Connection.
143     * <p>
144     * Leaves unchanged if no System Connection or Traffic Controller.
145     * @param memo System Connection of the Node.
146     */
147    public final void setCanId( CanSystemConnectionMemo  memo ) {
148        if ( memo != null ) {
149            TrafficController tc = memo.getTrafficController();
150            if ( tc != null ) {
151                setCanId(tc.getCanid());
152            }
153        }
154    }
155
156    /**
157     * Get the Node CAN ID.
158     * min 1 , ( max 128? )
159     * @return CAN ID of the node, default 1.
160     */
161    public int getNodeCanId() {
162        return _canId;
163    }
164
165    /**
166     * Set flag for this Node in Setup Mode.
167     * <p>
168     * Does NOT send instruction to actual node
169     *
170     * @param setup use true if in Setup, else false
171     */
172    public void setNodeInSetupMode( boolean setup ) {
173        _inSetupMode = setup;
174        notifyPropertyChangeListener("PARAMETER", null, null);
175    }
176    
177    /**
178     * Get if this Node is in Setup Mode.
179     *
180     * @return true if in Setup, else false
181     */
182    public boolean getNodeInSetupMode() {
183        return _inSetupMode;
184    }
185    
186    /**
187     * Set if the Node is in Learn Mode.
188     * Used to track node status, does NOT update Physical Node
189     * 
190     * @param inlearnmode set true if in Learn else false
191     */
192    public void setNodeInLearnMode( boolean inlearnmode) {
193        boolean oldLearnmode = _inlearnMode;
194        _inlearnMode = inlearnmode;
195        if (oldLearnmode != _inlearnMode) {
196            notifyPropertyChangeListener("LEARNMODE",oldLearnmode,_inlearnMode);
197        }
198    }
199    
200    /**
201     * Get if the Node is in Learn Mode.
202     * <p>
203     * Defaults to false if unset
204     * 
205     * @return true if in Learn else false
206     */
207    public boolean getNodeInLearnMode() {
208        return _inlearnMode;
209    }
210
211    /**
212     * Set if the Node is in FLiM Mode.
213     * <p>
214     * Defaults to true if unset
215     * 
216     * @param inFlimMode set true if in FlIM else false
217     */
218    public void setNodeInFLiMMode( boolean inFlimMode ) {
219        _inFLiMMode = inFlimMode;
220    }    
221    
222    /**
223     * Get if the Node is in FLiM Mode.
224     * <p>
225     * Defaults to true if unset
226     * 
227     * @return true if in FlIM else false
228     */
229    public boolean getNodeInFLiMMode() {
230        return _inFLiMMode;
231    }
232    
233    public CanSystemConnectionMemo getMemo() {
234        return _memo;
235    }
236    
237    private static final Logger log = LoggerFactory.getLogger(CbusBasicNode.class);
238    
239}