001package jmri.jmrix.can.adapters.gridconnect;
002
003import jmri.jmrix.AbstractMRMessage;
004import jmri.jmrix.can.CanMessage;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Class for GridConnect messages for a CAN hardware adapter.
010 * <p>
011 * The GridConnect protocol encodes messages as an ASCII string of up to 24
012 * characters of the form: :ShhhhNd0d1d2d3d4d5d6d7;
013 * <p>
014 * The S indicates a standard
015 * CAN frame :XhhhhhhhhNd0d1d2d3d4d5d6d7; The X indicates an extended CAN frame
016 * hhhh is the two byte header N or R indicates a normal or remote frame, in
017 * position 6 or 10 d0 - d7 are the (up to) 8 data bytes
018 *
019 * @author Andrew Crosland Copyright (C) 2008
020 */
021public class GridConnectMessage extends AbstractMRMessage {
022
023    /**
024     * Create a new instance of GridConnectMessage.
025     */
026    public GridConnectMessage() {
027        _nDataChars = 28;
028        _dataChars = new int[_nDataChars];
029        GridConnectMessage.this.setElement(0, ':');
030    }
031
032    /**
033     * Create a new GridConnectMessage from CanMessage.
034     * @param m CanMessage outgoing from JMRI.
035     */
036    public GridConnectMessage(CanMessage m) {
037        this();
038        addCanMessage(m);
039    }
040
041    private void addCanMessage(CanMessage m) {
042        // Standard or extended frame
043        setExtended(m.isExtended());
044
045        // Copy the header
046        setHeader(m.getHeader());
047
048        // Normal or Remote frame?
049        setRtr(m.isRtr());
050
051        // Data payload
052        for (int i = 0; i < m.getNumDataElements(); i++) {
053            setByte(m.getElement(i), i);
054        }
055        // Terminator
056        int offset = isExtended() ? 11 : 6;
057        setElement(offset + m.getNumDataElements() * 2, ';');
058        setNumDataElements(offset + 1 + m.getNumDataElements() * 2);
059        log.debug("encoded as {}", this.toString());
060    }
061
062    /**
063     * {@inheritDoc}
064     */
065    @Override
066    public int getNumDataElements() {
067        return _nDataChars;
068    }
069
070    /**
071     * Set Number of Data Elements.
072     * @param n number Elements.  Max 28.
073     */
074    public void setNumDataElements(int n) {
075        _nDataChars = (n <= 28) ? n : 28;
076    }
077
078    /**
079     * {@inheritDoc}
080     */
081    @Override
082    public int getElement(int n) {
083        return _dataChars[n];
084    }
085
086    /**
087     * {@inheritDoc}
088     */
089    @Override
090    public void setElement(int n, int v) {
091        _dataChars[n] = v;
092    }
093
094    /**
095     * Set data from array.
096     * @param d array, max length 24.
097     */
098    public void setData(int[] d) {
099        int len = (d.length <= 24) ? d.length : 24;
100        System.arraycopy(d, 0, _dataChars, 0, len);
101    }
102
103    /**
104     * Set the GC Message as Extended.
105     * @param extended true for extended, else false
106     */
107    public void setExtended(boolean extended) {
108        // Standard or extended frame
109        if (extended) {
110            setElement(1, 'X');
111        } else {
112            setElement(1, 'S');
113        }
114    }
115
116    /**
117     * Get if the GC Message is Extended.
118     * @return true for extended, else false
119     */
120    public boolean isExtended() {
121        return getElement(1) == 'X';
122    }
123
124    /**
125     * Set the header.
126     *
127     * @param header A valid CAN header value.
128     */
129    public void setHeader(int header) {
130        if (isExtended()) {
131            setHexDigit((header >> 28) & 0xF, 2);
132            setHexDigit((header >> 24) & 0xF, 3);
133            setHexDigit((header >> 20) & 0xF, 4);
134            setHexDigit((header >> 16) & 0xF, 5);
135            setHexDigit((header >> 12) & 0xF, 6);
136            setHexDigit((header >> 8) & 0xF, 7);
137            setHexDigit((header >> 4) & 0xF, 8);
138            setHexDigit(header & 0xF, 9);
139        } else {
140            setHexDigit((header >> 8) & 0xF, 2);
141            setHexDigit((header >> 4) & 0xF, 3);
142            setHexDigit(header & 0xF, 4);
143        }
144    }
145
146    /**
147     * Set CAN Frame as RtR.
148     * @param rtr true to set rtr, else false.
149     */
150    public void setRtr(boolean rtr) {
151        int offset = isExtended() ? 10 : 5;
152        setElement(offset, rtr ? 'R' : 'N');
153    }
154
155    /**
156     * Set a byte as two ASCII hex digits.
157     * <p>
158     * Data bytes are encoded as two ASCII hex digits starting at byte 7 of the
159     * message.
160     *
161     * @param val the value to set.
162     * @param n   the index of the byte to be set.
163     */
164    public void setByte(int val, int n) {
165        if ((n >= 0) && (n <= 7)) {
166            int index = n * 2 + (isExtended() ? 11 : 6);
167            setHexDigit((val / 16) & 0xF, index++);
168            setHexDigit(val & 0xF, index);
169        }
170    }
171
172    /**
173     * Set a hex digit at offset n in _dataChars.
174     * @param val min 0, max value 15.
175     * @param n _dataChars Array Index.
176     */
177    protected void setHexDigit(int val, int n) {
178        if ((val >= 0) && (val <= 15)) {
179            if (val < 10) {
180                _dataChars[n] = val + '0';
181            } else {
182                _dataChars[n] = val - 10 + 'A';
183            }
184        } else {
185            _dataChars[n] = '0';
186        }
187    }
188
189    private final static Logger log = LoggerFactory.getLogger(GridConnectMessage.class);
190}
191
192