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