How to call function when button pressed on Shuffleboard (Command-Based API)

Hi all!

I am trying to run a command when a ToggleButton is pressed on Shuffleboard. Our robot is command-based, so I would prefer avoiding periodic polling.

In the past our team has used the SmartDashboard API, but I decided to migrate to the newer and more fully-featured Shuffleboard API. I found an example on WPILib’s own docs that seems to do exactly what I want:

ShuffleboardTab tab = Shuffleboard.getTab("Shooter");
GenericEntry shooterEnable = tab.add("Shooter Enable", false).getEntry();

new NetworkButton(shooterEnable).onTrue(new InstantCommand(m_shooter::enable));

But when I tried this, it didn’t work, because NetworkButton does not have a constructor that takes a GenericEntry. However, I did find a ChiefDelphi post (in C++) that seems to have a workaround:

frc::Shuffleboard::GetTab("Subsystems")
  .GetLayout("Drive")
  .AddBoolean("Reset Gyro",
    [this] {
      return(m_resetGyroButton); 
    })
  .WithWidget(frc::BuiltInWidgets::kToggleButton)
  .WithSize(2, 1)
  .WithPosition(0, 7);
frc2::NetworkButton(
  nt::NetworkTableInstance::GetDefault()
    .GetBooleanTopic("/Shuffleboard/Subsystems/Drive/Reset Gyro")
)
  .OnTrue(frc2::cmd::RunOnce(
    [this] {
      m_drive.ZeroHeading();
      m_resetGyroButton = false;
    },
    {&m_drive})
);

However, this is a violation of DRY as the Subsystems/Drive/Reset Gyro is repeated and thus vulnerable to changes in one but not the other. After a lot of searching I finally came up with this:

    /*
     * (Taken from our 2024 code)
     * ShuffleboardTab dashboard;
     * ShuffleboardLayout addMap(ShuffleboardTab, ~)
     */
    Topic isEnabled =
    ShuffleboardTabWithMaps.addMap(dashboard, "<name>", "%s", Map.of(
      "<key>", value
    )).add(
      "IsEnabled", false
    )
      .withWidget(BuiltInWidgets.kToggleSwitch)
      .getEntry()
      .getTopic();

    new NetworkButton(new BooleanTopic(isEnabled))
      .onFalse(new InstantCommand(() -> { /* code */ }, m_arm))
      .onTrue(new InstantCommand(() -> { /* code */}, m_arm));

But all of this seems awfully verbose - getEntry, getTopic, constructing a BooleanTopic, constructing a NetworkButton - just to bind a function to a button press, when code to do the same thing for a controller button is merely:

m_driverController.<button>().onTrue(/* whatever */);

Am I doing something wrong or is the API really this complicated? And I find it strange the lack of documentation on this subject, or perhaps I didn’t look hard enough, but I was debugging this for at least an hour.

Any help is appreciated.
Thanks,
2262

This is definitely a case where an improved higher level API would help. Part of the problem is a collision between the older Shuffleboard API and the newer NT4 API. The NT4 API uses type-specific topics (e.g. BooleanTopic), while the older Shuffleboard API was originally designed around the NT3 API, which had untyped NetworkTableEntry’s. An overall redesign of the high-level telemetry and dashboard APIs is definitely needed, but beyond a conceptual design document, nobody has volunteered the time to implement it yet.

In that case, it doesn’t look like I can do anything much, but thanks for the help!

For anyone who finds this thread looking to do the same thing as me, I found another way too (untested):

new Trigger(m_genericEntry::getXXX).onTrue(/* whatever */);

This way makes it a little more obvious that it’s repeatedly polling the value.

Cheers,
2262

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.