001package jmri.jmrix.loconet.locostats;
002
003import java.util.Vector;
004import javax.annotation.Nonnull;
005
006import jmri.jmrix.loconet.LnConstants;
007import jmri.jmrix.loconet.LocoNetListener;
008import jmri.jmrix.loconet.LocoNetMessage;
009import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
010
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * Implements functionality to query the LocoNet interface device for status.
016 * 
017 * @author Bob Milhaupt Copyright (C) 2017
018 */
019public class LocoStatsFunc implements LocoNetListener {
020    private LocoNetSystemConnectionMemo memo;
021    public LocoStatsFunc(LocoNetSystemConnectionMemo memo) {
022        this.memo = memo;
023        updatePending = false;
024        need2ndUpdate = false;
025        ifaceStatus = null;
026        if (memo != null) {
027            this.memo.getLnTrafficController().addLocoNetListener(0, this);
028        }
029    }
030    private boolean updatePending;
031    private boolean need2ndUpdate;
032    private Object ifaceStatus;
033    
034    /**
035     * Request LocoNet interface Status
036     */
037    public void sendLocoNetInterfaceStatusQueryMessage() {
038        updatePending = true;
039        need2ndUpdate = false;  // assume that we do not need a second query
040
041        log.debug("Sent a LocoNet interface status query");
042        sendQuery();
043    }
044    
045    private void sendQuery() {
046        if (memo == null) {
047            return;
048        }
049        LocoNetMessage l = new LocoNetMessage(new int[] {0x81, 0x7f});
050        memo.getLnTrafficController().sendLocoNetMessage(l);
051    }
052    
053    /**
054     * LocoNet message handler.
055     * 
056     * @param msg  incoming LocoNet message to be interpreted
057     */
058    @Override
059    public void message(LocoNetMessage msg) {
060        if ((msg.getOpCode() == LnConstants.OPC_PEER_XFER)
061                && (msg.getElement(1) == 0x10)
062                && (msg.getElement(2) == 0x50)
063                && (msg.getElement(3) == 0x50)
064                && (msg.getElement(4) == 0x01)
065                && ((msg.getElement(5) & 0xF0) == 0x0)
066                && ((msg.getElement(10) & 0xF0) == 0x0)
067                && updatePending) {
068            // LocoBuffer II form
069            int[] data = msg.getPeerXfrData();
070            ifaceStatus = new LocoBufferIIStatus(
071                    data[0]*256+data[4],
072                    (data[5] << 16) + (data[6] << 8) + data[7],
073                    (data[1] << 16) + (data[2] << 8) + data[3]
074            );
075            updatePending = false;
076            updateListeners();
077            log.debug("Got a LocoNet interface status reply: LocoBufferII");
078
079        } else if ((msg.getOpCode() == LnConstants.OPC_PEER_XFER)
080                && (msg.getElement(1) == 0x10)
081                && (msg.getElement(2) == 0x22)
082                && (msg.getElement(3) == 0x22)
083                && (msg.getElement(4) == 0x01)
084                && ((msg.getElement(5) & 0x70) == 0x0)
085                && ((msg.getElement(10) & 0x70) == 0x0)
086                && updatePending) {  // Digitrax form, check PR2/PR3 or MS100/PR3 mode
087
088            if ((msg.getElement(8) & 0x20) == 0) {
089                // PR2 format
090                int[] data = msg.getPeerXfrData();
091                ifaceStatus = new PR2Status(
092                        data[1] * 256 + data[0],
093                        data[2],
094                        data[3],
095                        data[4],
096                        data[5]
097                );
098                log.debug("Got a LocoNet interface status reply: PR2 mode");
099                if (updatePending) {
100                    if (need2ndUpdate == false) {
101                        need2ndUpdate = true;
102                        sendQuery();   // get info for MS100 mode, too
103                    } else {
104                        need2ndUpdate = false;
105                        updatePending = false;
106                    }
107                }
108
109            } else {
110                // MS100 format
111                int[] data = msg.getPeerXfrData();
112                ifaceStatus = new PR3MS100ModeStatus(
113                        data[1] * 256 + data[0],
114                        data[5] * 256 + data[4],
115                        data[2]
116                );
117                log.debug("Got a LocoNet interface status reply: PR3 MS100 mode");
118                if (updatePending) {
119                    if (need2ndUpdate == false) {
120                        need2ndUpdate = true;
121                        sendQuery();   // get info for PR2 mode, too
122                    } else {
123                        need2ndUpdate = false;
124                        updatePending = false;
125                    }
126                }
127            }
128            updateListeners();
129
130        } else if ((msg.getOpCode() == LnConstants.OPC_PEER_XFER) && 
131                (msg.getElement(1) == 0x10) &&
132                updatePending) {
133            // Raw mode format
134            // Accept only the first OPC_PEER_XFER of length 0x10 after a request.  
135            // This assumes that the interface device will be the first response 
136            // after the request, and that the reply will be a "typical" OPC_PEER_XFER
137            // message, and not one of the "alternate" forms with different overall 
138            // length..
139            int[] data = msg.getPeerXfrData();
140            ifaceStatus = new RawStatus(data[0], data[1], data[2], data[3],
141                    data[4], data[5], data[6], data[7]
142            );
143
144            updatePending = false;
145            updateListeners();
146            log.debug("Got a LocoNet interface status reply: Raw mode");
147        }
148    }
149    private void updateListeners() {
150        listeners.stream().forEach((l) -> {
151            l.notifyChangedInterfaceStatus(ifaceStatus);
152        });
153    }
154    
155    /**
156     * Get the latest interface status
157     * 
158     * @return the latest interface status; will be null if status has 
159     *          not been pulled.
160     */
161    public Object getInterfaceStatus() {
162        return ifaceStatus;
163    }
164    
165    /**
166     * Free resources when no longer used
167     */
168    public void dispose() {
169        listeners.removeAllElements();
170        listeners = null;
171    }
172    
173    // The methods to implement adding and removing listeners
174    protected Vector<LocoNetInterfaceStatsListener> listeners = new Vector<LocoNetInterfaceStatsListener>();
175
176    /**
177     * Add a listener to the list of listeners which will be notified upon receipt 
178     * a LocoNet message containing interface statistics.
179     * 
180     * @param l  LocoNetInterfaceStatsListener to be added
181     */
182    public synchronized void addLocoNetInterfaceStatsListener(@Nonnull LocoNetInterfaceStatsListener l) {
183        java.util.Objects.requireNonNull(l);
184        // add only if not already registered
185        if (!listeners.contains(l)) {
186            listeners.addElement(l);
187        }
188    }
189
190    /**
191     * Remove a listener (if present) from the list of listeners which will be 
192     * notified upon receipt LocoNet message containing interface statistics.
193     * 
194     * @param l  LocoNetInterfaceStatsListener to be removed
195     */
196    public synchronized void removeLocoNetInterfaceStatsListener(@Nonnull LocoNetInterfaceStatsListener l) {
197        java.util.Objects.requireNonNull(l);
198        if (listeners.contains(l)) {
199            listeners.removeElement(l);
200        }
201    }
202
203    private final static Logger log = LoggerFactory.getLogger(LocoStatsFunc.class);
204}