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     * @param newcanid CAN ID of the node
128     */
129    public final void setCanId ( int newcanid ) {
130        _canId = newcanid;
131        notifyPropertyChangeListener("CANID", null, _canId);
132    }
133    
134    /**
135     * Set CAN ID by System Connection.
136     * <p>
137     * Leaves unchanged if no System Connection or Traffic Controller.
138     * @param memo System Connection of the Node.
139     */
140    public final void setCanId( CanSystemConnectionMemo  memo ) {
141        if ( memo != null ) {
142            TrafficController tc = memo.getTrafficController();
143            if ( tc != null ) {
144                setCanId(tc.getCanid());
145            }
146        }
147    }
148
149    /**
150     * Get the Node CAN ID.
151     * min 1 , ( max 128? )
152     * @return CAN ID of the node, default 1.
153     */
154    public int getNodeCanId() {
155        return _canId;
156    }
157
158    /**
159     * Set flag for this Node in Setup Mode.
160     * <p>
161     * Does NOT send instruction to actual node
162     *
163     * @param setup use true if in Setup, else false
164     */
165    public void setNodeInSetupMode( boolean setup ) {
166        _inSetupMode = setup;
167        notifyPropertyChangeListener("PARAMETER", null, null);
168    }
169    
170    /**
171     * Get if this Node is in Setup Mode.
172     *
173     * @return true if in Setup, else false
174     */
175    public boolean getNodeInSetupMode() {
176        return _inSetupMode;
177    }
178    
179    /**
180     * Set if the Node is in Learn Mode.
181     * Used to track node status, does NOT update Physical Node
182     * 
183     * @param inlearnmode set true if in Learn else false
184     */
185    public void setNodeInLearnMode( boolean inlearnmode) {
186        boolean oldLearnmode = _inlearnMode;
187        _inlearnMode = inlearnmode;
188        if (oldLearnmode != _inlearnMode) {
189            notifyPropertyChangeListener("LEARNMODE",oldLearnmode,_inlearnMode);
190        }
191    }
192    
193    /**
194     * Get if the Node is in Learn Mode.
195     * <p>
196     * Defaults to false if unset
197     * 
198     * @return true if in Learn else false
199     */
200    public boolean getNodeInLearnMode() {
201        return _inlearnMode;
202    }
203
204    /**
205     * Set if the Node is in FLiM Mode.
206     * <p>
207     * Defaults to true if unset
208     * 
209     * @param inFlimMode set true if in FlIM else false
210     */
211    public void setNodeInFLiMMode( boolean inFlimMode ) {
212        _inFLiMMode = inFlimMode;
213    }    
214    
215    /**
216     * Get if the Node is in FLiM Mode.
217     * <p>
218     * Defaults to true if unset
219     * 
220     * @return true if in FlIM else false
221     */
222    public boolean getNodeInFLiMMode() {
223        return _inFLiMMode;
224    }
225    
226    public CanSystemConnectionMemo getMemo() {
227        return _memo;
228    }
229    
230    private static final Logger log = LoggerFactory.getLogger(CbusBasicNode.class);
231    
232}