001    /*----------------------------------------------------------------------------*/
002    /* Copyright (c) FIRST 2008-2012. All Rights Reserved.                        */
003    /* Open Source Software - may be modified and shared by FRC teams. The code   */
004    /* must be accompanied by the FIRST BSD license file in the root directory of */
005    /* the project.                                                               */
006    /*----------------------------------------------------------------------------*/
007    package edu.wpi.first.wpilibj;
008    
009    import edu.wpi.first.wpilibj.communication.UsageReporting;
010    import edu.wpi.first.wpilibj.parsing.IInputOutput;
011    import java.util.Stack;
012    
013    /**
014     * Pack data into the "user data" field that gets sent to the dashboard laptop
015     * via the driver station.
016     */
017    public class Dashboard  implements IDashboard, IInputOutput {
018    
019        protected class MemAccess {
020    
021            byte[] m_bytes;
022    
023            protected MemAccess(byte[] bytes) {
024                m_bytes = bytes;
025            }
026    
027            protected MemAccess(int length) {
028                m_bytes = new byte[length];
029            }
030    
031            public void setByte(int index, byte value) {
032                m_bytes[index] = value;
033            }
034    
035            public void setShort(int index, short value) {
036                setByte(index++, (byte) (value >>> 8));
037                setByte(index++, (byte) (value));
038            }
039    
040            public void setInt(int index, int value) {
041                setByte(index++, (byte) (value >>> 24));
042                setByte(index++, (byte) (value >>> 16));
043                setByte(index++, (byte) (value >>> 8));
044                setByte(index++, (byte) (value));
045            }
046    
047            public void setFloat(int index, float value) {
048                setInt(index, Float.floatToIntBits(value));
049            }
050    
051            public void setDouble(int index, double value) {
052                setInt(index, (int) (Double.doubleToLongBits(value) >>> 32));
053                setInt(index + 4, (int) Double.doubleToLongBits(value));
054            }
055    
056            public void setString(int index, String value) {
057                setBytes(index, value.getBytes(), 0, value.length());
058            }
059    
060            public void setBytes(int index, byte[] value, int offset, int number) {
061                for (int i = 0; i < number; i++) {
062                    m_bytes[i + index] = value[i + offset];
063                }
064            }
065        }
066        private static final String kArray = "Array";
067        private static final String kCluster = "Cluster";
068        private static final Integer kByte = new Integer(0);
069        private static final Integer kShort = new Integer(1);
070        private static final Integer kInt = new Integer(2);
071        private static final Integer kFloat = new Integer(3);
072        private static final Integer kDouble = new Integer(4);
073        private static final Integer kString = new Integer(5);
074        private static final Integer kOther = new Integer(6);
075        private static final Integer kBoolean = new Integer(7);
076        private static final int kMaxDashboardDataSize = DriverStation.USER_STATUS_DATA_SIZE -
077                4 * 3 - 1; // 13 bytes needed for 3 size parameters and the sequence number
078        private static boolean m_reported = false;
079        protected MemAccess m_userStatus;
080        protected int m_userStatusSize = 0;
081        private MemAccess m_localBuffer;
082        private int m_packPtr;
083        private Stack m_expectedArrayElementType = new Stack();
084        private Stack m_arrayElementCount = new Stack();
085        private Stack m_arraySizePtr = new Stack();
086        private Stack m_complexTypeStack = new Stack();
087        private final Object m_statusDataSemaphore;
088    
089        /**
090         * Dashboard constructor.
091         *
092         * This is only called once when the DriverStation constructor is called.
093         * @param statusDataSemaphore the object to synchronize on
094         */
095        protected Dashboard(Object statusDataSemaphore) {
096            m_userStatus = new MemAccess(kMaxDashboardDataSize);
097            m_localBuffer = new MemAccess(kMaxDashboardDataSize);
098            m_packPtr = 0;
099            m_statusDataSemaphore = statusDataSemaphore;
100        }
101    
102        /**
103         * Pack a signed 8-bit int into the dashboard data structure.
104         * @param value Data to be packed into the structure.
105         * @return True on success
106         */
107        public boolean addByte(byte value) {
108            if (!validateAdd(1)) {
109                return false;
110            }
111            m_localBuffer.setByte(m_packPtr, value);
112            m_packPtr += 1;
113            return addedElement(kByte);
114        }
115    
116        /**
117         * Pack a signed 16-bit int into the dashboard data structure.
118         * @param value Data to be packed into the structure.
119         * @return True on success
120         */
121        public boolean addShort(short value) {
122            if (!validateAdd(2)) {
123                return false;
124            }
125            m_localBuffer.setShort(m_packPtr, value);
126            m_packPtr += 2;
127            return addedElement(kShort);
128        }
129    
130        /**
131         * Pack a signed 32-bit int into the dashboard data structure.
132         * @param value Data to be packed into the structure.
133         * @return True on success
134         */
135        public boolean addInt(int value) {
136            if (!validateAdd(4)) {
137                return false;
138            }
139            m_localBuffer.setInt(m_packPtr, value);
140            m_packPtr += 4;
141            return addedElement(kInt);
142        }
143    
144        /**
145         * Pack a 32-bit floating point number into the dashboard data structure.
146         * @param value Data to be packed into the structure.
147         * @return True on success
148         */
149        public boolean addFloat(float value) {
150            if (!validateAdd(4)) {
151                return false;
152            }
153            m_localBuffer.setFloat(m_packPtr, value);
154            m_packPtr += 4;
155            return addedElement(kFloat);
156        }
157    
158        /**
159         * Pack a 64-bit floating point number into the dashboard data structure.
160         * @param value Data to be packed into the structure.
161         * @return True on success
162         */
163        public boolean addDouble(double value) {
164            if (!validateAdd(8)) {
165                return false;
166            }
167            m_localBuffer.setDouble(m_packPtr, value);
168            m_packPtr += 8;
169            return addedElement(kDouble);
170        }
171    
172        /**
173         * Pack a boolean into the dashboard data structure.
174         * @param value Data to be packed into the structure.
175         * @return True on success
176         */
177        public boolean addBoolean(boolean value) {
178            if (!validateAdd(1)) {
179                return false;
180            }
181            m_localBuffer.setByte(m_packPtr, (byte) (value ? 1 : 0));
182            m_packPtr += 1;
183            return addedElement(kBoolean);
184        }
185    
186        /**
187         * Pack a NULL-terminated string of 8-bit characters into the dashboard data structure.
188         * @param value Data to be packed into the structure.
189         * @return True on success
190         */
191        public boolean addString(String value) {
192            if (!validateAdd(value.length() + 4)) {
193                return false;
194            }
195            m_localBuffer.setInt(m_packPtr, value.length());
196            m_packPtr += 4;
197            m_localBuffer.setString(m_packPtr, value);
198            m_packPtr += value.length();
199            return addedElement(kString);
200        }
201    
202        /**
203         * Pack a string of 8-bit characters of specified length into the dashboard data structure.
204         * @param value Data to be packed into the structure.
205         * @param length The number of bytes in the string to pack.
206         * @return True on success
207         */
208        public boolean addString(String value, int length) {
209            return addString(value.substring(0, length));
210        }
211    
212        /**
213         * Start an array in the packed dashboard data structure.
214         *
215         * After calling addArray(), call the appropriate Add method for each element of the array.
216         * Make sure you call the same add each time.  An array must contain elements of the same type.
217         * You can use clusters inside of arrays to make each element of the array contain a structure of values.
218         * You can also nest arrays inside of other arrays.
219         * Every call to addArray() must have a matching call to finalizeArray().
220         * @return True on success
221         */
222        public boolean addArray() {
223            if (!validateAdd(4)) {
224                return false;
225            }
226            m_complexTypeStack.push(kArray);
227            m_arrayElementCount.push(new Integer(0));
228            m_arraySizePtr.push(new Integer(m_packPtr));
229            m_packPtr += 4;
230            return true;
231        }
232    
233        /**
234         * Indicate the end of an array packed into the dashboard data structure.
235         *
236         * After packing data into the array, call finalizeArray().
237         * Every call to addArray() must have a matching call to finalizeArray().
238         * @return True on success
239         */
240        public boolean finalizeArray() {
241            if (m_complexTypeStack.peek() != kArray) {
242                System.err.println("Attempted to finalize an array in the middle of a cluster or without starting the array");
243                return false;
244            }
245            m_complexTypeStack.pop();
246            m_localBuffer.setInt(((Integer) m_arraySizePtr.pop()).intValue(),
247                    ((Integer) m_arrayElementCount.peek()).intValue());
248    
249    
250            if (((Integer) m_arrayElementCount.peek()).intValue() != 0) {
251                m_expectedArrayElementType.pop();
252            }
253            m_arrayElementCount.pop();
254            return addedElement(kOther);
255        }
256    
257        /**
258         * Start a cluster in the packed dashboard data structure.
259         *
260         * After calling addCluster(), call the appropriate Add method for each element of the cluster.
261         * You can use clusters inside of arrays to make each element of the array contain a structure of values.
262         * Every call to addCluster() must have a matching call to finalizeCluster().
263         * @return True on success
264         */
265        public boolean addCluster() {
266            m_complexTypeStack.push(kCluster);
267            return true;
268        }
269    
270        /**
271         * Indicate the end of a cluster packed into the dashboard data structure.
272         *
273         * After packing data into the cluster, call finalizeCluster().
274         * Every call to addCluster() must have a matching call to finalizeCluster
275         * @return True on success
276         */
277        public boolean finalizeCluster() {
278            if (m_complexTypeStack.peek() != kCluster) {
279                System.err.println("Attempted to close a cluster on an open array or without starting the cluster");
280                return false;
281            }
282            m_complexTypeStack.pop();
283            return addedElement(kOther);
284        }
285    
286        /**
287         * Indicate that the packing is complete and commit the buffer to the DriverStation.
288         *
289         * The packing of the dashboard packet is complete.
290         * If you are not using the packed dashboard data, you can call commit() to commit the Printf() buffer and the error string buffer.
291         * In effect, you are packing an empty structure.
292         * Prepares a packet to go to the dashboard...
293         * Pack the sequence number, Printf() buffer, the errors messages (not implemented yet), and packed dashboard data buffer.
294         * @return The total size of the data packed into the userData field of the status packet.
295         */
296        public synchronized int commit() {
297    
298            if (!m_complexTypeStack.empty()) {
299                System.err.println("didn't finish complex type");
300                m_packPtr = 0;
301                System.err.println("didn't finish complex type");
302                return 0;
303            }
304    
305            if(!m_reported){
306                UsageReporting.report(UsageReporting.kResourceType_Dashboard, 0);
307                m_reported = true;
308            }
309    
310            synchronized (m_statusDataSemaphore) {
311                // Sequence number
312                DriverStation.getInstance().incrementUpdateNumber();
313    
314                // Packed Dashboard Data
315                m_userStatusSize = m_packPtr;
316                m_userStatus.setBytes(0, m_localBuffer.m_bytes, 0, m_userStatusSize);
317                m_packPtr = 0;
318    
319            }
320            return m_userStatusSize;
321        }
322    
323        /**
324         * Validate that the data being packed will fit in the buffer.
325         */
326        private boolean validateAdd(int size) {
327            if (m_packPtr + size > kMaxDashboardDataSize) {
328                m_packPtr = 0;
329                System.err.println("Dashboard data is too long to send");
330                return false;
331            }
332            return true;
333        }
334    
335        /**
336         * Check for consistent types when adding elements to an array and keep track of the number of elements in the array.
337         */
338        private boolean addedElement(Integer type) {
339            if (isArrayRoot()) {
340                if (((Integer) m_arrayElementCount.peek()).intValue() == 0) {
341                    m_expectedArrayElementType.push(type);
342                } else {
343                    if (type != m_expectedArrayElementType.peek()) {
344                        System.err.println("Attempted to add multiple datatypes to the same array");
345                        return false;
346                    }
347                }
348                m_arrayElementCount.push(new Integer(((Integer) m_arrayElementCount.pop()).intValue() + 1));
349            }
350            return true;
351        }
352    
353        /**
354         * If the top of the type stack an array?
355         */
356        private boolean isArrayRoot() {
357            return !m_complexTypeStack.empty() && m_complexTypeStack.peek() == kArray;
358        }
359    
360        public byte[] getBytes() {
361            return m_userStatus.m_bytes;
362        }
363    
364        public int getBytesLength() {
365            return m_userStatusSize;
366        }
367    
368        public void flush() {
369        }
370    }