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