Demistifying the Dashboard

Okay, here it is. A verbose explanation of the updateDashboard() method contained in the DashboardExampleProject. With any luck, this should enable you to make sense of exactly what does what. This is written for the default FRC Dashboard, so if you make you’re own, well, best of luck!

EDIT: see the blurb at the bottom for a temporary fix which allows this code to work with update 2011.4.


    /**
     * Taken from DashboardExampleProject.  I'll try to explain how this is 
     * organized.
     * 
     * The LabView dashboard is organized with all the various displays in
     * "clusters".  These clusters are embedded in other clusters, which are
     * embedded in others, and so on.  For example, PWM display 1 is embedded
     * in the cluster for Digital Module 0, which is embedded in Digital
     * Cluster 1, which is in the cluster containing all the digital modules,
     * which is in the cluster containing everything.  Whew.  Basically it's 
     * a tree structure.
     * 
     * To send information to the dashboard, you have to "pack" this tree,
     * filling in all the various clusters with the correct data, and then 
     * send the whole tree at one time to the dashboard.  You create the
     * tree by using the addCluster() method, nesting each lower level of the
     * tree in the higher ones.  When a cluster is loaded you finalize it, 
     * which basically packs up that particular module for shipment to the 
     * dashboard.  After all the clusters are finalized you use the commit()
     * method to send it to the dashboard.
     * 
     * I've extracted the tree structure from the LabView dashboard code to
     * interpret what's below.  I'll try to provide enough comments for you
     * to understand which module is where, and which instrument display it
     * connects to.
     * 
     * To keep the dashboard up to date you have to call this method repeatedly,
     * either in an infinite loop or in teleopContinuous() if you use 
     * IterativeRobot.  I use an infinite loop running in a separate thread.
     */
    void updateDashboard() {
        
        // first, get an instance of the dashboard "packer" file.  This is 
        // essentially a map of how the dashboard has the clusters configured.
        Dashboard lowDashData = DriverStation.getInstance().getDashboardPackerLow();

        // We now recreate the tree using the addCluster method, nesting
        // the individual display panels.
        
        // First, the overall container that holds everything else
        lowDashData.addCluster();  // overall container
        {
            // There are 3 clusters in the next level, the analog modules,
            // the digital modules, and the solenoids.
            
            // Add the cluster containing the analog modules.
            lowDashData.addCluster();
            {
                // The analog module cluster contains two analog modules, one
                // for each of the first two slots on the cRio.
                
                // Add the cluster for Analog Module 0 (Slot One on the cRio)
                lowDashData.addCluster();
                {
                    // Here we just iterate through the analog ports, adding
                    // float values containing the average voltage for the given
                    // port.
                    for (int i = 1; i <= 8; i++) {
                        lowDashData.addFloat((float) AnalogModule.getInstance(1).getAverageVoltage(i));
                    }
                }
                // Finished with Analog Module 0, finalize the cluster
                lowDashData.finalizeCluster(); // finalize analog module 0

                /// Add the cluster for Analog Module 1 (Slot Two on the cRio)
                lowDashData.addCluster();
                {
                    // And again, iterate through the voltages for each port
                    // on the module
                    for (int i = 1; i <= 8; i++) {
                        lowDashData.addFloat((float) AnalogModule.getInstance(2).getAverageVoltage(i));
                    }
                }
                // Finished with Analog Module 1, finalize the cluster.
                lowDashData.finalizeCluster(); // finalize analog module 1
            }
            // Done loading the data for the analog modules, so we finalize
            // the cluster that holds both of them.
            lowDashData.finalizeCluster(); // finalize analog cluster

            // Add the cluster containing the digital sub clusters.
            lowDashData.addCluster();
            {
                // The digital cluster contains two sub clusters, one for
                // slot 4 and one for slot 6 on the cRio.  Each of those
                // clusters contain modules for relays and for the PWM
                // slots.
                
                // Add the cluster for Digital SubCluster 1 (slot 4 on the cRio)
                lowDashData.addCluster();
                {
                    // The digital module itself contains two sub modules.
                    // The numbering system is screwey, but it's taken straight
                    // from the LabView code...
                    
                    // Add the cluster Digital Module 0 in SubCluster 1
                    lowDashData.addCluster();
                    {
                        int module = 4; // this specifies the cRio slot
                        
                        // And the next 4 lines handle relays
                        lowDashData.addByte(DigitalModule.getInstance(module).getRelayForward());
                        lowDashData.addByte(DigitalModule.getInstance(module).getRelayForward());
                        lowDashData.addShort(DigitalModule.getInstance(module).getAllDIO());
                        lowDashData.addShort(DigitalModule.getInstance(module).getDIODirection());

                        // Add the cluster for PWM module 1 - I know for a fact
                        // that these are the 10 PWM connections on the digital
                        // sidecar hooked to the module in cRio Slot 4 :)
                        lowDashData.addCluster();
                        {
                            // Iterate through the PWM values for each port
                            for (int i = 1; i <= 10; i++) {
                                lowDashData.addByte((byte) DigitalModule.getInstance(module).getPWM(i));
                            }
                        }
                        // Finished with PWM Module 1, finalize the cluster.
                        lowDashData.finalizeCluster(); // finalize PWM 1
                    }
                    // And finished with Digital Module 0
                    lowDashData.finalizeCluster(); // finalize digital module 0
                }
                // And with Digital SubCluster 1
                lowDashData.finalizeCluster(); // finalize subcluster 1

                // Add the cluster for Digital SubCluster 2 (slot 6 on the cRio)
                lowDashData.addCluster();
                {
                    // Add the cluster for Digital Module 1 in SubCluster 2
                    lowDashData.addCluster();
                    {
                        int module = 6; // cRio slot 6
                        
                        // And again, load the relay data
                        lowDashData.addByte(DigitalModule.getInstance(module).getRelayForward());
                        lowDashData.addByte(DigitalModule.getInstance(module).getRelayReverse());
                        lowDashData.addShort(DigitalModule.getInstance(module).getAllDIO());
                        lowDashData.addShort(DigitalModule.getInstance(module).getDIODirection());

                        // Add the cluster for PWM Module 2
                        lowDashData.addCluster();
                        {
                            // iterate through all 10 PWM ports
                            for (int i = 1; i <= 10; i++) {
                                lowDashData.addByte((byte) DigitalModule.getInstance(module).getPWM(i));
                            }
                        }
                        // Finished with PWM Module 2, finalize it.
                        lowDashData.finalizeCluster(); // finalize PWM 2
                    }
                    // Finished with Digital Module 2
                    lowDashData.finalizeCluster(); // finalize digital 2
                }
                // And done with Digital SubCluster 2
                lowDashData.finalizeCluster(); // finalize subcluster 2
            }
            // And that completes the entire Digital Cluster
            lowDashData.finalizeCluster(); // finalize digital cluster

            // The solenoids, for some reason, are not in a cluster of their
            // own, but are instead sitting in the overall container.  As such,
            // we don't need to addCluster() for them, we just need to add
            // the data.
            lowDashData.addByte(Solenoid.getAll());

            // If you are going to add instruments to the dashboard the
            // easiest place to put them is in the overall cluster, and then 
            // load the values for them here the same way the solenoid was
            // done.  Makes me think the solenoid was an afterthought.
            // Actually I think the whole dashboard was designed haphazardly
            // instead of in a planned manner, which is why this whole structure
            // sucks so bad.
        }
        
        // Once all the inputs are read and loaded, we close the overall container
        lowDashData.finalizeCluster(); // finalize entire tree structure.

        // We are now ready to send our packed up data tree to the dashboard
        // for display.
        lowDashData.commit(); // commit changes to update dashboard

    } // end method updateDashboard()

