Squashed 'third_party/allwpilib/' changes from e473a00f97..e4b91005cf

e4b91005cf [examples] Update SwerveModule constructor doc (NFC) (#4042)
a260bfd83b [examples] Remove "this" keyword from SwerveModule (#4043)
18e262a100 [examples] Fix multiple doc typos in SwerveControllerCommand example (NFC) (#4044)
4bd1f526ab [wpilibc] Prevent StopMotor from terminating robot during MotorSafety check (#4038)
27847d7eb2 [sim] Expose GUI control functions via HAL_RegisterExtension (#4034)
b2a8d3f0f3 [wpilibc] Add mechanism to reset MotorSafety list (#4037)
49adac9564 [wpilib] Check for signedness in ArcadeDriveIK() (#4028)
a19d1133b1 [wpiutil] libuv: Fix sign compare warnings in gcc 11.2 (#4031)
dde91717e4 [build] cmake: Add ability to customize target warnings (#4032)
e9050afd67 [sim] Update sim match time to match real robot (#4024)
165d2837cf [wpilib] Preferences: Set Persistent in Init methods (#4025)
ac7549edca [glass] Fix snprintf truncation warning (#4029)
4d96bc72e0 [wpilibj] Fix typos in error messages for non-null assertions (#4014)
3411eee20f [hal] Replace hardcoded sim array sizes with constants (#4015)
74de97eeca [wpilibc] Add mechanism to reset various global structures (#4007)
4e3cc25012 [examples] Fix periodic function rate comment (NFC) (#4013)
90c1db393e [sim] Add exported functions to control the sim GUI (#3995)
2f43274aa4 [wpilibj] MechanismRoot2d: Add flush to setPosition (#4011)
aeca09db09 [glass] Support remapping of Enter key (#3994)
c107f22c67 [sim] Sim GUI: don't force-show Timing and Other Devices (#4001)
68fe51e8da [wpigui] Update PFD to latest, fix kdialog multiselect (#4005)
8d08d67cf1 [wpigui] PFD: Add console warning if file chooser unavailable (#4003)
4f1782f66e [wpilibc] Only call HAL_Report when initializing SmartDashboard (#4006)
3f77725cd3 Remove uses of iostream (#4004)
5635f33a32 [glass] Increase plot depth to 20K points (#3993)
bca4b7111b [glass] Fix PlotSeries::SetSource() (#3991)
6a6366b0d6 [commands] Add until() as alias for withInterrupt() (#3981)
16bf2c70c5 [wpilib] Fix joystick out of range error messages (#3988)
4b3edb742c [wpilib] Fix ADIS16448 IMU default constructor not working in Java (#3989)
fcf23fc9e9 [hal] Fix potential gamedata out of bounds read (#3983)
af5ef510c5 [wpilibc] Fix REV PH pulse duration units (#3982)
05401e2b81 [wpilib] Write REV PH firmware version to roboRIO to display on driver station (#3977)
9fde0110b6 Update to 2022 v4.0 image (#3944)
b03f8ddb2e [examples] fix incorrect variable in Arm Simulation Pref (#3980)
a26df2a022 [examples] Update ArmSimulation example to use Preferences (#3976)
d68d6674e8 [examples] Armbot: rename kCos to kG (#3975)
a8f0f6bb90 [wpilibj] Fix ADIS16448 getRate to return rate instead of angle (#3974)
dd9c92d5bf [build] Remove debug info from examples (#3971)
84df14dd70 [rtns] Fix icons (#3972)
560094ad92 [examples] Correct Mecanum example axes (#3955)
7ea1be9c01 [wpilibc] Fix typo in hardware version for REV PDH (#3969)
700f13bffd [wpilibj] Make methods public for Java REV PDH (#3970)
b6aa7c1aa9 [wpilibj] Make methods public for Java REVPH (#3968)
eb4d183e48 [wpimath] Fix clang-tidy bugprone-integer-division warning (#3966)
77e4e81e1e [wpilib] Add Field widget to BuiltInWidgets in shuffleboard (#3961)
88f5cb6eb0 [build] Publish PDBs with C++ tools (#3960)
efae552f3e [wpimath] Remove DifferentialDriveKinematics include from odometry (#3958)
46b277421a [glass] Update Speed Controller Type name for 2022 WPILib (#3952)
42908126b9 [wpilib] Add DCMotorSim (#3910)
a467392cbd [wpiutil] StackTrace: Add ability to override default implementation (#3951)
78d0bcf49d [templates] Add SimulationInit()/SimulationPeriodic() to robot templates (#3943)
02a0ced9b0 [wpilib] MecanumDrive: update docs for axis to match implementation (NFC) (#3942)
4ccfe1c9f2 [wpilib] Added docs clarification on units for drive class WheelSpeeds (NFC) (#3939)
830c0c5c2f [wpilib] MechanismLigament2d: Add getters for color and line weight (#3947)
5548a37465 [wpilib] PowerDistribution: Add module type getter (#3948)
2f9a600de2 [hal] Fix PCM one shot (#3949)
559db11a20 [myRobot] Skip deploying debug libraries for myRobot deploys (#3950)
76c78e295b [examples] Reorder SwerveModules in SwerveControllerCommand example odometry update (#3934)
debbd5ff4b [wpilib] Improve PowerDistribution docs (NFC) (#3925)
841174f302 [commands] Change command vendordep JSON version number to 1.0.0 (#3938)
8c55844f91 [wpilib] Remove comment about Mecanum right side inverted (NFC) (#3929)
0b990bf0f5 [hal] Fix PCM sticky faults clear function crashing (#3932)
104d7e2abc [hal] Don't throw exceptions in PCM JNI (#3933)
5ba69e1af1 [examples] Updated type in Java SwerveModule (#3928)
f3a0b5c7d7 [wpimath] Fix Java SimpleMotorFeedforward Docs (NFC) (#3926)
7f4265facc [wpimath] Add LinearFilter::FiniteDifference() (#3900)
63d1fb3bed [wpiutil] Modify fmt to not throw on write failure (#3919)
36af6d25a5 [wpimath] Fix input vector in pose estimator docs (NFC) (#3923)
8f387f7255 [wpilibj] Switch ControlWord mutex to actual reentrant mutex (#3922)
792e735e08 [wpimath] Move TrajectoryGenerator::SetErrorHandler definition to .cpp (#3920)
3b76de83eb [commands] Fix ProfiledPIDCommand use-after-free (#3904)
ad9f738cfa [fieldimages] Fix maven publishing (#3897)
49455199e5 [examples] Use left/rightGroup.Get() for simulator inputs to fix inversions (#3908)
64426502ea [wpimath] Fix arm -> flywheel typo (NFC) (#3911)
8cc112d196 [wpiutil] Fix wpi::array for move-only types (#3917)
e78cd49861 [build] Upgrade Java formatter plugins (#3894)
cfb4f756d6 [build] Upgrade to shadow 7.1.2 (#3893)
ba0908216c [wpimath] Fix crash in KF latency compensator (#3888)
a3a0334fad [build] cmake: Move fieldImages to WITH_GUI (#3885)
cf7460c3a8 [fieldImages] Add 2022 field (#3883)
db0fbb6448 [wpimath] Fix LQR matrix constructor overload for Q, R, and N (#3884)
8ac45f20bb [commands] Update Command documentation (NFC) (#3881)
b3707cca0b [wpiutil] Upgrade to fmt 8.1.1 (#3879)
a69ee3ece9 [wpimath] Const-qualify Twist2d scalar multiply (#3882)
750d9a30c9 [examples] Fix Eigen out of range error when running example (#3877)
41c5b2b5ac [rtns] Add cmake build (#3866)
6cf3f9b28e [build] Upgrade to Gradle 7.3.3 (#3878)
269cf03472 [examples] Add communication examples (e.g. arduino) (#2500)
5ccfc4adbd [oldcommands] Deprecate PIDWrappers, since they use deprecated interfaces (#3868)
b6f44f98be [hal] Add warning about onboard I2C (#3871)
0dca57e9ec [templates] romieducational: Invert drivetrain and disable motor safety (#3869)
22c4da152e [wpilib] Add GetRate() to ADIS classes (#3864)
05d66f862d [templates] Change the template ordering to put command based first (#3863)
b09f5b2cf2 [wpilibc] Add virtual dtor for LinearSystemSim (#3861)
a2510aaa0e [wpilib] Make ADIS IMU classes unit-safe (#3860)
947f589916 [wpilibc] Rename ADIS_16470_IMU.cpp to match class name (#3859)
bbd8980a20 [myRobot] Fix cameraserver library order (#3858)
831052f118 [wpilib] Add simulation support to ADIS classes (#3857)
c137569f91 [wpilib] Throw exception if the REV Pneumatic Hub firmware version is older than 22.0.0 (#3853)
dae61226fa Fix Maven Artifacts readme (#3856)
3ad4594a88 Update Maven artifacts readme for 2022 (#3855)
112acb9a62 [wpilibc] Move ADIS IMU constants to inside class (#3852)
ecee224e81 [wpilib] Allow SendableCameraWrappers to take arbitrary URLs (#3850)
a3645dea34 LICENSE: Bump year range to include 2022 (#3854)
7c09f44898 [wpilib] Use PSI for compressor config and sensor reading (#3847)
f401ea9aae [wpigui] Remove wpiutil linkage (#3851)
bf8517f1e6 [wpimath] TimeInterpolatableBufferTest: Fix lint warnings (#3849)
528087e308 [hal] Use enums with fixed underlying type in clang C (#3297)
1f59ff72f9 [wpilib] Add ADIS IMUs (#3777)
315be873c4 [wpimath] Add TimeInterpolatableBuffer (#2695)
b8d019cdb4 [wpilib] Rename NormalizeWheelSpeeds to DesaturateWheelSpeeds (#3791)
102f23bbdb [wpilibj] DriverStation: Set thread interrupted state (#3846)
b85c24a79c [wpilib] Add warning about onboard I2C (#3842)
eee29daaf9 [newCommands] Trigger: Allow override of debounce type (#3845)
aa9dfabde2 [wpimath] Move debouncer to filters (#3838)
5999a26fba [wpiutil] Add GetSystemTime() (#3840)
1e82595ffb [examples] Fix arcade inversions (#3841)
e373fa476b [wpiutil] Add disableMockTime to JNI (#3839)
dceb5364f4 [examples] Ensure right side motors are inverted (#3836)
baacbc8e24 [wpilib] Tachometer: Add function to return RPS (#3833)
84b15f0883 [templates] Add Java Romi Educational template (#3837)
c0da9d2d35 [examples] Invert Right Motor in Romi Java examples (#3828)
0fe0be2733 [build] Change project year to intellisense (#3835)
eafa947338 [wpimath] Make copies of trajectory constraint arguments (#3832)
9d13ae8d01 [wpilib] Add notes for Servo get that it only returns cmd (NFC) (#3820)
2a64e4bae5 [wpimath] Give drivetrain a more realistic width in TrajectoryJsonTest.java (#3822)
c3fd20db59 [wpilib] Fix trajectory sampling in DifferentialDriveSim test (#3821)
6f91f37cd0 [examples] Fix SwerveControllerCommand order of Module States (#3815)
5158730b81 [wpigui] Upgrade to imgui 1.86, GLFW 3.3.6 (#3817)
2ad2d2ca96 [wpiutil] MulticastServiceResolver: Fix C array returning functions (#3816)
b5fd29774f [wpilibj] Trigger: implement BooleanSupplier interface (#3811)
9f8f330e96 [wpilib] Fix Mecanum and SwerveControllerCommand when desired rotation passed (#3808)
1ad3b1b333 [hal] Don't copy byte to where null terminator goes (#3807)
dfc24425c3 [build] Fix gazebo gradle IDE warnings (#3806)
c02577bb51 [glass] Configure delay loading for windows camera server support (#3803)
c9e6a96a61 [wpilib] Document range of Servo angle (NFC) (#3796)
9778626f34 [wpilib, hal] Add support for getting faults and versions from power distribution (#3794)
34b2d0dae1 [wpilib, hal] High Level REV PH changes (#3792)
59a7528fd6 [cscore] Fix crash when usbcamera is deleted before message pump thread fully starts (#3804)
11d9859ef1 [build] Update plugins to remove log4j vulnerabilities (#3805)
e44ed752ad [glass] Fix CollapsingHeader in Encoder, PCM, and DeviceTree (#3797)
52b2dd5b89 [build] Bump native utils to remove log4j (#3802)
c46636f218 [wpilib] Improve new counter classes documentation (NFC) (#3801)
dc531462e1 [build] Update to gradle 7.3.2 (#3800)
92ba98621c [wpimath] Add helper variable templates for units type traits (#3790)
d41d051f1b [wpilibc] Fix Mecanum & Swerve ControllerCommand lambda capture (#3795)
c5ae0effac OtherVersions.md: Add one missing case of useLocal (#3788)
b3974c6ed3 [wpimath] Upgrade to Drake v0.37.0 (#3786)
589a00e379 [wpilibc] Start DriverStation thread from RobotBase (#3785)
8d9836ca02 [wpilib] Improve curvature drive documentation (NFC) (#3783)
8b5bf8632e [myRobot] Add wpimath and wpiutil JNI (#3784)
1846114491 [examples] Update references from characterization to SysId (NFC) (#3782)
2c461c794e [build] Update to gradle 7.3 (#3778)
109363daa4 [hal] Add remaining driver functions for REVPH (#3776)
41d26bee8d [hal] Refactor REV PDH (#3775)
7269a170fb Upgrade maven deps to latest versions and fix new linter errors (#3772)
441f2ed9b0 [build] actions: use fixed image versions instead latest (#3761)
15275433d4 [examples] Fix duplicate port allocations in C++ SwerveBot/SwerveDrivePoseEstimator/RomiReference (#3773)
1ac02d2f58 [examples] Fix drive Joystick axes in several examples (#3769)
8ee6257e92 [wpilib] DifferentialDrivetrainSim.KitbotMotor: Add NEO and Falcon 500 (#3762)
d81ef2bc5c [wpilib] Fix deadlocks in Mechanism2d et al. (#3770)
acb64dff97 [wpimath] Make RamseteController::Calculate() more concise (#3763)
3f6cf76a8c [hal] Refactor REV PH CAN frames (#3756)
3ef2dab465 [wpilib] DutyCycleEncoder: add setting of duty cycle range (#3759)
a5a56dd067 Readme: Add Visual Studio 2022 (#3760)
04957a6d30 [wpimath] Fix units of RamseteController's b and zeta (#3757)
5da54888f8 [glass] Upgrade imgui to 0.85, implot to HEAD, glfw to 3.3.5 (#3754)
6c93365b0f [wpiutil] MulticastService cleanup (#3750)
1c4a8bfb66 [cscore] Cleanup Windows USB camera impl (#3751)
d51a1d3b3d [rtns] Fix icon (#3749)
aced2e7da6 Add roboRIO Team Number Setter tool (#3744)
fa1ceca83a [wpilibj] Use DS cache for iterative robot control word cache (#3748)
0ea05d34e6 [build] Update to gradle 7.2 (#3746)
09db4f672b [build] Update to native utils 2022.6.1 (#3745)
4ba80a3a8c [wpigui] Don't recursively render frames in size callback (#3743)
ae208d2b17 [wpiutil] StringExtras: Add substr() (#3742)
6f51cb3b98 [wpiutil] MulticastResolver: make event manual reset, change to multiple read (#3736)
f6159ee1a2 [glass] Fix Drive widget handling of negative rotation (#3739)
7f401ae895 [build] Update NI libraries to 2022.2.3 (#3738)
0587b7043a [glass] Use JSON files for storage instead of imgui ini
0bbf51d566 [wpigui] Change maximized to bool
92c6eae6b0 [wpigui] PFD: Add explicit to constructors
141354cd79 [wpigui] Add hooks for custom load/save settings
f6e9fc7d71 [wpiutil] Handle multicast service collision on linux (#3734)
d8418be7d1 [glass, outlineviewer] Return 0 from WinMain (#3735)
82066946e5 [wpiutil] Add mDNS resolver and announcer (#3733)
4b1defc8d8 [wpilib] Remove automatic PD type from module type enum (#3732)
da90c1cd2c [wpilib] Add bang-bang controller (#3676)
3aa54fa027 [wpilib] Add new counter implementations (#2447)
b156db400d [hal, wpilib] Incorporate pneumatic control type into wpilibc/j (#3728)
9aba2b7583 [oldCommands] Add wrappers for WPILib objects to work with old PID Controller (#3710)
a9931223f0 [hal] Add REV PH faults (#3729)
aacf9442e4 [wpimath] Fix units typo in LinearSystemId source comment (#3730)
7db10ecf00 [wpilibc] Make SPI destructor virtual since SPI contains virtual functions (#3727)
a0a5b2aea5 [wpimath] Upgrade to EJML 0.41 (#3726)
eb835598a4 [hal] Add HAL functions for compressor config modes on REV PH (#3724)
f0ab6df5b6 [wpimath] Upgrade to Drake v0.36.0 (#3722)
075144faa3 [docs] Parse files without extensions with Doxygen (#3721)
32468a40cb [hal] Remove use of getDmaDescriptor from autospi (#3717)
38611e9dd7 [hal] Fix REVPH Analog Pressure channel selection (#3716)
4d78def31e [wpilib] Add DeadbandElimination forwarding to PWMMotorController (#3714)
3be0c1217a [wpilibcExamples] Make GearsBot use idiomatic C++ (#3711)
42d3a50aa2 [hal] Check error codes during serial port initialization (#3712)
52f1464029 Add project with field images and their json config files (#3668)
68ce62e2e9 [hal] Add autodetect for power modules (#3706)
3dd41c0d37 [wpilib] Don't print PD errors for LiveWindow reads (#3708)
7699a1f827 [hal] Fix sim not working with automatic PD type and default module (#3707)

Change-Id: I6b0b8fa8b2d2a24071240f624db9ec6d127f6648
git-subtree-dir: third_party/allwpilib
git-subtree-split: e4b91005cf69161f1cb3d934f6526232e6b9169e
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/wpiutil/src/main/native/linux/AvahiClient.cpp b/wpiutil/src/main/native/linux/AvahiClient.cpp
new file mode 100644
index 0000000..6a17ea0
--- /dev/null
+++ b/wpiutil/src/main/native/linux/AvahiClient.cpp
@@ -0,0 +1,118 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "AvahiClient.h"
+
+#include <wpi/mutex.h>
+
+#include <thread>
+
+#include "dlfcn.h"
+
+using namespace wpi;
+
+#define AvahiFunctionLoad(snake_name)                                          \
+  do {                                                                         \
+    snake_name =                                                               \
+        reinterpret_cast<snake_name##_func>(dlsym(lib, "avahi_" #snake_name)); \
+    if (!snake_name) {                                                         \
+      return;                                                                  \
+    }                                                                          \
+  } while (false)
+
+AvahiFunctionTable::AvahiFunctionTable() {
+  void* lib = dlopen("libavahi-common.so.3", RTLD_LAZY);
+
+  valid = false;
+
+  if (lib == nullptr) {
+    return;
+  }
+
+  AvahiFunctionLoad(threaded_poll_new);
+  AvahiFunctionLoad(threaded_poll_free);
+  AvahiFunctionLoad(threaded_poll_get);
+  AvahiFunctionLoad(threaded_poll_start);
+  AvahiFunctionLoad(threaded_poll_stop);
+  AvahiFunctionLoad(threaded_poll_lock);
+  AvahiFunctionLoad(threaded_poll_unlock);
+  AvahiFunctionLoad(string_list_new_from_array);
+  AvahiFunctionLoad(string_list_free);
+  AvahiFunctionLoad(unescape_label);
+  AvahiFunctionLoad(alternative_service_name);
+  AvahiFunctionLoad(free);
+
+  lib = dlopen("libavahi-client.so.3", RTLD_LAZY);
+
+  if (lib == nullptr) {
+    return;
+  }
+
+  AvahiFunctionLoad(client_new);
+  AvahiFunctionLoad(client_free);
+  AvahiFunctionLoad(service_browser_new);
+  AvahiFunctionLoad(service_browser_get_client);
+  AvahiFunctionLoad(service_browser_free);
+  AvahiFunctionLoad(service_resolver_new);
+  AvahiFunctionLoad(service_resolver_free);
+  AvahiFunctionLoad(entry_group_new);
+  AvahiFunctionLoad(entry_group_free);
+  AvahiFunctionLoad(entry_group_add_service_strlst);
+  AvahiFunctionLoad(entry_group_reset);
+  AvahiFunctionLoad(entry_group_is_empty);
+  AvahiFunctionLoad(entry_group_commit);
+  AvahiFunctionLoad(entry_group_get_client);
+
+  valid = true;
+}
+
+AvahiFunctionTable& AvahiFunctionTable::Get() {
+  static AvahiFunctionTable table;
+  return table;
+}
+
+static wpi::mutex ThreadLoopLock;
+static std::weak_ptr<AvahiThread> ThreadLoop;
+
+std::shared_ptr<AvahiThread> AvahiThread::Get() {
+  std::scoped_lock lock{ThreadLoopLock};
+  auto locked = ThreadLoop.lock();
+  if (!locked) {
+    locked = std::make_unique<AvahiThread>(private_init{});
+    ThreadLoop = locked;
+  }
+  return locked;
+}
+
+AvahiThread::AvahiThread(const private_init&) {
+  if (!table.IsValid()) {
+    return;
+  }
+
+  threadedPoll = table.threaded_poll_new();
+  table.threaded_poll_start(threadedPoll);
+}
+
+AvahiThread::~AvahiThread() noexcept {
+  if (!table.IsValid()) {
+    return;
+  }
+
+  if (threadedPoll) {
+    table.threaded_poll_stop(threadedPoll);
+    table.threaded_poll_free(threadedPoll);
+  }
+}
+
+void AvahiThread::lock() {
+  table.threaded_poll_lock(threadedPoll);
+}
+
+void AvahiThread::unlock() {
+  table.threaded_poll_unlock(threadedPoll);
+}
+
+const AvahiPoll* AvahiThread::GetPoll() const {
+  return table.threaded_poll_get(threadedPoll);
+}
diff --git a/wpiutil/src/main/native/linux/AvahiClient.h b/wpiutil/src/main/native/linux/AvahiClient.h
new file mode 100644
index 0000000..ca679bb
--- /dev/null
+++ b/wpiutil/src/main/native/linux/AvahiClient.h
@@ -0,0 +1,316 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <stdint.h>
+
+#include <memory>
+
+/***
+  This file is part of avahi.
+  avahi is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+  avahi is distributed in the hope that it will be useful, but WITHOUT
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+  Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with avahi; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+typedef struct AvahiPoll AvahiPoll;
+
+typedef enum {
+  AVAHI_SERVER_INVALID,
+  AVAHI_SERVER_REGISTERING,
+  AVAHI_SERVER_RUNNING,
+  AVAHI_SERVER_COLLISION,
+  AVAHI_SERVER_FAILURE
+} AvahiServerState;
+
+typedef struct AvahiClient AvahiClient;
+
+typedef enum {
+  AVAHI_CLIENT_S_REGISTERING = AVAHI_SERVER_REGISTERING,
+  AVAHI_CLIENT_S_RUNNING = AVAHI_SERVER_RUNNING,
+  AVAHI_CLIENT_S_COLLISION = AVAHI_SERVER_COLLISION,
+  AVAHI_CLIENT_FAILURE = 100,
+  AVAHI_CLIENT_CONNECTING = 101
+} AvahiClientState;
+
+typedef enum {
+  AVAHI_CLIENT_IGNORE_USER_CONFIG = 1,
+  AVAHI_CLIENT_NO_FAIL = 2
+} AvahiClientFlags;
+
+typedef void (*AvahiClientCallback)(AvahiClient* s, AvahiClientState state,
+                                    void* userdata);
+
+typedef struct AvahiServiceBrowser AvahiServiceBrowser;
+
+typedef int AvahiProtocol;
+
+typedef int AvahiIfIndex;
+
+typedef enum {
+  AVAHI_BROWSER_NEW,
+  AVAHI_BROWSER_REMOVE,
+  AVAHI_BROWSER_CACHE_EXHAUSTED,
+  AVAHI_BROWSER_ALL_FOR_NOW,
+  AVAHI_BROWSER_FAILURE
+} AvahiBrowserEvent;
+
+typedef enum {
+  AVAHI_LOOKUP_RESULT_CACHED = 1,
+  AVAHI_LOOKUP_RESULT_WIDE_AREA = 2,
+  AVAHI_LOOKUP_RESULT_MULTICAST = 4,
+  AVAHI_LOOKUP_RESULT_LOCAL = 8,
+  AVAHI_LOOKUP_RESULT_OUR_OWN = 16,
+  AVAHI_LOOKUP_RESULT_STATIC = 32
+} AvahiLookupResultFlags;
+
+typedef void (*AvahiServiceBrowserCallback)(
+    AvahiServiceBrowser* b, AvahiIfIndex interface, AvahiProtocol protocol,
+    AvahiBrowserEvent event, const char* name, const char* type,
+    const char* domain, AvahiLookupResultFlags flags, void* userdata);
+
+typedef enum {
+  AVAHI_LOOKUP_USE_WIDE_AREA = 1,
+  AVAHI_LOOKUP_USE_MULTICAST = 2,
+
+  AVAHI_LOOKUP_NO_TXT = 4,
+  AVAHI_LOOKUP_NO_ADDRESS = 8
+} AvahiLookupFlags;
+
+typedef struct AvahiServiceResolver AvahiServiceResolver;
+
+typedef enum {
+  AVAHI_RESOLVER_FOUND,
+  AVAHI_RESOLVER_FAILURE
+} AvahiResolverEvent;
+
+typedef struct AvahiIPv4Address {
+  uint32_t address;
+} AvahiIPv4Address;
+
+typedef struct AvahiIPv6Address {
+  uint8_t address[16];
+} AvahiIPv6Address;
+
+typedef struct AvahiAddress {
+  AvahiProtocol proto;
+
+  union {
+    AvahiIPv6Address ipv6;
+    AvahiIPv4Address ipv4;
+    uint8_t data[1];
+  } data;
+} AvahiAddress;
+
+typedef struct AvahiStringList {
+  struct AvahiStringList* next;
+  size_t size;
+  uint8_t text[1];
+} AvahiStringList;
+
+typedef void (*AvahiServiceResolverCallback)(
+    AvahiServiceResolver* r, AvahiIfIndex interface, AvahiProtocol protocol,
+    AvahiResolverEvent event, const char* name, const char* type,
+    const char* domain, const char* host_name, const AvahiAddress* a,
+    uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags,
+    void* userdata);
+
+typedef struct AvahiThreadedPoll AvahiThreadedPoll;
+
+typedef struct AvahiEntryGroup AvahiEntryGroup;
+
+typedef enum {
+  AVAHI_ENTRY_GROUP_UNCOMMITED,
+  AVAHI_ENTRY_GROUP_REGISTERING,
+  AVAHI_ENTRY_GROUP_ESTABLISHED,
+  AVAHI_ENTRY_GROUP_COLLISION,
+  AVAHI_ENTRY_GROUP_FAILURE
+} AvahiEntryGroupState;
+
+typedef void (*AvahiEntryGroupCallback)(AvahiEntryGroup* g,
+                                        AvahiEntryGroupState state,
+                                        void* userdata);
+
+typedef enum {
+  AVAHI_PUBLISH_UNIQUE = 1,
+  AVAHI_PUBLISH_NO_PROBE = 2,
+  AVAHI_PUBLISH_NO_ANNOUNCE = 4,
+  AVAHI_PUBLISH_ALLOW_MULTIPLE = 8,
+
+  AVAHI_PUBLISH_NO_REVERSE = 16,
+  AVAHI_PUBLISH_NO_COOKIE = 32,
+  AVAHI_PUBLISH_UPDATE = 64,
+  AVAHI_PUBLISH_USE_WIDE_AREA = 128,
+  AVAHI_PUBLISH_USE_MULTICAST = 256
+} AvahiPublishFlags;
+
+enum { AVAHI_IF_UNSPEC = -1 };
+
+enum { AVAHI_PROTO_INET = 0, AVAHI_PROTO_INET6 = 1, AVAHI_PROTO_UNSPEC = -1 };
+
+enum {
+  AVAHI_OK = 0,
+  AVAHI_ERR_FAILURE = -1,
+  AVAHI_ERR_BAD_STATE = -2,
+  AVAHI_ERR_INVALID_HOST_NAME = -3,
+  AVAHI_ERR_INVALID_DOMAIN_NAME = -4,
+  AVAHI_ERR_NO_NETWORK = -5,
+  AVAHI_ERR_INVALID_TTL = -6,
+  AVAHI_ERR_IS_PATTERN = -7,
+  AVAHI_ERR_COLLISION = -8,
+  AVAHI_ERR_INVALID_RECORD = -9,
+
+  AVAHI_ERR_INVALID_SERVICE_NAME = -10,
+  AVAHI_ERR_INVALID_SERVICE_TYPE = -11,
+  AVAHI_ERR_INVALID_PORT = -12,
+  AVAHI_ERR_INVALID_KEY = -13,
+  AVAHI_ERR_INVALID_ADDRESS = -14,
+  AVAHI_ERR_TIMEOUT = -15,
+  AVAHI_ERR_TOO_MANY_CLIENTS = -16,
+  AVAHI_ERR_TOO_MANY_OBJECTS = -17,
+  AVAHI_ERR_TOO_MANY_ENTRIES = -18,
+  AVAHI_ERR_OS = -19,
+
+  AVAHI_ERR_ACCESS_DENIED = -20,
+  AVAHI_ERR_INVALID_OPERATION = -21,
+  AVAHI_ERR_DBUS_ERROR = -22,
+  AVAHI_ERR_DISCONNECTED = -23,
+  AVAHI_ERR_NO_MEMORY = -24,
+  AVAHI_ERR_INVALID_OBJECT = -25,
+  AVAHI_ERR_NO_DAEMON = -26,
+  AVAHI_ERR_INVALID_INTERFACE = -27,
+  AVAHI_ERR_INVALID_PROTOCOL = -28,
+  AVAHI_ERR_INVALID_FLAGS = -29,
+
+  AVAHI_ERR_NOT_FOUND = -30,
+  AVAHI_ERR_INVALID_CONFIG = -31,
+  AVAHI_ERR_VERSION_MISMATCH = -32,
+  AVAHI_ERR_INVALID_SERVICE_SUBTYPE = -33,
+  AVAHI_ERR_INVALID_PACKET = -34,
+  AVAHI_ERR_INVALID_DNS_ERROR = -35,
+  AVAHI_ERR_DNS_FORMERR = -36,
+  AVAHI_ERR_DNS_SERVFAIL = -37,
+  AVAHI_ERR_DNS_NXDOMAIN = -38,
+  AVAHI_ERR_DNS_NOTIMP = -39,
+
+  AVAHI_ERR_DNS_REFUSED = -40,
+  AVAHI_ERR_DNS_YXDOMAIN = -41,
+  AVAHI_ERR_DNS_YXRRSET = -42,
+  AVAHI_ERR_DNS_NXRRSET = -43,
+  AVAHI_ERR_DNS_NOTAUTH = -44,
+  AVAHI_ERR_DNS_NOTZONE = -45,
+  AVAHI_ERR_INVALID_RDATA = -46,
+  AVAHI_ERR_INVALID_DNS_CLASS = -47,
+  AVAHI_ERR_INVALID_DNS_TYPE = -48,
+  AVAHI_ERR_NOT_SUPPORTED = -49,
+
+  AVAHI_ERR_NOT_PERMITTED = -50,
+  AVAHI_ERR_INVALID_ARGUMENT = -51,
+  AVAHI_ERR_IS_EMPTY = -52,
+  AVAHI_ERR_NO_CHANGE = -53,
+
+  AVAHI_ERR_MAX = -54
+};
+
+namespace wpi {
+class AvahiFunctionTable {
+ public:
+#define AvahiFunction(CapName, RetType, Parameters) \
+  using CapName##_func = RetType(*) Parameters;     \
+  CapName##_func CapName = nullptr
+
+  AvahiFunction(threaded_poll_new, AvahiThreadedPoll*, (void));
+  AvahiFunction(threaded_poll_free, void, (AvahiThreadedPoll*));
+  AvahiFunction(threaded_poll_get, const AvahiPoll*, (AvahiThreadedPoll*));
+  AvahiFunction(threaded_poll_start, int, (AvahiThreadedPoll*));
+  AvahiFunction(threaded_poll_stop, int, (AvahiThreadedPoll*));
+  AvahiFunction(threaded_poll_lock, int, (AvahiThreadedPoll*));
+  AvahiFunction(threaded_poll_unlock, int, (AvahiThreadedPoll*));
+
+  AvahiFunction(client_new, AvahiClient*,
+                (const AvahiPoll* poll_api, AvahiClientFlags flags,
+                 AvahiClientCallback callback, void* userdata, int* error));
+  AvahiFunction(client_free, void, (AvahiClient*));
+
+  AvahiFunction(service_browser_new, AvahiServiceBrowser*,
+                (AvahiClient * client, AvahiIfIndex interface,
+                 AvahiProtocol protocol, const char* type, const char* domain,
+                 AvahiLookupFlags flags, AvahiServiceBrowserCallback callback,
+                 void* userdata));
+
+  AvahiFunction(service_browser_free, int, (AvahiServiceBrowser*));
+
+  AvahiFunction(service_resolver_new, AvahiServiceResolver*,
+                (AvahiClient * client, AvahiIfIndex interface,
+                 AvahiProtocol protocol, const char* name, const char* type,
+                 const char* domain, AvahiProtocol aprotocol,
+                 AvahiLookupFlags flags, AvahiServiceResolverCallback callback,
+                 void* userdata));
+  AvahiFunction(service_resolver_free, int, (AvahiServiceResolver*));
+
+  AvahiFunction(entry_group_new, AvahiEntryGroup*,
+                (AvahiClient*, AvahiEntryGroupCallback, void*));
+  AvahiFunction(entry_group_free, int, (AvahiEntryGroup*));
+
+  AvahiFunction(entry_group_add_service_strlst, int,
+                (AvahiEntryGroup * group, AvahiIfIndex interface,
+                 AvahiProtocol protocol, AvahiPublishFlags flags,
+                 const char* name, const char* type, const char* domain,
+                 const char* host, uint16_t port, AvahiStringList*));
+
+  AvahiFunction(entry_group_reset, int, (AvahiEntryGroup*));
+  AvahiFunction(entry_group_is_empty, int, (AvahiEntryGroup*));
+  AvahiFunction(entry_group_commit, int, (AvahiEntryGroup*));
+  AvahiFunction(entry_group_get_client, AvahiClient*, (AvahiEntryGroup*));
+
+  AvahiFunction(string_list_new_from_array, AvahiStringList*,
+                (const char** array, int len));
+  AvahiFunction(string_list_free, void, (AvahiStringList*));
+
+  AvahiFunction(service_browser_get_client, AvahiClient*,
+                (AvahiServiceBrowser*));
+
+  AvahiFunction(unescape_label, char*, (const char**, char*, size_t));
+  AvahiFunction(alternative_service_name, char*, (const char*));
+  AvahiFunction(free, void, (void*));
+
+  bool IsValid() const { return valid; }
+
+  static AvahiFunctionTable& Get();
+
+ private:
+  AvahiFunctionTable();
+  bool valid;
+};
+
+class AvahiThread {
+ private:
+  struct private_init {};
+
+ public:
+  explicit AvahiThread(const private_init&);
+  ~AvahiThread() noexcept;
+
+  void lock();
+  const AvahiPoll* GetPoll() const;
+  void unlock();
+
+  static std::shared_ptr<AvahiThread> Get();
+
+ private:
+  AvahiThreadedPoll* threadedPoll;
+  AvahiFunctionTable& table = AvahiFunctionTable::Get();
+};
+
+}  // namespace wpi
diff --git a/wpiutil/src/main/native/linux/MulticastServiceAnnouncer.cpp b/wpiutil/src/main/native/linux/MulticastServiceAnnouncer.cpp
new file mode 100644
index 0000000..1a7af39
--- /dev/null
+++ b/wpiutil/src/main/native/linux/MulticastServiceAnnouncer.cpp
@@ -0,0 +1,169 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/MulticastServiceAnnouncer.h"
+
+#include <vector>
+
+#include "AvahiClient.h"
+#include "fmt/format.h"
+#include "wpi/mutex.h"
+
+using namespace wpi;
+
+struct MulticastServiceAnnouncer::Impl {
+  AvahiFunctionTable& table = AvahiFunctionTable::Get();
+  std::shared_ptr<AvahiThread> thread = AvahiThread::Get();
+  AvahiClient* client;
+  AvahiEntryGroup* group = nullptr;
+  std::string serviceName;
+  std::string serviceType;
+  int port;
+  AvahiStringList* stringList = nullptr;
+
+  ~Impl() noexcept {
+    if (stringList != nullptr && table.IsValid()) {
+      table.string_list_free(stringList);
+    }
+  }
+
+  template <typename T>
+  Impl(std::string_view serviceName, std::string_view serviceType, int port,
+       wpi::span<const std::pair<T, T>> txt);
+};
+
+template <typename T>
+MulticastServiceAnnouncer::Impl::Impl(std::string_view serviceName,
+                                      std::string_view serviceType, int port,
+                                      wpi::span<const std::pair<T, T>> txt) {
+  if (!this->table.IsValid()) {
+    return;
+  }
+
+  this->serviceName = serviceName;
+  this->serviceType = serviceType;
+  this->port = port;
+
+  std::vector<std::string> txts;
+  for (auto&& i : txt) {
+    txts.push_back(fmt::format("{}={}", i.first, i.second));
+  }
+
+  std::vector<const char*> txtArr;
+  for (auto&& i : txts) {
+    txtArr.push_back(i.c_str());
+  }
+
+  this->stringList =
+      this->table.string_list_new_from_array(txtArr.data(), txtArr.size());
+}
+
+static void RegisterService(AvahiClient* client,
+                            MulticastServiceAnnouncer::Impl* impl);
+
+static void EntryGroupCallback(AvahiEntryGroup* group,
+                               AvahiEntryGroupState state, void* userdata) {
+  if (state == AVAHI_ENTRY_GROUP_COLLISION) {
+    // Remote collision
+    MulticastServiceAnnouncer::Impl* impl =
+        reinterpret_cast<MulticastServiceAnnouncer::Impl*>(userdata);
+    char* newName =
+        impl->table.alternative_service_name(impl->serviceName.c_str());
+    impl->serviceName = newName;
+    impl->table.free(newName);
+    RegisterService(impl->table.entry_group_get_client(group), impl);
+  }
+}
+
+static void RegisterService(AvahiClient* client,
+                            MulticastServiceAnnouncer::Impl* impl) {
+  if (impl->group == nullptr) {
+    impl->group = impl->table.entry_group_new(client, EntryGroupCallback, impl);
+  }
+
+  while (true) {
+    if (impl->table.entry_group_is_empty(impl->group)) {
+      auto ret = impl->table.entry_group_add_service_strlst(
+          impl->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+          AVAHI_PUBLISH_USE_MULTICAST, impl->serviceName.c_str(),
+          impl->serviceType.c_str(), "local", nullptr, impl->port,
+          impl->stringList);
+      if (ret == AVAHI_ERR_COLLISION) {
+        // Local collision
+        char* newName =
+            impl->table.alternative_service_name(impl->serviceName.c_str());
+        impl->serviceName = newName;
+        impl->table.free(newName);
+        continue;
+      } else if (ret != AVAHI_OK) {
+        break;
+      }
+      impl->table.entry_group_commit(impl->group);
+      break;
+    }
+  }
+}
+
+static void ClientCallback(AvahiClient* client, AvahiClientState state,
+                           void* userdata) {
+  MulticastServiceAnnouncer::Impl* impl =
+      reinterpret_cast<MulticastServiceAnnouncer::Impl*>(userdata);
+
+  if (state == AVAHI_CLIENT_S_RUNNING) {
+    RegisterService(client, impl);
+  } else if (state == AVAHI_CLIENT_S_COLLISION ||
+             state == AVAHI_CLIENT_S_REGISTERING) {
+    if (impl->group) {
+      impl->table.entry_group_reset(impl->group);
+    }
+  }
+}
+
+MulticastServiceAnnouncer::MulticastServiceAnnouncer(
+    std::string_view serviceName, std::string_view serviceType, int port,
+    wpi::span<const std::pair<std::string, std::string>> txt) {
+  pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt);
+}
+
+MulticastServiceAnnouncer::MulticastServiceAnnouncer(
+    std::string_view serviceName, std::string_view serviceType, int port,
+    wpi::span<const std::pair<std::string_view, std::string_view>> txt) {
+  pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt);
+}
+
+MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept {
+  Stop();
+}
+
+bool MulticastServiceAnnouncer::HasImplementation() const {
+  return pImpl->table.IsValid();
+}
+
+void MulticastServiceAnnouncer::Start() {
+  if (!pImpl->table.IsValid()) {
+    return;
+  }
+  std::scoped_lock lock{*pImpl->thread};
+  if (pImpl->client) {
+    return;
+  }
+  pImpl->client =
+      pImpl->table.client_new(pImpl->thread->GetPoll(), AVAHI_CLIENT_NO_FAIL,
+                              ClientCallback, pImpl.get(), nullptr);
+}
+
+void MulticastServiceAnnouncer::Stop() {
+  if (!pImpl->table.IsValid()) {
+    return;
+  }
+  std::scoped_lock lock{*pImpl->thread};
+  if (pImpl->client) {
+    if (pImpl->group) {
+      pImpl->table.entry_group_free(pImpl->group);
+      pImpl->group = nullptr;
+    }
+    pImpl->table.client_free(pImpl->client);
+    pImpl->client = nullptr;
+  }
+}
diff --git a/wpiutil/src/main/native/linux/MulticastServiceResolver.cpp b/wpiutil/src/main/native/linux/MulticastServiceResolver.cpp
new file mode 100644
index 0000000..6585443
--- /dev/null
+++ b/wpiutil/src/main/native/linux/MulticastServiceResolver.cpp
@@ -0,0 +1,149 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/MulticastServiceResolver.h"
+
+#include "AvahiClient.h"
+#include "wpi/SmallString.h"
+#include "wpi/StringExtras.h"
+#include "wpi/mutex.h"
+
+using namespace wpi;
+
+struct MulticastServiceResolver::Impl {
+  AvahiFunctionTable& table = AvahiFunctionTable::Get();
+  std::shared_ptr<AvahiThread> thread = AvahiThread::Get();
+  AvahiClient* client;
+  AvahiServiceBrowser* browser;
+  std::string serviceType;
+  MulticastServiceResolver* resolver;
+
+  void onFound(ServiceData&& data) {
+    resolver->PushData(std::forward<ServiceData>(data));
+  }
+};
+
+MulticastServiceResolver::MulticastServiceResolver(
+    std::string_view serviceType) {
+  pImpl = std::make_unique<Impl>();
+  pImpl->serviceType = serviceType;
+  pImpl->resolver = this;
+}
+
+MulticastServiceResolver::~MulticastServiceResolver() noexcept {
+  Stop();
+}
+
+bool MulticastServiceResolver::HasImplementation() const {
+  return pImpl->table.IsValid();
+}
+
+static void ResolveCallback(AvahiServiceResolver* r, AvahiIfIndex interface,
+                            AvahiProtocol protocol, AvahiResolverEvent event,
+                            const char* name, const char* type,
+                            const char* domain, const char* host_name,
+                            const AvahiAddress* address, uint16_t port,
+                            AvahiStringList* txt, AvahiLookupResultFlags flags,
+                            void* userdata) {
+  MulticastServiceResolver::Impl* impl =
+      reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
+
+  if (event == AVAHI_RESOLVER_FOUND) {
+    if (address->proto == AVAHI_PROTO_INET) {
+      AvahiStringList* strLst = txt;
+      MulticastServiceResolver::ServiceData data;
+      while (strLst != nullptr) {
+        std::string_view value{reinterpret_cast<const char*>(strLst->text),
+                               strLst->size};
+        strLst = strLst->next;
+        size_t splitIndex = value.find('=');
+        if (splitIndex == value.npos) {
+          // Todo make this just do key
+          continue;
+        }
+        std::string_view key = wpi::substr(value, 0, splitIndex);
+        value =
+            wpi::substr(value, splitIndex + 1, value.size() - splitIndex - 1);
+        data.txt.emplace_back(std::pair<std::string, std::string>{key, value});
+      }
+      wpi::SmallString<256> outputHostName;
+      char label[256];
+      do {
+        impl->table.unescape_label(&host_name, label, sizeof(label));
+        if (label[0] == '\0') {
+          break;
+        }
+        outputHostName.append(label);
+        outputHostName.append(".");
+      } while (true);
+
+      data.ipv4Address = address->data.ipv4.address;
+      data.port = port;
+      data.serviceName = name;
+      data.hostName = outputHostName.string();
+
+      impl->onFound(std::move(data));
+    }
+  }
+
+  impl->table.service_resolver_free(r);
+}
+
+static void BrowseCallback(AvahiServiceBrowser* b, AvahiIfIndex interface,
+                           AvahiProtocol protocol, AvahiBrowserEvent event,
+                           const char* name, const char* type,
+                           const char* domain, AvahiLookupResultFlags flags,
+                           void* userdata) {
+  MulticastServiceResolver::Impl* impl =
+      reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
+
+  if (event == AVAHI_BROWSER_NEW) {
+    impl->table.service_resolver_new(
+        impl->table.service_browser_get_client(b), interface, protocol, name,
+        type, domain, AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_MULTICAST,
+        ResolveCallback, userdata);
+  }
+}
+
+static void ClientCallback(AvahiClient* client, AvahiClientState state,
+                           void* userdata) {
+  MulticastServiceResolver::Impl* impl =
+      reinterpret_cast<MulticastServiceResolver::Impl*>(userdata);
+
+  if (state == AVAHI_CLIENT_S_RUNNING) {
+    impl->browser = impl->table.service_browser_new(
+        client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, impl->serviceType.c_str(),
+        "local", AvahiLookupFlags::AVAHI_LOOKUP_USE_MULTICAST, BrowseCallback,
+        userdata);
+  }
+}
+
+void MulticastServiceResolver::Start() {
+  if (!pImpl->table.IsValid()) {
+    return;
+  }
+  std::scoped_lock lock{*pImpl->thread};
+  if (pImpl->client) {
+    return;
+  }
+
+  pImpl->client =
+      pImpl->table.client_new(pImpl->thread->GetPoll(), AVAHI_CLIENT_NO_FAIL,
+                              ClientCallback, pImpl.get(), nullptr);
+}
+
+void MulticastServiceResolver::Stop() {
+  if (!pImpl->table.IsValid()) {
+    return;
+  }
+  std::scoped_lock lock{*pImpl->thread};
+  if (pImpl->client) {
+    if (pImpl->browser) {
+      pImpl->table.service_browser_free(pImpl->browser);
+      pImpl->browser = nullptr;
+    }
+    pImpl->table.client_free(pImpl->client);
+    pImpl->client = nullptr;
+  }
+}