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. */ 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 final 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 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 /** Clear properties. */ 208 @Override 209 public void clearProperties() { 210 stopListeners(); 211 for (Property<?, ?> property : m_properties) { 212 property.close(); 213 } 214 m_properties.clear(); 215 } 216 217 @Override 218 public void addCloseable(AutoCloseable closeable) { 219 m_closeables.add(closeable); 220 } 221 222 /** 223 * Set the string representation of the named data type that will be used by the smart dashboard 224 * for this sendable. 225 * 226 * @param type data type 227 */ 228 @Override 229 public void setSmartDashboardType(String type) { 230 if (m_typePub == null) { 231 m_typePub = 232 m_table 233 .getStringTopic(".type") 234 .publishEx(StringTopic.kTypeString, "{\"SmartDashboard\":\"" + type + "\"}"); 235 } 236 m_typePub.set(type); 237 } 238 239 /** 240 * Set a flag indicating if this sendable should be treated as an actuator. By default, this flag 241 * is false. 242 * 243 * @param value true if actuator, false if not 244 */ 245 @Override 246 public void setActuator(boolean value) { 247 if (m_actuatorPub == null) { 248 m_actuatorPub = m_table.getBooleanTopic(".actuator").publish(); 249 } 250 m_actuatorPub.set(value); 251 m_actuator = value; 252 } 253 254 /** 255 * Set the function that should be called to update the network table for things other than 256 * properties. Note this function is not passed the network table object; instead it should use 257 * the topics returned by getTopic(). 258 * 259 * @param func function 260 */ 261 @Override 262 public void setUpdateTable(Runnable func) { 263 m_updateTables.add(func); 264 } 265 266 /** 267 * Add a property without getters or setters. This can be used to get entry handles for the 268 * function called by setUpdateTable(). 269 * 270 * @param key property name 271 * @return Network table entry 272 */ 273 @Override 274 public Topic getTopic(String key) { 275 return m_table.getTopic(key); 276 } 277 278 /** 279 * Add a boolean property. 280 * 281 * @param key property name 282 * @param getter getter function (returns current value) 283 * @param setter setter function (sets new value) 284 */ 285 @Override 286 public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) { 287 Property<BooleanPublisher, BooleanSubscriber> property = new Property<>(); 288 BooleanTopic topic = m_table.getBooleanTopic(key); 289 if (getter != null) { 290 property.m_pub = topic.publish(); 291 property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsBoolean(), time); 292 } 293 if (setter != null) { 294 property.m_sub = topic.subscribe(false, PubSubOption.excludePublisher(property.m_pub)); 295 property.m_updateLocal = 296 sub -> { 297 for (boolean val : sub.readQueueValues()) { 298 setter.accept(val); 299 } 300 }; 301 } 302 m_properties.add(property); 303 } 304 305 @Override 306 public void publishConstBoolean(String key, boolean value) { 307 Property<BooleanPublisher, BooleanSubscriber> property = new Property<>(); 308 BooleanTopic topic = m_table.getBooleanTopic(key); 309 property.m_pub = topic.publish(); 310 property.m_pub.set(value); 311 m_properties.add(property); 312 } 313 314 /** 315 * Add an integer property. 316 * 317 * @param key property name 318 * @param getter getter function (returns current value) 319 * @param setter setter function (sets new value) 320 */ 321 @Override 322 public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) { 323 Property<IntegerPublisher, IntegerSubscriber> property = new Property<>(); 324 IntegerTopic topic = m_table.getIntegerTopic(key); 325 if (getter != null) { 326 property.m_pub = topic.publish(); 327 property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsLong(), time); 328 } 329 if (setter != null) { 330 property.m_sub = topic.subscribe(0, PubSubOption.excludePublisher(property.m_pub)); 331 property.m_updateLocal = 332 sub -> { 333 for (long val : sub.readQueueValues()) { 334 setter.accept(val); 335 } 336 }; 337 } 338 m_properties.add(property); 339 } 340 341 @Override 342 public void publishConstInteger(String key, long value) { 343 Property<IntegerPublisher, IntegerSubscriber> property = new Property<>(); 344 IntegerTopic topic = m_table.getIntegerTopic(key); 345 property.m_pub = topic.publish(); 346 property.m_pub.set(value); 347 m_properties.add(property); 348 } 349 350 /** 351 * Add a float property. 352 * 353 * @param key property name 354 * @param getter getter function (returns current value) 355 * @param setter setter function (sets new value) 356 */ 357 @Override 358 public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) { 359 Property<FloatPublisher, FloatSubscriber> property = new Property<>(); 360 FloatTopic topic = m_table.getFloatTopic(key); 361 if (getter != null) { 362 property.m_pub = topic.publish(); 363 property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsFloat(), time); 364 } 365 if (setter != null) { 366 property.m_sub = topic.subscribe(0.0f, PubSubOption.excludePublisher(property.m_pub)); 367 property.m_updateLocal = 368 sub -> { 369 for (float val : sub.readQueueValues()) { 370 setter.accept(val); 371 } 372 }; 373 } 374 m_properties.add(property); 375 } 376 377 @Override 378 public void publishConstFloat(String key, float value) { 379 Property<FloatPublisher, FloatSubscriber> property = new Property<>(); 380 FloatTopic topic = m_table.getFloatTopic(key); 381 property.m_pub = topic.publish(); 382 property.m_pub.set(value); 383 m_properties.add(property); 384 } 385 386 /** 387 * Add a double property. 388 * 389 * @param key property name 390 * @param getter getter function (returns current value) 391 * @param setter setter function (sets new value) 392 */ 393 @Override 394 public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) { 395 Property<DoublePublisher, DoubleSubscriber> property = new Property<>(); 396 DoubleTopic topic = m_table.getDoubleTopic(key); 397 if (getter != null) { 398 property.m_pub = topic.publish(); 399 property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsDouble(), time); 400 } 401 if (setter != null) { 402 property.m_sub = topic.subscribe(0.0, PubSubOption.excludePublisher(property.m_pub)); 403 property.m_updateLocal = 404 sub -> { 405 for (double val : sub.readQueueValues()) { 406 setter.accept(val); 407 } 408 }; 409 } 410 m_properties.add(property); 411 } 412 413 @Override 414 public void publishConstDouble(String key, double value) { 415 Property<DoublePublisher, DoubleSubscriber> property = new Property<>(); 416 DoubleTopic topic = m_table.getDoubleTopic(key); 417 property.m_pub = topic.publish(); 418 property.m_pub.set(value); 419 m_properties.add(property); 420 } 421 422 /** 423 * Add a string property. 424 * 425 * @param key property name 426 * @param getter getter function (returns current value) 427 * @param setter setter function (sets new value) 428 */ 429 @Override 430 public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) { 431 Property<StringPublisher, StringSubscriber> property = new Property<>(); 432 StringTopic topic = m_table.getStringTopic(key); 433 if (getter != null) { 434 property.m_pub = topic.publish(); 435 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 436 } 437 if (setter != null) { 438 property.m_sub = topic.subscribe("", PubSubOption.excludePublisher(property.m_pub)); 439 property.m_updateLocal = 440 sub -> { 441 for (String val : sub.readQueueValues()) { 442 setter.accept(val); 443 } 444 }; 445 } 446 m_properties.add(property); 447 } 448 449 @Override 450 public void publishConstString(String key, String value) { 451 Property<StringPublisher, StringSubscriber> property = new Property<>(); 452 StringTopic topic = m_table.getStringTopic(key); 453 property.m_pub = topic.publish(); 454 property.m_pub.set(value); 455 m_properties.add(property); 456 } 457 458 /** 459 * Add a boolean array property. 460 * 461 * @param key property name 462 * @param getter getter function (returns current value) 463 * @param setter setter function (sets new value) 464 */ 465 @Override 466 public void addBooleanArrayProperty( 467 String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter) { 468 Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>(); 469 BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key); 470 if (getter != null) { 471 property.m_pub = topic.publish(); 472 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 473 } 474 if (setter != null) { 475 property.m_sub = 476 topic.subscribe(new boolean[] {}, PubSubOption.excludePublisher(property.m_pub)); 477 property.m_updateLocal = 478 sub -> { 479 for (boolean[] val : sub.readQueueValues()) { 480 setter.accept(val); 481 } 482 }; 483 } 484 m_properties.add(property); 485 } 486 487 @Override 488 public void publishConstBooleanArray(String key, boolean[] value) { 489 Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>(); 490 BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key); 491 property.m_pub = topic.publish(); 492 property.m_pub.set(value); 493 m_properties.add(property); 494 } 495 496 /** 497 * Add an integer array property. 498 * 499 * @param key property name 500 * @param getter getter function (returns current value) 501 * @param setter setter function (sets new value) 502 */ 503 @Override 504 public void addIntegerArrayProperty( 505 String key, Supplier<long[]> getter, Consumer<long[]> setter) { 506 Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>(); 507 IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key); 508 if (getter != null) { 509 property.m_pub = topic.publish(); 510 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 511 } 512 if (setter != null) { 513 property.m_sub = 514 topic.subscribe(new long[] {}, PubSubOption.excludePublisher(property.m_pub)); 515 property.m_updateLocal = 516 sub -> { 517 for (long[] val : sub.readQueueValues()) { 518 setter.accept(val); 519 } 520 }; 521 } 522 m_properties.add(property); 523 } 524 525 @Override 526 public void publishConstIntegerArray(String key, long[] value) { 527 Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>(); 528 IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key); 529 property.m_pub = topic.publish(); 530 property.m_pub.set(value); 531 m_properties.add(property); 532 } 533 534 /** 535 * Add a float array property. 536 * 537 * @param key property name 538 * @param getter getter function (returns current value) 539 * @param setter setter function (sets new value) 540 */ 541 @Override 542 public void addFloatArrayProperty( 543 String key, Supplier<float[]> getter, Consumer<float[]> setter) { 544 Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>(); 545 FloatArrayTopic topic = m_table.getFloatArrayTopic(key); 546 if (getter != null) { 547 property.m_pub = topic.publish(); 548 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 549 } 550 if (setter != null) { 551 property.m_sub = 552 topic.subscribe(new float[] {}, PubSubOption.excludePublisher(property.m_pub)); 553 property.m_updateLocal = 554 sub -> { 555 for (float[] val : sub.readQueueValues()) { 556 setter.accept(val); 557 } 558 }; 559 } 560 m_properties.add(property); 561 } 562 563 @Override 564 public void publishConstFloatArray(String key, float[] value) { 565 Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>(); 566 FloatArrayTopic topic = m_table.getFloatArrayTopic(key); 567 property.m_pub = topic.publish(); 568 property.m_pub.set(value); 569 m_properties.add(property); 570 } 571 572 /** 573 * Add a double array property. 574 * 575 * @param key property name 576 * @param getter getter function (returns current value) 577 * @param setter setter function (sets new value) 578 */ 579 @Override 580 public void addDoubleArrayProperty( 581 String key, Supplier<double[]> getter, Consumer<double[]> setter) { 582 Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>(); 583 DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key); 584 if (getter != null) { 585 property.m_pub = topic.publish(); 586 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 587 } 588 if (setter != null) { 589 property.m_sub = 590 topic.subscribe(new double[] {}, PubSubOption.excludePublisher(property.m_pub)); 591 property.m_updateLocal = 592 sub -> { 593 for (double[] val : sub.readQueueValues()) { 594 setter.accept(val); 595 } 596 }; 597 } 598 m_properties.add(property); 599 } 600 601 @Override 602 public void publishConstDoubleArray(String key, double[] value) { 603 Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>(); 604 DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key); 605 property.m_pub = topic.publish(); 606 property.m_pub.set(value); 607 m_properties.add(property); 608 } 609 610 /** 611 * Add a string array property. 612 * 613 * @param key property name 614 * @param getter getter function (returns current value) 615 * @param setter setter function (sets new value) 616 */ 617 @Override 618 public void addStringArrayProperty( 619 String key, Supplier<String[]> getter, Consumer<String[]> setter) { 620 Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>(); 621 StringArrayTopic topic = m_table.getStringArrayTopic(key); 622 if (getter != null) { 623 property.m_pub = topic.publish(); 624 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 625 } 626 if (setter != null) { 627 property.m_sub = 628 topic.subscribe(new String[] {}, PubSubOption.excludePublisher(property.m_pub)); 629 property.m_updateLocal = 630 sub -> { 631 for (String[] val : sub.readQueueValues()) { 632 setter.accept(val); 633 } 634 }; 635 } 636 m_properties.add(property); 637 } 638 639 @Override 640 public void publishConstStringArray(String key, String[] value) { 641 Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>(); 642 StringArrayTopic topic = m_table.getStringArrayTopic(key); 643 property.m_pub = topic.publish(); 644 property.m_pub.set(value); 645 m_properties.add(property); 646 } 647 648 /** 649 * Add a raw property. 650 * 651 * @param key property name 652 * @param typeString type string 653 * @param getter getter function (returns current value) 654 * @param setter setter function (sets new value) 655 */ 656 @Override 657 public void addRawProperty( 658 String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter) { 659 Property<RawPublisher, RawSubscriber> property = new Property<>(); 660 RawTopic topic = m_table.getRawTopic(key); 661 if (getter != null) { 662 property.m_pub = topic.publish(typeString); 663 property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time); 664 } 665 if (setter != null) { 666 property.m_sub = 667 topic.subscribe(typeString, new byte[] {}, PubSubOption.excludePublisher(property.m_pub)); 668 property.m_updateLocal = 669 sub -> { 670 for (byte[] val : sub.readQueueValues()) { 671 setter.accept(val); 672 } 673 }; 674 } 675 m_properties.add(property); 676 } 677 678 @Override 679 public void publishConstRaw(String key, String typestring, byte[] value) { 680 Property<RawPublisher, RawSubscriber> property = new Property<>(); 681 RawTopic topic = m_table.getRawTopic(key); 682 property.m_pub = topic.publish(typestring); 683 property.m_pub.set(value); 684 m_properties.add(property); 685 } 686}