Edit: It was an error on my system which allowed this to work as is. Solenoid.getAll() will not compile currently due to a bug in the 2011.4 libraries. You can work around it by replacing the line

lowDashData.addByte(Solenoid.getAll());

with the two lines

byte thisBytes = 0;
lowDashData.addByte(thisBytes);

This “zero byte workaround” allows the code to compile, and the dashboard to operate without errors. You won’t get any actual solenoid data, since we are essentially telling the dashboard that all the solenoids are registering zero all the time.

Thank you, this is very helpful!

Do the curly braces actually do anything, or are they just there for clarity?

I have no idea. Right now I’m kind of in the mind of “if it ain’t broke, don’t fix it.” Took me long enough to sort out how it works, and with just over 3 weeks left I’ll wait till after we ship before I start screwing with it.

I’m of the mind that a better dashboard, one designed logically, is in order, but to do that I have to figure out LabView and right now I’m rebelling against the powers that be and refusing to program in anything but Java.

Actually, C++ will probably come before LabView… :slight_smile:

They are there just for clarity. You can remove them if you really want to. But there’s this helpful feature in Netbeans IDE that allows you to collapse things inside curly braces. Which can come in handy if you want to organize your Dashboard code.

Good to know. That means I can rewrite it, split it up into a more manageable set of methods.

@wdell

LabVIEW is really quite a pain, I’d keep avoiding it as long as possible. This year we switched from LabVIEW to Java, and the number of students we’ve been able to keep interested in programing increased from 1 sorta-interested student to 5 enthusiastic students. :smiley:

@Patrick

Thanks for the clarification! I was worried they might be an obscure necessity for threading. I’ll probably write a nice neat wrapper class and pretend that mess doesn’t exist. :stuck_out_tongue:

Exactly :slight_smile: Share it with me when you are done, I really need to clean up my DStation class, the one that wraps up driver station and dashboard functions into one more manageable package.

Mostly clarity/indentation. However, they do something as well. They create a new scope/namespace. This is how they can declare “int module” twice in the same method. An easy enough problem to solve of course.

Updated the first post with a temporary fix allowing the example code to work with update 2011.4.

Also, check out the Smart Dashboard. Look at the SmartDashboard project on firstforge.wpi.edu. You just call logging methods on the robot, and values start popping up in fields and other widget types on the laptop. Then you can change the layout with drag and drop and save the new layout.

Also, the source code for the smart dashboard is available in the SmartDashboard project.

One problem we had with Smartdashboard is that is we couldn’t get the camera to display on there.

Me either, it would show the window in a brief flicker and then disappear. I finally had to go back to the old one.