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