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