001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.wpilibj.smartdashboard; 006 007import edu.wpi.first.networktables.BooleanArrayPublisher; 008import edu.wpi.first.networktables.BooleanArraySubscriber; 009import edu.wpi.first.networktables.BooleanArrayTopic; 010import edu.wpi.first.networktables.BooleanPublisher; 011import edu.wpi.first.networktables.BooleanSubscriber; 012import edu.wpi.first.networktables.BooleanTopic; 013import edu.wpi.first.networktables.DoubleArrayPublisher; 014import edu.wpi.first.networktables.DoubleArraySubscriber; 015import edu.wpi.first.networktables.DoubleArrayTopic; 016import edu.wpi.first.networktables.DoublePublisher; 017import edu.wpi.first.networktables.DoubleSubscriber; 018import edu.wpi.first.networktables.DoubleTopic; 019import edu.wpi.first.networktables.FloatArrayPublisher; 020import edu.wpi.first.networktables.FloatArraySubscriber; 021import edu.wpi.first.networktables.FloatArrayTopic; 022import edu.wpi.first.networktables.FloatPublisher; 023import edu.wpi.first.networktables.FloatSubscriber; 024import edu.wpi.first.networktables.FloatTopic; 025import edu.wpi.first.networktables.IntegerArrayPublisher; 026import edu.wpi.first.networktables.IntegerArraySubscriber; 027import edu.wpi.first.networktables.IntegerArrayTopic; 028import edu.wpi.first.networktables.IntegerPublisher; 029import edu.wpi.first.networktables.IntegerSubscriber; 030import edu.wpi.first.networktables.IntegerTopic; 031import edu.wpi.first.networktables.NTSendableBuilder; 032import edu.wpi.first.networktables.NetworkTable; 033import edu.wpi.first.networktables.PubSubOption; 034import edu.wpi.first.networktables.Publisher; 035import edu.wpi.first.networktables.RawPublisher; 036import edu.wpi.first.networktables.RawSubscriber; 037import edu.wpi.first.networktables.RawTopic; 038import edu.wpi.first.networktables.StringArrayPublisher; 039import edu.wpi.first.networktables.StringArraySubscriber; 040import edu.wpi.first.networktables.StringArrayTopic; 041import edu.wpi.first.networktables.StringPublisher; 042import edu.wpi.first.networktables.StringSubscriber; 043import edu.wpi.first.networktables.StringTopic; 044import edu.wpi.first.networktables.Subscriber; 045import edu.wpi.first.networktables.Topic; 046import edu.wpi.first.util.function.BooleanConsumer; 047import edu.wpi.first.util.function.FloatConsumer; 048import edu.wpi.first.util.function.FloatSupplier; 049import edu.wpi.first.wpilibj.RobotController; 050import java.util.ArrayList; 051import java.util.List; 052import java.util.function.BooleanSupplier; 053import java.util.function.Consumer; 054import java.util.function.DoubleConsumer; 055import java.util.function.DoubleSupplier; 056import java.util.function.LongConsumer; 057import java.util.function.LongSupplier; 058import java.util.function.Supplier; 059 060/** Implementation detail for SendableBuilder. */ 061public class SendableBuilderImpl implements NTSendableBuilder { 062 @FunctionalInterface 063 private interface TimedConsumer<T> { 064 void accept(T value, long time); 065 } 066 067 private static final class Property<P extends Publisher, S extends Subscriber> 068 implements AutoCloseable { 069 @Override 070 @SuppressWarnings("PMD.AvoidCatchingGenericException") 071 public void close() { 072 try { 073 if (m_pub != null) { 074 m_pub.close(); 075 } 076 if (m_sub != null) { 077 m_sub.close(); 078 } 079 } catch (Exception e) { 080 // ignore 081 } 082 } 083 084 void update(boolean controllable, long time) { 085 if (controllable && m_sub != null && m_updateLocal != null) { 086 m_updateLocal.accept(m_sub); 087 } 088 if (m_pub != null && m_updateNetwork != null) { 089 m_updateNetwork.accept(m_pub, time); 090 } 091 } 092 093 P m_pub; 094 S m_sub; 095 TimedConsumer<P> m_updateNetwork; 096 Consumer<S> m_updateLocal; 097 } 098 099 private final List<Property<?, ?>> m_properties = new ArrayList<>(); 100 private Runnable m_safeState; 101 private final List<Runnable> m_updateTables = new ArrayList<>(); 102 private NetworkTable m_table; 103 private boolean m_controllable; 104 private boolean m_actuator; 105 106 private BooleanPublisher m_controllablePub; 107 private StringPublisher m_typePub; 108 private BooleanPublisher m_actuatorPub; 109 110 private final List<AutoCloseable> m_closeables = new ArrayList<>(); 111 112 /** Default constructor. */ 113 public SendableBuilderImpl() {} 114 115 @Override 116 @SuppressWarnings("PMD.AvoidCatchingGenericException") 117 public void close() { 118 if (m_controllablePub != null) { 119 m_controllablePub.close(); 120 } 121 if (m_typePub != null) { 122 m_typePub.close(); 123 } 124 if (m_actuatorPub != null) { 125 m_actuatorPub.close(); 126 } 127 for (Property<?, ?> property : m_properties) { 128 property.close(); 129 } 130 for (AutoCloseable closeable : m_closeables) { 131 try { 132 closeable.close(); 133 } catch (Exception e) { 134 // ignore 135 } 136 } 137 } 138 139 /** 140 * Set the network table. Must be called prior to any Add* functions being called. 141 * 142 * @param table Network table 143 */ 144 public void setTable(NetworkTable table) { 145 m_table = table; 146 m_controllablePub = table.getBooleanTopic(".controllable").publish(); 147 m_controllablePub.setDefault(false); 148 } 149 150 /** 151 * Get the network table. 152 * 153 * @return The network table 154 */ 155 @Override 156 public NetworkTable getTable() { 157 return m_table; 158 } 159 160 /** 161 * Return whether this sendable has an associated table. 162 * 163 * @return True if it has a table, false if not. 164 */ 165 @Override 166 public boolean isPublished() { 167 return m_table != null; 168 } 169 170 /** 171 * Return whether this sendable should be treated as an actuator. 172 * 173 * @return True if actuator, false if not. 174 */ 175 public boolean isActuator() { 176 return m_actuator; 177 } 178 179 /** Update the network table values by calling the getters for all properties. */ 180 @Override 181 public void update() { 182 long time = RobotController.getTime(); 183 for (Property<?, ?> property : m_properties) { 184 property.update(m_controllable, time); 185 } 186 for (Runnable updateTable : m_updateTables) { 187 updateTable.run(); 188 } 189 } 190 191 /** Hook setters for all properties. */ 192 public void startListeners() { 193 m_controllable = true; 194 if (m_controllablePub != null) { 195 m_controllablePub.set(true); 196 } 197 } 198 199 /** Unhook setters for all properties. */ 200 public void stopListeners() { 201 m_controllable = false; 202 if (m_controllablePub != null) { 203 m_controllablePub.set(false); 204 } 205 } 206 207 /** 208 * Start LiveWindow mode by hooking the setters for all properties. Also calls the safeState 209 * function if one was provided. 210 */ 211 public void startLiveWindowMode() { 212 if (m_safeState != null) { 213 m_safeState.run(); 214 } 215 startListeners(); 216 } 217 218 /** 219 * Stop LiveWindow mode by unhooking the setters for all properties. Also calls the safeState 220 * function if one was provided. 221 */ 222 public void stopLiveWindowMode() { 223 stopListeners(); 224 if (m_safeState != null) { 225 m_safeState.run(); 226 } 227 } 228 229 /** Clear properties. */ 230 @Override 231 public void clearProperties() { 232 stopListeners(); 233 for (Property<?, ?> property : m_properties) { 234 property.close(); 235 } 236 m_properties.clear(); 237 } 238 239 @Override 240 public void addCloseable(AutoCloseable closeable) { 241 m_closeables.add(closeable); 242 } 243 244 /** 245 * Set the string representation of the named data type that will be used by the smart dashboard 246 * for this sendable. 247 * 248 * @param type data type 249 */ 250 @Override 251 public void setSmartDashboardType(String type) { 252 if (m_typePub == null) { 253 m_typePub = 254 m_table 255 .getStringTopic(".type") 256 .publishEx(StringTopic.kTypeString, "{\"SmartDashboard\":\"" + type + "\"}"); 257 } 258 m_typePub.set(type); 259 } 260 261 /** 262 * Set a flag indicating if this sendable should be treated as an actuator. By default, this flag 263 * is false. 264 * 265 * @param value true if actuator, false if not 266 */ 267 @Override 268 public void setActuator(boolean value) { 269 if (m_actuatorPub == null) { 270 m_actuatorPub = m_table.getBooleanTopic(".actuator").publish(); 271 } 272 m_actuatorPub.set(value); 273 m_actuator = value; 274 } 275 276 /** 277 * Set the function that should be called to set the Sendable into a safe state. This is called 278 * when entering and exiting Live Window mode. 279 * 280 * @param func function 281 */ 282 @Override 283 public void setSafeState(Runnable func) { 284 m_safeState = func; 285 } 286 287 /** 288 * Set the function that should be called to update the network table for things other than 289 * properties. Note this function is not passed the network table object; instead it should use 290 * the topics returned by getTopic(). 291 * 292 * @param func function 293 */ 294 @Override 295 public void setUpdateTable(Runnable func) { 296 m_updateTables.add(func); 297 } 298 299 /** 300 * Add a property without getters or setters. This can be used to get entry handles for the 301 * function called by setUpdateTable(). 302 * 303 * @param key property name 304 * @return Network table entry 305 */ 306 @Override 307 public Topic getTopic(String key) { 308 return m_table.getTopic(key); 309 } 310 311 /** 312 * Add a boolean property. 313 * 314 * @param key property name 315 * @param getter getter function (returns current value) 316 * @param setter setter function (sets new value) 317 */ 318 @Override 319 public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) { 320 Property<BooleanPublisher, BooleanSubscriber> property = new Property<>(); 321 BooleanTopic topic = m_table.getBooleanTopic(key); 322 if (getter != null) { 323 property.m_pub = topic.publish(); 324 property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsBoolean(), time); 325 } 326 if (setter != null) { 327 property.m_sub = topic.subscribe(false, PubSubOption.excludePublisher(property.m_pub)); 328 property.m_updateLocal = 329 sub -> { 330 for (boolean val : sub.readQueueValues()) { 331 setter.accept(val); 332 } 333 }; 334 } 335 m_properties.add(property); 336 } 337 338 @Override 339 public void publishConstBoolean(String key, boolean value) { 340 Property<BooleanPublisher, BooleanSubscriber> property = new Property<>(); 341 BooleanTopic topic = m_table.getBooleanTopic(key); 342 property.m_pub = topic.publish(); 343 property.m_pub.set(value); 344 m_properties.add(property); 345 } 346 347 /** 348 * Add an integer property. 349 * 350 * @param key property name 351 * @param getter getter function (returns current value) 352 * @param setter setter function (sets new value) 353 */ 354 @Override 355 public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) { 356 Property<IntegerPublisher, IntegerSubscriber> property = new Property<>(); 357 IntegerTopic topic = m_table.getIntegerTopic(key); 358 if (getter != null) { 359 property.m_pub = topic.publish(); 360 property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsLong(), time); 361 } 362 if (setter != null) { 363 property.m_sub = topic.subscribe(0, PubSubOption.excludePublisher(property.m_pub)); 364 property.m_updateLocal = 365 sub -> { 366 for (long val : sub.readQueueValues()) { 367 setter.accept(val); 368 } 369 }; 370 } 371 m_properties.add(property); 372 } 373 374 @Override 375 public void publishConstInteger(String key, long value) { 376 Property<IntegerPublisher, IntegerSubscriber> property = new Property<>(); 377 IntegerTopic topic = m_table.getIntegerTopic(key); 378 property.m_pub = topic.publish(); 379 property.m_pub.set(value); 380 m_properties.add(property); 381 } 382 383 /** 384 * Add a float property. 385 * 386 * @param key property name 387 * @param getter getter function (returns current value) 388 * @param setter setter function (sets new value) 389 */ 390 @Override 391 public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) { 392 Property<FloatPublisher, FloatSubscriber> property = new Property<>(); 393 FloatTopic topic = m_table.getFloatTopic(key); 394 if (getter != null) { 395 property.m_pub = topic.publish(); 396 property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsFloat(), time); 397 } 398 if (setter != null) { 399 property.m_sub = topic.subscribe(0.0f, PubSubOption.excludePublisher(property.m_pub)); 400 property.m_updateLocal = 401 sub -> { 402 for (float val : sub.readQueueValues()) { 403 setter.accept(val); 404 } 405 }; 406 } 407 m_properties.add(property); 408 } 409 410 @Override 411 public void publishConstFloat(String key, float value) { 412 Property<FloatPublisher, FloatSubscriber> property = new Property<>(); 413 FloatTopic topic = m_table.getFloatTopic(key); 414 property.m_pub = topic.publish(); 415 property.m_pub.set(value); 416 m_properties.add(property); 417 } 418 419 /** 420 * Add a double property. 421 * 422 * @param key property name 423 * @param getter getter function (returns current value) 424 * @param setter setter function (sets new value) 425 */ 426 @Override 427 public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) { 428 Property<DoublePublisher, DoubleSubscriber> property = new Property<>(); 429 DoubleTopic topic = m_table.getDoubleTopic(key); 430 if (getter != null) { 431 property.m_pub = topic.publish(); 432 property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsDouble(), time); 433 } 434 if (setter != null) { 435 property.m_sub = topic.subscribe(0.0, PubSubOption.excludePublisher(property.m_pub)); 436 property.m_updateLocal = 437 sub -> { 438 for (double val : sub.readQueueValues()) { 439 setter.accept(val); 440 } 441 }; 442 } 443 m_properties.add(property); 444 } 445 446 @Override 447 public void publishConstDouble(String key, double value) { 448 Property<DoublePublisher, DoubleSubscriber> property = new Property<>(); 449 DoubleTopic topic = m_table.getDoubleTopic(key); 450 property.m_pub = topic.publish(); 451 property.m_pub.set(value); 452 m_properties.add(property); 453 } 454 455 /** 456 * Add a string property. 457 * 458 * @param key property name 459 * @param getter getter function (returns current value) 460 * @param setter setter function (sets new value) 461 */ 462 @Override 463 public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) { 464 Property<StringPublisher, StringSubscriber> property = new Property<>(); 465 StringTopic topic = m_table.getStringTopic(key); 466 if (getter != null) { 467 property.m_pub = topic.publish(); 468 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 469 } 470 if (setter != null) { 471 property.m_sub = topic.subscribe("", PubSubOption.excludePublisher(property.m_pub)); 472 property.m_updateLocal = 473 sub -> { 474 for (String val : sub.readQueueValues()) { 475 setter.accept(val); 476 } 477 }; 478 } 479 m_properties.add(property); 480 } 481 482 @Override 483 public void publishConstString(String key, String value) { 484 Property<StringPublisher, StringSubscriber> property = new Property<>(); 485 StringTopic topic = m_table.getStringTopic(key); 486 property.m_pub = topic.publish(); 487 property.m_pub.set(value); 488 m_properties.add(property); 489 } 490 491 /** 492 * Add a boolean array property. 493 * 494 * @param key property name 495 * @param getter getter function (returns current value) 496 * @param setter setter function (sets new value) 497 */ 498 @Override 499 public void addBooleanArrayProperty( 500 String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter) { 501 Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>(); 502 BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key); 503 if (getter != null) { 504 property.m_pub = topic.publish(); 505 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 506 } 507 if (setter != null) { 508 property.m_sub = 509 topic.subscribe(new boolean[] {}, PubSubOption.excludePublisher(property.m_pub)); 510 property.m_updateLocal = 511 sub -> { 512 for (boolean[] val : sub.readQueueValues()) { 513 setter.accept(val); 514 } 515 }; 516 } 517 m_properties.add(property); 518 } 519 520 @Override 521 public void publishConstBooleanArray(String key, boolean[] value) { 522 Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>(); 523 BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key); 524 property.m_pub = topic.publish(); 525 property.m_pub.set(value); 526 m_properties.add(property); 527 } 528 529 /** 530 * Add an integer array property. 531 * 532 * @param key property name 533 * @param getter getter function (returns current value) 534 * @param setter setter function (sets new value) 535 */ 536 @Override 537 public void addIntegerArrayProperty( 538 String key, Supplier<long[]> getter, Consumer<long[]> setter) { 539 Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>(); 540 IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key); 541 if (getter != null) { 542 property.m_pub = topic.publish(); 543 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 544 } 545 if (setter != null) { 546 property.m_sub = 547 topic.subscribe(new long[] {}, PubSubOption.excludePublisher(property.m_pub)); 548 property.m_updateLocal = 549 sub -> { 550 for (long[] val : sub.readQueueValues()) { 551 setter.accept(val); 552 } 553 }; 554 } 555 m_properties.add(property); 556 } 557 558 @Override 559 public void publishConstIntegerArray(String key, long[] value) { 560 Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>(); 561 IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key); 562 property.m_pub = topic.publish(); 563 property.m_pub.set(value); 564 m_properties.add(property); 565 } 566 567 /** 568 * Add a float array property. 569 * 570 * @param key property name 571 * @param getter getter function (returns current value) 572 * @param setter setter function (sets new value) 573 */ 574 @Override 575 public void addFloatArrayProperty( 576 String key, Supplier<float[]> getter, Consumer<float[]> setter) { 577 Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>(); 578 FloatArrayTopic topic = m_table.getFloatArrayTopic(key); 579 if (getter != null) { 580 property.m_pub = topic.publish(); 581 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 582 } 583 if (setter != null) { 584 property.m_sub = 585 topic.subscribe(new float[] {}, PubSubOption.excludePublisher(property.m_pub)); 586 property.m_updateLocal = 587 sub -> { 588 for (float[] val : sub.readQueueValues()) { 589 setter.accept(val); 590 } 591 }; 592 } 593 m_properties.add(property); 594 } 595 596 @Override 597 public void publishConstFloatArray(String key, float[] value) { 598 Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>(); 599 FloatArrayTopic topic = m_table.getFloatArrayTopic(key); 600 property.m_pub = topic.publish(); 601 property.m_pub.set(value); 602 m_properties.add(property); 603 } 604 605 /** 606 * Add a double array property. 607 * 608 * @param key property name 609 * @param getter getter function (returns current value) 610 * @param setter setter function (sets new value) 611 */ 612 @Override 613 public void addDoubleArrayProperty( 614 String key, Supplier<double[]> getter, Consumer<double[]> setter) { 615 Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>(); 616 DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key); 617 if (getter != null) { 618 property.m_pub = topic.publish(); 619 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 620 } 621 if (setter != null) { 622 property.m_sub = 623 topic.subscribe(new double[] {}, PubSubOption.excludePublisher(property.m_pub)); 624 property.m_updateLocal = 625 sub -> { 626 for (double[] val : sub.readQueueValues()) { 627 setter.accept(val); 628 } 629 }; 630 } 631 m_properties.add(property); 632 } 633 634 @Override 635 public void publishConstDoubleArray(String key, double[] value) { 636 Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>(); 637 DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key); 638 property.m_pub = topic.publish(); 639 property.m_pub.set(value); 640 m_properties.add(property); 641 } 642 643 /** 644 * Add a string array property. 645 * 646 * @param key property name 647 * @param getter getter function (returns current value) 648 * @param setter setter function (sets new value) 649 */ 650 @Override 651 public void addStringArrayProperty( 652 String key, Supplier<String[]> getter, Consumer<String[]> setter) { 653 Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>(); 654 StringArrayTopic topic = m_table.getStringArrayTopic(key); 655 if (getter != null) { 656 property.m_pub = topic.publish(); 657 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 658 } 659 if (setter != null) { 660 property.m_sub = 661 topic.subscribe(new String[] {}, PubSubOption.excludePublisher(property.m_pub)); 662 property.m_updateLocal = 663 sub -> { 664 for (String[] val : sub.readQueueValues()) { 665 setter.accept(val); 666 } 667 }; 668 } 669 m_properties.add(property); 670 } 671 672 @Override 673 public void publishConstStringArray(String key, String[] value) { 674 Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>(); 675 StringArrayTopic topic = m_table.getStringArrayTopic(key); 676 property.m_pub = topic.publish(); 677 property.m_pub.set(value); 678 m_properties.add(property); 679 } 680 681 /** 682 * Add a raw property. 683 * 684 * @param key property name 685 * @param typeString type string 686 * @param getter getter function (returns current value) 687 * @param setter setter function (sets new value) 688 */ 689 @Override 690 public void addRawProperty( 691 String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter) { 692 Property<RawPublisher, RawSubscriber> property = new Property<>(); 693 RawTopic topic = m_table.getRawTopic(key); 694 if (getter != null) { 695 property.m_pub = topic.publish(typeString); 696 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 697 } 698 if (setter != null) { 699 property.m_sub = 700 topic.subscribe(typeString, new byte[] {}, PubSubOption.excludePublisher(property.m_pub)); 701 property.m_updateLocal = 702 sub -> { 703 for (byte[] val : sub.readQueueValues()) { 704 setter.accept(val); 705 } 706 }; 707 } 708 m_properties.add(property); 709 } 710 711 @Override 712 public void publishConstRaw(String key, String typestring, byte[] value) { 713 Property<RawPublisher, RawSubscriber> property = new Property<>(); 714 RawTopic topic = m_table.getRawTopic(key); 715 property.m_pub = topic.publish(typestring); 716 property.m_pub.set(value); 717 m_properties.add(property); 718 } 719}