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