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.networktables; 006 007import edu.wpi.first.util.WPIUtilJNI; 008import edu.wpi.first.util.concurrent.Event; 009import edu.wpi.first.util.datalog.DataLog; 010import java.util.EnumSet; 011import java.util.HashMap; 012import java.util.Map; 013import java.util.OptionalLong; 014import java.util.concurrent.ConcurrentHashMap; 015import java.util.concurrent.ConcurrentMap; 016import java.util.concurrent.TimeUnit; 017import java.util.concurrent.locks.Condition; 018import java.util.concurrent.locks.ReentrantLock; 019import java.util.function.Consumer; 020 021/** 022 * NetworkTables Instance. 023 * 024 * <p>Instances are completely independent from each other. Table operations on one instance will 025 * not be visible to other instances unless the instances are connected via the network. The main 026 * limitation on instances is that you cannot have two servers on the same network port. The main 027 * utility of instances is for unit testing, but they can also enable one program to connect to two 028 * different NetworkTables networks. 029 * 030 * <p>The global "default" instance (as returned by {@link #getDefault()}) is always available, and 031 * is intended for the common case when there is only a single NetworkTables instance being used in 032 * the program. 033 * 034 * <p>Additional instances can be created with the {@link #create()} function. A reference must be 035 * kept to the NetworkTableInstance returned by this function to keep it from being garbage 036 * collected. 037 */ 038@SuppressWarnings("PMD.CouplingBetweenObjects") 039public final class NetworkTableInstance implements AutoCloseable { 040 /** Client/server mode flag values (as returned by {@link #getNetworkMode()}). */ 041 public enum NetworkMode { 042 /** Running in server mode. */ 043 kServer(0x01), 044 045 /** Running in NT3 client mode. */ 046 kClient3(0x02), 047 048 /** Running in NT4 client mode. */ 049 kClient4(0x04), 050 051 /** Currently starting up (either client or server). */ 052 kStarting(0x08), 053 054 /** Running in local-only mode. */ 055 kLocal(0x10); 056 057 private final int value; 058 059 NetworkMode(int value) { 060 this.value = value; 061 } 062 063 public int getValue() { 064 return value; 065 } 066 } 067 068 /** The default port that network tables operates on for NT3. */ 069 public static final int kDefaultPort3 = 1735; 070 071 /** The default port that network tables operates on for NT4. */ 072 public static final int kDefaultPort4 = 5810; 073 074 /** 075 * Construct from native handle. 076 * 077 * @param handle Native handle 078 */ 079 private NetworkTableInstance(int handle) { 080 m_owned = false; 081 m_handle = handle; 082 } 083 084 /** Destroys the instance (if created by {@link #create()}). */ 085 @Override 086 public synchronized void close() { 087 if (m_owned && m_handle != 0) { 088 m_listeners.close(); 089 NetworkTablesJNI.destroyInstance(m_handle); 090 m_handle = 0; 091 } 092 } 093 094 /** 095 * Determines if the native handle is valid. 096 * 097 * @return True if the native handle is valid, false otherwise. 098 */ 099 public boolean isValid() { 100 return m_handle != 0; 101 } 102 103 /* The default instance. */ 104 private static NetworkTableInstance s_defaultInstance; 105 106 /** 107 * Get global default instance. 108 * 109 * @return Global default instance 110 */ 111 public static synchronized NetworkTableInstance getDefault() { 112 if (s_defaultInstance == null) { 113 s_defaultInstance = new NetworkTableInstance(NetworkTablesJNI.getDefaultInstance()); 114 } 115 return s_defaultInstance; 116 } 117 118 /** 119 * Create an instance. Note: A reference to the returned instance must be retained to ensure the 120 * instance is not garbage collected. 121 * 122 * @return Newly created instance 123 */ 124 public static NetworkTableInstance create() { 125 NetworkTableInstance inst = new NetworkTableInstance(NetworkTablesJNI.createInstance()); 126 inst.m_owned = true; 127 return inst; 128 } 129 130 /** 131 * Gets the native handle for the instance. 132 * 133 * @return Native handle 134 */ 135 public int getHandle() { 136 return m_handle; 137 } 138 139 /** 140 * Get (generic) topic. 141 * 142 * @param name topic name 143 * @return Topic 144 */ 145 public Topic getTopic(String name) { 146 Topic topic = m_topics.get(name); 147 if (topic == null) { 148 int handle = NetworkTablesJNI.getTopic(m_handle, name); 149 topic = new Topic(this, handle); 150 Topic oldTopic = m_topics.putIfAbsent(name, topic); 151 if (oldTopic != null) { 152 topic = oldTopic; 153 } 154 // also cache by handle 155 m_topicsByHandle.putIfAbsent(handle, topic); 156 } 157 return topic; 158 } 159 160 /** 161 * Get boolean topic. 162 * 163 * @param name topic name 164 * @return BooleanTopic 165 */ 166 public BooleanTopic getBooleanTopic(String name) { 167 Topic topic = m_topics.get(name); 168 if (topic instanceof BooleanTopic) { 169 return (BooleanTopic) topic; 170 } 171 172 int handle; 173 if (topic == null) { 174 handle = NetworkTablesJNI.getTopic(m_handle, name); 175 } else { 176 handle = topic.getHandle(); 177 } 178 179 topic = new BooleanTopic(this, handle); 180 m_topics.put(name, topic); 181 182 // also cache by handle 183 m_topicsByHandle.put(handle, topic); 184 185 return (BooleanTopic) topic; 186 } 187 188 /** 189 * Get long topic. 190 * 191 * @param name topic name 192 * @return IntegerTopic 193 */ 194 public IntegerTopic getIntegerTopic(String name) { 195 Topic topic = m_topics.get(name); 196 if (topic instanceof IntegerTopic) { 197 return (IntegerTopic) topic; 198 } 199 200 int handle; 201 if (topic == null) { 202 handle = NetworkTablesJNI.getTopic(m_handle, name); 203 } else { 204 handle = topic.getHandle(); 205 } 206 207 topic = new IntegerTopic(this, handle); 208 m_topics.put(name, topic); 209 210 // also cache by handle 211 m_topicsByHandle.put(handle, topic); 212 213 return (IntegerTopic) topic; 214 } 215 216 /** 217 * Get float topic. 218 * 219 * @param name topic name 220 * @return FloatTopic 221 */ 222 public FloatTopic getFloatTopic(String name) { 223 Topic topic = m_topics.get(name); 224 if (topic instanceof FloatTopic) { 225 return (FloatTopic) topic; 226 } 227 228 int handle; 229 if (topic == null) { 230 handle = NetworkTablesJNI.getTopic(m_handle, name); 231 } else { 232 handle = topic.getHandle(); 233 } 234 235 topic = new FloatTopic(this, handle); 236 m_topics.put(name, topic); 237 238 // also cache by handle 239 m_topicsByHandle.put(handle, topic); 240 241 return (FloatTopic) topic; 242 } 243 244 /** 245 * Get double topic. 246 * 247 * @param name topic name 248 * @return DoubleTopic 249 */ 250 public DoubleTopic getDoubleTopic(String name) { 251 Topic topic = m_topics.get(name); 252 if (topic instanceof DoubleTopic) { 253 return (DoubleTopic) topic; 254 } 255 256 int handle; 257 if (topic == null) { 258 handle = NetworkTablesJNI.getTopic(m_handle, name); 259 } else { 260 handle = topic.getHandle(); 261 } 262 263 topic = new DoubleTopic(this, handle); 264 m_topics.put(name, topic); 265 266 // also cache by handle 267 m_topicsByHandle.put(handle, topic); 268 269 return (DoubleTopic) topic; 270 } 271 272 /** 273 * Get String topic. 274 * 275 * @param name topic name 276 * @return StringTopic 277 */ 278 public StringTopic getStringTopic(String name) { 279 Topic topic = m_topics.get(name); 280 if (topic instanceof StringTopic) { 281 return (StringTopic) topic; 282 } 283 284 int handle; 285 if (topic == null) { 286 handle = NetworkTablesJNI.getTopic(m_handle, name); 287 } else { 288 handle = topic.getHandle(); 289 } 290 291 topic = new StringTopic(this, handle); 292 m_topics.put(name, topic); 293 294 // also cache by handle 295 m_topicsByHandle.put(handle, topic); 296 297 return (StringTopic) topic; 298 } 299 300 /** 301 * Get byte[] topic. 302 * 303 * @param name topic name 304 * @return RawTopic 305 */ 306 public RawTopic getRawTopic(String name) { 307 Topic topic = m_topics.get(name); 308 if (topic instanceof RawTopic) { 309 return (RawTopic) topic; 310 } 311 312 int handle; 313 if (topic == null) { 314 handle = NetworkTablesJNI.getTopic(m_handle, name); 315 } else { 316 handle = topic.getHandle(); 317 } 318 319 topic = new RawTopic(this, handle); 320 m_topics.put(name, topic); 321 322 // also cache by handle 323 m_topicsByHandle.put(handle, topic); 324 325 return (RawTopic) topic; 326 } 327 328 /** 329 * Get boolean[] topic. 330 * 331 * @param name topic name 332 * @return BooleanArrayTopic 333 */ 334 public BooleanArrayTopic getBooleanArrayTopic(String name) { 335 Topic topic = m_topics.get(name); 336 if (topic instanceof BooleanArrayTopic) { 337 return (BooleanArrayTopic) topic; 338 } 339 340 int handle; 341 if (topic == null) { 342 handle = NetworkTablesJNI.getTopic(m_handle, name); 343 } else { 344 handle = topic.getHandle(); 345 } 346 347 topic = new BooleanArrayTopic(this, handle); 348 m_topics.put(name, topic); 349 350 // also cache by handle 351 m_topicsByHandle.put(handle, topic); 352 353 return (BooleanArrayTopic) topic; 354 } 355 356 /** 357 * Get long[] topic. 358 * 359 * @param name topic name 360 * @return IntegerArrayTopic 361 */ 362 public IntegerArrayTopic getIntegerArrayTopic(String name) { 363 Topic topic = m_topics.get(name); 364 if (topic instanceof IntegerArrayTopic) { 365 return (IntegerArrayTopic) topic; 366 } 367 368 int handle; 369 if (topic == null) { 370 handle = NetworkTablesJNI.getTopic(m_handle, name); 371 } else { 372 handle = topic.getHandle(); 373 } 374 375 topic = new IntegerArrayTopic(this, handle); 376 m_topics.put(name, topic); 377 378 // also cache by handle 379 m_topicsByHandle.put(handle, topic); 380 381 return (IntegerArrayTopic) topic; 382 } 383 384 /** 385 * Get float[] topic. 386 * 387 * @param name topic name 388 * @return FloatArrayTopic 389 */ 390 public FloatArrayTopic getFloatArrayTopic(String name) { 391 Topic topic = m_topics.get(name); 392 if (topic instanceof FloatArrayTopic) { 393 return (FloatArrayTopic) topic; 394 } 395 396 int handle; 397 if (topic == null) { 398 handle = NetworkTablesJNI.getTopic(m_handle, name); 399 } else { 400 handle = topic.getHandle(); 401 } 402 403 topic = new FloatArrayTopic(this, handle); 404 m_topics.put(name, topic); 405 406 // also cache by handle 407 m_topicsByHandle.put(handle, topic); 408 409 return (FloatArrayTopic) topic; 410 } 411 412 /** 413 * Get double[] topic. 414 * 415 * @param name topic name 416 * @return DoubleArrayTopic 417 */ 418 public DoubleArrayTopic getDoubleArrayTopic(String name) { 419 Topic topic = m_topics.get(name); 420 if (topic instanceof DoubleArrayTopic) { 421 return (DoubleArrayTopic) topic; 422 } 423 424 int handle; 425 if (topic == null) { 426 handle = NetworkTablesJNI.getTopic(m_handle, name); 427 } else { 428 handle = topic.getHandle(); 429 } 430 431 topic = new DoubleArrayTopic(this, handle); 432 m_topics.put(name, topic); 433 434 // also cache by handle 435 m_topicsByHandle.put(handle, topic); 436 437 return (DoubleArrayTopic) topic; 438 } 439 440 /** 441 * Get String[] topic. 442 * 443 * @param name topic name 444 * @return StringArrayTopic 445 */ 446 public StringArrayTopic getStringArrayTopic(String name) { 447 Topic topic = m_topics.get(name); 448 if (topic instanceof StringArrayTopic) { 449 return (StringArrayTopic) topic; 450 } 451 452 int handle; 453 if (topic == null) { 454 handle = NetworkTablesJNI.getTopic(m_handle, name); 455 } else { 456 handle = topic.getHandle(); 457 } 458 459 topic = new StringArrayTopic(this, handle); 460 m_topics.put(name, topic); 461 462 // also cache by handle 463 m_topicsByHandle.put(handle, topic); 464 465 return (StringArrayTopic) topic; 466 } 467 468 private Topic[] topicHandlesToTopics(int[] handles) { 469 Topic[] topics = new Topic[handles.length]; 470 for (int i = 0; i < handles.length; i++) { 471 topics[i] = getCachedTopic(handles[i]); 472 } 473 return topics; 474 } 475 476 /** 477 * Get all published topics. 478 * 479 * @return Array of topics. 480 */ 481 public Topic[] getTopics() { 482 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, "", 0)); 483 } 484 485 /** 486 * Get published topics starting with the given prefix. The results are optionally filtered by 487 * string prefix to only return a subset of all topics. 488 * 489 * @param prefix topic name required prefix; only topics whose name starts with this string are 490 * returned 491 * @return Array of topic information. 492 */ 493 public Topic[] getTopics(String prefix) { 494 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, prefix, 0)); 495 } 496 497 /** 498 * Get published topics starting with the given prefix. The results are optionally filtered by 499 * string prefix and data type to only return a subset of all topics. 500 * 501 * @param prefix topic name required prefix; only topics whose name starts with this string are 502 * returned 503 * @param types bitmask of data types; 0 is treated as a "don't care" 504 * @return Array of topic information. 505 */ 506 public Topic[] getTopics(String prefix, int types) { 507 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, prefix, types)); 508 } 509 510 /** 511 * Get published topics starting with the given prefix. The results are optionally filtered by 512 * string prefix and data type to only return a subset of all topics. 513 * 514 * @param prefix topic name required prefix; only topics whose name starts with this string are 515 * returned 516 * @param types array of data type strings 517 * @return Array of topic information. 518 */ 519 public Topic[] getTopics(String prefix, String[] types) { 520 return topicHandlesToTopics(NetworkTablesJNI.getTopicsStr(m_handle, prefix, types)); 521 } 522 523 /** 524 * Get information about all topics. 525 * 526 * @return Array of topic information. 527 */ 528 public TopicInfo[] getTopicInfo() { 529 return NetworkTablesJNI.getTopicInfos(this, m_handle, "", 0); 530 } 531 532 /** 533 * Get information about topics starting with the given prefix. The results are optionally 534 * filtered by string prefix to only return a subset of all topics. 535 * 536 * @param prefix topic name required prefix; only topics whose name starts with this string are 537 * returned 538 * @return Array of topic information. 539 */ 540 public TopicInfo[] getTopicInfo(String prefix) { 541 return NetworkTablesJNI.getTopicInfos(this, m_handle, prefix, 0); 542 } 543 544 /** 545 * Get information about topics starting with the given prefix. The results are optionally 546 * filtered by string prefix and data type to only return a subset of all topics. 547 * 548 * @param prefix topic name required prefix; only topics whose name starts with this string are 549 * returned 550 * @param types bitmask of data types; 0 is treated as a "don't care" 551 * @return Array of topic information. 552 */ 553 public TopicInfo[] getTopicInfo(String prefix, int types) { 554 return NetworkTablesJNI.getTopicInfos(this, m_handle, prefix, types); 555 } 556 557 /** 558 * Get information about topics starting with the given prefix. The results are optionally 559 * filtered by string prefix and data type to only return a subset of all topics. 560 * 561 * @param prefix topic name required prefix; only topics whose name starts with this string are 562 * returned 563 * @param types array of data type strings 564 * @return Array of topic information. 565 */ 566 public TopicInfo[] getTopicInfo(String prefix, String[] types) { 567 return NetworkTablesJNI.getTopicInfosStr(this, m_handle, prefix, types); 568 } 569 570 /* Cache of created entries. */ 571 private final ConcurrentMap<String, NetworkTableEntry> m_entries = new ConcurrentHashMap<>(); 572 573 /** 574 * Gets the entry for a key. 575 * 576 * @param name Key 577 * @return Network table entry. 578 */ 579 public NetworkTableEntry getEntry(String name) { 580 NetworkTableEntry entry = m_entries.get(name); 581 if (entry == null) { 582 entry = new NetworkTableEntry(this, NetworkTablesJNI.getEntry(m_handle, name)); 583 NetworkTableEntry oldEntry = m_entries.putIfAbsent(name, entry); 584 if (oldEntry != null) { 585 entry = oldEntry; 586 } 587 } 588 return entry; 589 } 590 591 /* Cache of created topics. */ 592 private final ConcurrentMap<String, Topic> m_topics = new ConcurrentHashMap<>(); 593 private final ConcurrentMap<Integer, Topic> m_topicsByHandle = new ConcurrentHashMap<>(); 594 595 Topic getCachedTopic(String name) { 596 Topic topic = m_topics.get(name); 597 if (topic == null) { 598 int handle = NetworkTablesJNI.getTopic(m_handle, name); 599 topic = new Topic(this, handle); 600 Topic oldTopic = m_topics.putIfAbsent(name, topic); 601 if (oldTopic != null) { 602 topic = oldTopic; 603 } 604 // also cache by handle 605 m_topicsByHandle.putIfAbsent(handle, topic); 606 } 607 return topic; 608 } 609 610 Topic getCachedTopic(int handle) { 611 Topic topic = m_topicsByHandle.get(handle); 612 if (topic == null) { 613 topic = new Topic(this, handle); 614 Topic oldTopic = m_topicsByHandle.putIfAbsent(handle, topic); 615 if (oldTopic != null) { 616 topic = oldTopic; 617 } 618 } 619 return topic; 620 } 621 622 /* Cache of created tables. */ 623 private final ConcurrentMap<String, NetworkTable> m_tables = new ConcurrentHashMap<>(); 624 625 /** 626 * Gets the table with the specified key. 627 * 628 * @param key the key name 629 * @return The network table 630 */ 631 public NetworkTable getTable(String key) { 632 // prepend leading / if not present 633 String theKey; 634 if (key.isEmpty() || "/".equals(key)) { 635 theKey = ""; 636 } else if (key.charAt(0) == NetworkTable.PATH_SEPARATOR) { 637 theKey = key; 638 } else { 639 theKey = NetworkTable.PATH_SEPARATOR + key; 640 } 641 642 // cache created tables 643 NetworkTable table = m_tables.get(theKey); 644 if (table == null) { 645 table = new NetworkTable(this, theKey); 646 NetworkTable oldTable = m_tables.putIfAbsent(theKey, table); 647 if (oldTable != null) { 648 table = oldTable; 649 } 650 } 651 return table; 652 } 653 654 /* 655 * Callback Creation Functions 656 */ 657 658 private static class ListenerStorage implements AutoCloseable { 659 private final ReentrantLock m_lock = new ReentrantLock(); 660 private final Map<Integer, Consumer<NetworkTableEvent>> m_listeners = new HashMap<>(); 661 private Thread m_thread; 662 private int m_poller; 663 private boolean m_waitQueue; 664 private final Event m_waitQueueEvent = new Event(); 665 private final Condition m_waitQueueCond = m_lock.newCondition(); 666 private final NetworkTableInstance m_inst; 667 668 ListenerStorage(NetworkTableInstance inst) { 669 m_inst = inst; 670 } 671 672 int add( 673 String[] prefixes, 674 EnumSet<NetworkTableEvent.Kind> eventKinds, 675 Consumer<NetworkTableEvent> listener) { 676 m_lock.lock(); 677 try { 678 if (m_poller == 0) { 679 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 680 startThread(); 681 } 682 int h = NetworkTablesJNI.addListener(m_poller, prefixes, eventKinds); 683 m_listeners.put(h, listener); 684 return h; 685 } finally { 686 m_lock.unlock(); 687 } 688 } 689 690 int add( 691 int handle, 692 EnumSet<NetworkTableEvent.Kind> eventKinds, 693 Consumer<NetworkTableEvent> listener) { 694 m_lock.lock(); 695 try { 696 if (m_poller == 0) { 697 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 698 startThread(); 699 } 700 int h = NetworkTablesJNI.addListener(m_poller, handle, eventKinds); 701 m_listeners.put(h, listener); 702 return h; 703 } finally { 704 m_lock.unlock(); 705 } 706 } 707 708 int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> listener) { 709 m_lock.lock(); 710 try { 711 if (m_poller == 0) { 712 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 713 startThread(); 714 } 715 int h = NetworkTablesJNI.addLogger(m_poller, minLevel, maxLevel); 716 m_listeners.put(h, listener); 717 return h; 718 } finally { 719 m_lock.unlock(); 720 } 721 } 722 723 void remove(int listener) { 724 m_lock.lock(); 725 try { 726 m_listeners.remove(listener); 727 } finally { 728 m_lock.unlock(); 729 } 730 NetworkTablesJNI.removeListener(listener); 731 } 732 733 @Override 734 public void close() { 735 if (m_poller != 0) { 736 NetworkTablesJNI.destroyListenerPoller(m_poller); 737 } 738 m_poller = 0; 739 } 740 741 private void startThread() { 742 m_thread = 743 new Thread( 744 () -> { 745 boolean wasInterrupted = false; 746 int[] handles = new int[] { m_poller, m_waitQueueEvent.getHandle() }; 747 while (!Thread.interrupted()) { 748 try { 749 WPIUtilJNI.waitForObjects(handles); 750 } catch (InterruptedException ex) { 751 m_lock.lock(); 752 try { 753 if (m_waitQueue) { 754 m_waitQueue = false; 755 m_waitQueueCond.signalAll(); 756 } 757 } finally { 758 m_lock.unlock(); 759 } 760 Thread.currentThread().interrupt(); 761 // don't try to destroy poller, as its handle is likely no longer valid 762 wasInterrupted = true; 763 break; 764 } 765 for (NetworkTableEvent event : 766 NetworkTablesJNI.readListenerQueue(m_inst, m_poller)) { 767 Consumer<NetworkTableEvent> listener; 768 m_lock.lock(); 769 try { 770 listener = m_listeners.get(event.listener); 771 } finally { 772 m_lock.unlock(); 773 } 774 if (listener != null) { 775 try { 776 listener.accept(event); 777 } catch (Throwable throwable) { 778 System.err.println( 779 "Unhandled exception during listener callback: " 780 + throwable.toString()); 781 throwable.printStackTrace(); 782 } 783 } 784 } 785 m_lock.lock(); 786 try { 787 if (m_waitQueue) { 788 m_waitQueue = false; 789 m_waitQueueCond.signalAll(); 790 } 791 } finally { 792 m_lock.unlock(); 793 } 794 } 795 m_lock.lock(); 796 try { 797 if (!wasInterrupted) { 798 NetworkTablesJNI.destroyListenerPoller(m_poller); 799 } 800 m_poller = 0; 801 } finally { 802 m_lock.unlock(); 803 } 804 }, 805 "NTListener"); 806 m_thread.setDaemon(true); 807 m_thread.start(); 808 } 809 810 boolean waitForQueue(double timeout) { 811 m_lock.lock(); 812 try { 813 if (m_poller != 0) { 814 m_waitQueue = true; 815 m_waitQueueEvent.set(); 816 while (m_waitQueue) { 817 try { 818 if (timeout < 0) { 819 m_waitQueueCond.await(); 820 } else { 821 return m_waitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS); 822 } 823 } catch (InterruptedException ex) { 824 Thread.currentThread().interrupt(); 825 return true; 826 } 827 } 828 } 829 } finally { 830 m_lock.unlock(); 831 } 832 return true; 833 } 834 } 835 836 private ListenerStorage m_listeners = new ListenerStorage(this); 837 838 /** 839 * Remove a connection listener. 840 * 841 * @param listener Listener handle to remove 842 */ 843 public void removeListener(int listener) { 844 m_listeners.remove(listener); 845 } 846 847 /** 848 * Wait for the listener queue to be empty. This is primarily useful for deterministic 849 * testing. This blocks until either the listener queue is empty (e.g. there are no 850 * more events that need to be passed along to callbacks or poll queues) or the timeout expires. 851 * 852 * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to 853 * block indefinitely 854 * @return False if timed out, otherwise true. 855 */ 856 public boolean waitForListenerQueue(double timeout) { 857 return m_listeners.waitForQueue(timeout); 858 } 859 860 /** 861 * Add a connection listener. The callback function is called asynchronously on a separate 862 * thread, so it's important to use synchronization or atomics when accessing any shared state 863 * from the callback function. 864 * 865 * @param immediateNotify Notify listener of all existing connections 866 * @param listener Listener to add 867 * @return Listener handle 868 */ 869 public int addConnectionListener( 870 boolean immediateNotify, Consumer<NetworkTableEvent> listener) { 871 EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kConnection); 872 if (immediateNotify) { 873 eventKinds.add(NetworkTableEvent.Kind.kImmediate); 874 } 875 return m_listeners.add(m_handle, eventKinds, listener); 876 } 877 878 /** 879 * Add a time synchronization listener. The callback function is called asynchronously on a 880 * separate thread, so it's important to use synchronization or atomics when accessing any shared 881 * state from the callback function. 882 * 883 * @param immediateNotify Notify listener of current time synchronization value 884 * @param listener Listener to add 885 * @return Listener handle 886 */ 887 public int addTimeSyncListener( 888 boolean immediateNotify, Consumer<NetworkTableEvent> listener) { 889 EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kTimeSync); 890 if (immediateNotify) { 891 eventKinds.add(NetworkTableEvent.Kind.kImmediate); 892 } 893 return m_listeners.add(m_handle, eventKinds, listener); 894 } 895 896 /** 897 * Add a listener for changes on a particular topic. The callback function is called 898 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 899 * accessing any shared state from the callback function. 900 * 901 * <p>This creates a corresponding internal subscriber with the lifetime of the 902 * listener. 903 * 904 * @param topic Topic 905 * @param eventKinds set of event kinds to listen to 906 * @param listener Listener function 907 * @return Listener handle 908 */ 909 public int addListener( 910 Topic topic, 911 EnumSet<NetworkTableEvent.Kind> eventKinds, 912 Consumer<NetworkTableEvent> listener) { 913 if (topic.getInstance().getHandle() != m_handle) { 914 throw new IllegalArgumentException("topic is not from this instance"); 915 } 916 return m_listeners.add(topic.getHandle(), eventKinds, listener); 917 } 918 919 /** 920 * Add a listener for changes on a subscriber. The callback function is called 921 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 922 * accessing any shared state from the callback function. This does NOT keep the subscriber 923 * active. 924 * 925 * @param subscriber Subscriber 926 * @param eventKinds set of event kinds to listen to 927 * @param listener Listener function 928 * @return Listener handle 929 */ 930 public int addListener( 931 Subscriber subscriber, 932 EnumSet<NetworkTableEvent.Kind> eventKinds, 933 Consumer<NetworkTableEvent> listener) { 934 if (subscriber.getTopic().getInstance().getHandle() != m_handle) { 935 throw new IllegalArgumentException("subscriber is not from this instance"); 936 } 937 return m_listeners.add(subscriber.getHandle(), eventKinds, listener); 938 } 939 940 /** 941 * Add a listener for changes on a subscriber. The callback function is called 942 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 943 * accessing any shared state from the callback function. This does NOT keep the subscriber 944 * active. 945 * 946 * @param subscriber Subscriber 947 * @param eventKinds set of event kinds to listen to 948 * @param listener Listener function 949 * @return Listener handle 950 */ 951 public int addListener( 952 MultiSubscriber subscriber, 953 EnumSet<NetworkTableEvent.Kind> eventKinds, 954 Consumer<NetworkTableEvent> listener) { 955 if (subscriber.getInstance().getHandle() != m_handle) { 956 throw new IllegalArgumentException("subscriber is not from this instance"); 957 } 958 return m_listeners.add(subscriber.getHandle(), eventKinds, listener); 959 } 960 961 /** 962 * Add a listener for changes on an entry. The callback function is called 963 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 964 * accessing any shared state from the callback function. 965 * 966 * @param entry Entry 967 * @param eventKinds set of event kinds to listen to 968 * @param listener Listener function 969 * @return Listener handle 970 */ 971 public int addListener( 972 NetworkTableEntry entry, 973 EnumSet<NetworkTableEvent.Kind> eventKinds, 974 Consumer<NetworkTableEvent> listener) { 975 if (entry.getTopic().getInstance().getHandle() != m_handle) { 976 throw new IllegalArgumentException("entry is not from this instance"); 977 } 978 return m_listeners.add(entry.getHandle(), eventKinds, listener); 979 } 980 981 /** 982 * Add a listener for changes to topics with names that start with any of the given 983 * prefixes. The callback function is called asynchronously on a separate thread, so it's 984 * important to use synchronization or atomics when accessing any shared state from the callback 985 * function. 986 * 987 * <p>This creates a corresponding internal subscriber with the lifetime of the 988 * listener. 989 * 990 * @param prefixes Topic name string prefixes 991 * @param eventKinds set of event kinds to listen to 992 * @param listener Listener function 993 * @return Listener handle 994 */ 995 public int addListener( 996 String[] prefixes, 997 EnumSet<NetworkTableEvent.Kind> eventKinds, 998 Consumer<NetworkTableEvent> listener) { 999 return m_listeners.add(prefixes, eventKinds, listener); 1000 } 1001 1002 /* 1003 * Client/Server Functions 1004 */ 1005 1006 /** 1007 * Get the current network mode. 1008 * 1009 * @return Enum set of NetworkMode. 1010 */ 1011 public EnumSet<NetworkMode> getNetworkMode() { 1012 int flags = NetworkTablesJNI.getNetworkMode(m_handle); 1013 EnumSet<NetworkMode> rv = EnumSet.noneOf(NetworkMode.class); 1014 for (NetworkMode mode : NetworkMode.values()) { 1015 if ((flags & mode.getValue()) != 0) { 1016 rv.add(mode); 1017 } 1018 } 1019 return rv; 1020 } 1021 1022 /** 1023 * Starts local-only operation. Prevents calls to startServer or startClient from taking effect. 1024 * Has no effect if startServer or startClient has already been called. 1025 */ 1026 public void startLocal() { 1027 NetworkTablesJNI.startLocal(m_handle); 1028 } 1029 1030 /** 1031 * Stops local-only operation. startServer or startClient can be called after this call to start 1032 * a server or client. 1033 */ 1034 public void stopLocal() { 1035 NetworkTablesJNI.stopLocal(m_handle); 1036 } 1037 1038 /** 1039 * Starts a server using the networktables.json as the persistent file, using the default 1040 * listening address and port. 1041 */ 1042 public void startServer() { 1043 startServer("networktables.json"); 1044 } 1045 1046 /** 1047 * Starts a server using the specified persistent filename, using the default listening address 1048 * and port. 1049 * 1050 * @param persistFilename the name of the persist file to use 1051 */ 1052 public void startServer(String persistFilename) { 1053 startServer(persistFilename, ""); 1054 } 1055 1056 /** 1057 * Starts a server using the specified filename and listening address, using the default port. 1058 * 1059 * @param persistFilename the name of the persist file to use 1060 * @param listenAddress the address to listen on, or empty to listen on any address 1061 */ 1062 public void startServer(String persistFilename, String listenAddress) { 1063 startServer(persistFilename, listenAddress, kDefaultPort3, kDefaultPort4); 1064 } 1065 1066 /** 1067 * Starts a server using the specified filename, listening address, and port. 1068 * 1069 * @param persistFilename the name of the persist file to use 1070 * @param listenAddress the address to listen on, or empty to listen on any address 1071 * @param port3 port to communicate over (NT3) 1072 */ 1073 public void startServer(String persistFilename, String listenAddress, int port3) { 1074 startServer(persistFilename, listenAddress, port3, kDefaultPort4); 1075 } 1076 1077 /** 1078 * Starts a server using the specified filename, listening address, and port. 1079 * 1080 * @param persistFilename the name of the persist file to use 1081 * @param listenAddress the address to listen on, or empty to listen on any address 1082 * @param port3 port to communicate over (NT3) 1083 * @param port4 port to communicate over (NT4) 1084 */ 1085 public void startServer(String persistFilename, String listenAddress, int port3, int port4) { 1086 NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port3, port4); 1087 } 1088 1089 /** Stops the server if it is running. */ 1090 public void stopServer() { 1091 NetworkTablesJNI.stopServer(m_handle); 1092 } 1093 1094 /** 1095 * Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port. 1096 * 1097 * @param identity network identity to advertise (cannot be empty string) 1098 */ 1099 public void startClient3(String identity) { 1100 NetworkTablesJNI.startClient3(m_handle, identity); 1101 } 1102 1103 /** 1104 * Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port. 1105 * 1106 * @param identity network identity to advertise (cannot be empty string) 1107 */ 1108 public void startClient4(String identity) { 1109 NetworkTablesJNI.startClient4(m_handle, identity); 1110 } 1111 1112 /** Stops the client if it is running. */ 1113 public void stopClient() { 1114 NetworkTablesJNI.stopClient(m_handle); 1115 } 1116 1117 /** 1118 * Sets server address and port for client (without restarting client). Changes the port to the 1119 * default port. 1120 * 1121 * @param serverName server name 1122 */ 1123 public void setServer(String serverName) { 1124 setServer(serverName, 0); 1125 } 1126 1127 /** 1128 * Sets server address and port for client (without restarting client). 1129 * 1130 * @param serverName server name 1131 * @param port port to communicate over (0=default) 1132 */ 1133 public void setServer(String serverName, int port) { 1134 NetworkTablesJNI.setServer(m_handle, serverName, port); 1135 } 1136 1137 /** 1138 * Sets server addresses and port for client (without restarting client). Changes the port to the 1139 * default port. The client will attempt to connect to each server in round robin fashion. 1140 * 1141 * @param serverNames array of server names 1142 */ 1143 public void setServer(String[] serverNames) { 1144 setServer(serverNames, 0); 1145 } 1146 1147 /** 1148 * Sets server addresses and port for client (without restarting client). The client will attempt 1149 * to connect to each server in round robin fashion. 1150 * 1151 * @param serverNames array of server names 1152 * @param port port to communicate over (0=default) 1153 */ 1154 public void setServer(String[] serverNames, int port) { 1155 int[] ports = new int[serverNames.length]; 1156 for (int i = 0; i < serverNames.length; i++) { 1157 ports[i] = port; 1158 } 1159 setServer(serverNames, ports); 1160 } 1161 1162 /** 1163 * Sets server addresses and ports for client (without restarting client). The client will 1164 * attempt to connect to each server in round robin fashion. 1165 * 1166 * @param serverNames array of server names 1167 * @param ports array of port numbers (0=default) 1168 */ 1169 public void setServer(String[] serverNames, int[] ports) { 1170 NetworkTablesJNI.setServer(m_handle, serverNames, ports); 1171 } 1172 1173 /** 1174 * Sets server addresses and port for client (without restarting client). Changes the port to the 1175 * default port. The client will attempt to connect to each server in round robin fashion. 1176 * 1177 * @param team team number 1178 */ 1179 public void setServerTeam(int team) { 1180 setServerTeam(team, 0); 1181 } 1182 1183 /** 1184 * Sets server addresses and port for client (without restarting client). Connects using commonly 1185 * known robot addresses for the specified team. 1186 * 1187 * @param team team number 1188 * @param port port to communicate over (0=default) 1189 */ 1190 public void setServerTeam(int team, int port) { 1191 NetworkTablesJNI.setServerTeam(m_handle, team, port); 1192 } 1193 1194 /** 1195 * Disconnects the client if it's running and connected. This will automatically start 1196 * reconnection attempts to the current server list. 1197 */ 1198 public void disconnect() { 1199 NetworkTablesJNI.disconnect(m_handle); 1200 } 1201 1202 /** 1203 * Starts requesting server address from Driver Station. This connects to the Driver Station 1204 * running on localhost to obtain the server IP address, and connects with the default port. 1205 */ 1206 public void startDSClient() { 1207 startDSClient(0); 1208 } 1209 1210 /** 1211 * Starts requesting server address from Driver Station. This connects to the Driver Station 1212 * running on localhost to obtain the server IP address. 1213 * 1214 * @param port server port to use in combination with IP from DS (0=default) 1215 */ 1216 public void startDSClient(int port) { 1217 NetworkTablesJNI.startDSClient(m_handle, port); 1218 } 1219 1220 /** Stops requesting server address from Driver Station. */ 1221 public void stopDSClient() { 1222 NetworkTablesJNI.stopDSClient(m_handle); 1223 } 1224 1225 /** 1226 * Flushes all updated values immediately to the local client/server. This does not flush to the 1227 * network. 1228 */ 1229 public void flushLocal() { 1230 NetworkTablesJNI.flushLocal(m_handle); 1231 } 1232 1233 /** 1234 * Flushes all updated values immediately to the network. Note: This is rate-limited to protect 1235 * the network from flooding. This is primarily useful for synchronizing network updates with 1236 * user code. 1237 */ 1238 public void flush() { 1239 NetworkTablesJNI.flush(m_handle); 1240 } 1241 1242 /** 1243 * Gets information on the currently established network connections. If operating as a client, 1244 * this will return either zero or one values. 1245 * 1246 * @return array of connection information 1247 */ 1248 public ConnectionInfo[] getConnections() { 1249 return NetworkTablesJNI.getConnections(m_handle); 1250 } 1251 1252 /** 1253 * Return whether or not the instance is connected to another node. 1254 * 1255 * @return True if connected. 1256 */ 1257 public boolean isConnected() { 1258 return NetworkTablesJNI.isConnected(m_handle); 1259 } 1260 1261 /** 1262 * Get the time offset between server time and local time. Add this value to local time to get 1263 * the estimated equivalent server time. In server mode, this always returns 0. In client mode, 1264 * this returns the time offset only if the client and server are connected and have exchanged 1265 * synchronization messages. Note the time offset may change over time as it is periodically 1266 * updated; to receive updates as events, add a listener to the "time sync" event. 1267 * 1268 * @return Time offset in microseconds (optional) 1269 */ 1270 public OptionalLong getServerTimeOffset() { 1271 return NetworkTablesJNI.getServerTimeOffset(m_handle); 1272 } 1273 1274 /** 1275 * Starts logging entry changes to a DataLog. 1276 * 1277 * @param log data log object; lifetime must extend until StopEntryDataLog is called or the 1278 * instance is destroyed 1279 * @param prefix only store entries with names that start with this prefix; the prefix is not 1280 * included in the data log entry name 1281 * @param logPrefix prefix to add to data log entry names 1282 * @return Data logger handle 1283 */ 1284 public int startEntryDataLog(DataLog log, String prefix, String logPrefix) { 1285 return NetworkTablesJNI.startEntryDataLog(m_handle, log, prefix, logPrefix); 1286 } 1287 1288 /** 1289 * Stops logging entry changes to a DataLog. 1290 * 1291 * @param logger data logger handle 1292 */ 1293 public static void stopEntryDataLog(int logger) { 1294 NetworkTablesJNI.stopEntryDataLog(logger); 1295 } 1296 1297 /** 1298 * Starts logging connection changes to a DataLog. 1299 * 1300 * @param log data log object; lifetime must extend until StopConnectionDataLog is called or the 1301 * instance is destroyed 1302 * @param name data log entry name 1303 * @return Data logger handle 1304 */ 1305 public int startConnectionDataLog(DataLog log, String name) { 1306 return NetworkTablesJNI.startConnectionDataLog(m_handle, log, name); 1307 } 1308 1309 /** 1310 * Stops logging connection changes to a DataLog. 1311 * 1312 * @param logger data logger handle 1313 */ 1314 public static void stopConnectionDataLog(int logger) { 1315 NetworkTablesJNI.stopConnectionDataLog(logger); 1316 } 1317 1318 /** 1319 * Add logger callback function. By default, log messages are sent to stderr; this function sends 1320 * log messages with the specified levels to the provided callback function instead. The callback 1321 * function will only be called for log messages with level greater than or equal to minLevel and 1322 * less than or equal to maxLevel; messages outside this range will be silently ignored. 1323 * 1324 * @param minLevel minimum log level 1325 * @param maxLevel maximum log level 1326 * @param func callback function 1327 * @return Listener handle 1328 */ 1329 public int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> func) { 1330 return m_listeners.addLogger(minLevel, maxLevel, func); 1331 } 1332 1333 @Override 1334 public boolean equals(Object other) { 1335 if (other == this) { 1336 return true; 1337 } 1338 if (!(other instanceof NetworkTableInstance)) { 1339 return false; 1340 } 1341 1342 return m_handle == ((NetworkTableInstance) other).m_handle; 1343 } 1344 1345 @Override 1346 public int hashCode() { 1347 return m_handle; 1348 } 1349 1350 private boolean m_owned; 1351 private int m_handle; 1352}