Name
ImudClient — Arduino/ESP32 client library for the imud IMU daemon
Heading, pitch, roll, quaternion, heave and sea state on a microcontroller, straight off the network.
Synopsis
#include <WiFi.h>
#include <ImudClient.h>
ImudClient imud;
WiFiClient net;
void setup() {
Serial.begin(115200);
WiFi.begin("ssid", "password");
while (WiFi.status() != WL_CONNECTED) delay(100);
imud.beginTCP(net, "192.168.1.50", 10112);
}
void loop() {
if (imud.poll()) { // a NEW valid packet arrived
const imud_packet_t &p = imud.packet();
Serial.printf("heading %.1f pitch %.1f roll %.1f\n",
p.heading_deg,
imud_rad_to_deg(p.pitch),
imud_rad_to_deg(p.roll));
}
}Description
ImudClient is a header-only Arduino library that receives, validates, and decodes the binary attitude packets published by imud — an IMU daemon for marine and robotics navigation that fuses gyro, accelerometer, and magnetometer data with a Kalman filter and publishes the result over the network.
The daemon does the sensor work on a Linux host. The microcontroller just reads the answer. That makes it a practical way to build cockpit displays, NMEA gauges, autopilot remotes, and telemetry heads that need live attitude without carrying an IMU or a filter of their own.
- Both transports, one packet.
- TCP on port 10112 — lossless, framed, up to 8 concurrent clients — or UDP on port 10111, unicast, broadcast, or multicast, at up to 500 Hz.
- No heap, no
String, no exceptions. - Fixed buffers only, roughly 600 bytes of RAM.
- Header-only.
- Drop in
src/ImudClient.hand#include <ImudClient.h>. Nothing to build or link. - Portable core.
- The parser depends only on the C++ standard library and is unit-tested
on a host PC — no Arduino, no mocks. The Arduino wrapper touches only the
abstract
ClientandUDPbase classes, so it works withWiFiClient/WiFiUDP,EthernetClient/EthernetUDP, or anything else implementing those interfaces. - Hostile-input hardened.
- Wire data is untrusted. Packets are validated on magic, version, size, and CRC; the range checks fail closed on NaN and infinities, and the streaming path resynchronizes by rescanning for the next magic sequence rather than discarding its buffer.
Requirements
millisSinceLastPacket() that
climbs forever. If a sketch connects but never receives, check this
first.
There is deliberately no dual-version support. The CRC offset moved again between v17 and v18, so a parser would have to guess a frame's length before it could validate it — on a stream where the framing is the validation.
Boards. ESP32 is the primary target, under both the Arduino IDE and PlatformIO. The library also compiles for ESP8266, RP2040 (Pico W), and Ethernet-shield boards.
Installation
Arduino IDE. Tools → Manage Libraries…, search for
ImudClient, and click Install. The examples then appear
under File → Examples → ImudClient; start with
HelloAttitude.
PlatformIO. Add the repository to
platformio.ini:
lib_deps =
https://github.com/richcreations/imud-arduino.gitTo install an unreleased version or a fork, download the repository as a
ZIP and use Sketch → Include Library → Add .ZIP Library…, or clone straight
into your Arduino libraries/ folder.
Transports
- TCP —
beginTCP(net, host, 10112) - Lossless, framed, ordered, up to 8 concurrent clients. The right choice for a display that needs every update. The initial connect is allowed to fail — auto-reconnect takes over, throttled to one attempt every 2 seconds.
- UDP —
beginUDP(udp, 10111) - Higher rate, up to 500 Hz, unicast or broadcast. Binding is all that is
needed;
beginUDP()callsudp.begin()for you. - UDP multicast
- imud's default high-rate destination is
239.255.0.1. The abstract ArduinoUDPclass has no portable multicast join, so join with the concrete transport's own API first, then hand over the already-bound socket:
WiFiUDP udp;
udp.beginMulticast(IPAddress(239, 255, 0, 1), 10111); // ESP32 API
imud.beginUDP(udp, 10111, /*alreadyBound=*/true);What's in a packet
Every packet carries the same full set of fields, read straight off the
struct — imud.packet().heading_deg. There are no per-field
accessors.
heading_deg is in degrees,
but pitch, roll, and yaw are in
radians. Convert with imud_rad_to_deg().
Rate of turn is in degrees per minute.
| Field | Meaning | Units |
|---|---|---|
heading_deg | magnetic heading | degrees, 0–360 |
trueHeading() | true heading; -1.0f if unknown | degrees, 0–360 |
pitch / roll / yaw | bow up +, starboard up + | radians |
quat_w/x/y/z | same orientation, body→NED | unit quaternion |
rate_of_turn | turn rate, + = right | degrees/minute |
declination_deg | magnetic variation, + = east | degrees |
heave_m / heave_rate | vertical displacement / velocity, + up | m, m/s |
wave_height_m | significant wave height (Hs) | metres |
wave_period_s | mean zero-crossing wave period | seconds |
roll_period_s / pitch_period_s | vessel periods; 0.0 = not rolling | seconds |
accel_* / gyro_* / mag_* | calibrated sensors, NED; *_raw_* before calibration | m/s², rad/s, µT |
Some fields are gated by a validity flag and read 0.0 until
it is set — heave by HEAVE_VALID, sea state by
WAVE_VALID, true heading and declination by
DECLINATION_VALID. The full 288-byte struct and the
IMUD_FLAG_* bitmask are defined in
src/ImudClient.h.
Interface
| Method | Description |
|---|---|
beginTCP(c, host, port) | Stores host/port for reconnects. A failed initial connect is fine. |
beginUDP(u, port, alreadyBound) | Binds unless alreadyBound; pass true for multicast. |
poll() | Non-blocking drain. true if ≥1 new valid packet arrived. |
packet() | The newest valid packet; zero-initialized before the first. |
trueHeading() | True heading, or -1.0f until declination is valid. |
connected() | TCP link state; always true on UDP after beginUDP(). |
reconnect() | TCP only. Blocks, as Client::connect() does. |
setAutoReconnect(on) | Default on, ≥2 s between attempts. Turn off in latency-critical loops. |
millisSinceLastPacket() | For watchdogs and staleness indicators; UINT32_MAX before the first. |
daemonShutdown() | true on the daemon's final packet — a clean exit, not a dropped link. |
packetsReceived() / crcErrors() / resyncs() | Cumulative counters. |
end() | Stops the transport, resets the parser and counters. |
Power users can use ImudParser directly — pure C++, zero
Arduino includes, zero I/O. It is what ImudClient wraps, and
what the host-side unit tests exercise. Use it to decode packets from a
transport the wrapper does not cover, or from a file of captured frames.
Examples
HelloAttitude- The smallest sketch that prints live heading, pitch, and roll.
TcpBasic- A complete TCP sketch with staleness detection, reconnect handling, and daemon-shutdown reporting.
UdpListen- UDP reception that also reports the achieved packet rate.
You do not need an IMU, or any wiring, to try these. The repository ships
a test server in tools/ that feeds a sketch synthetic packets
from a PC — the
getting-started
guide walks from an empty sketch to live attitude on the Serial Monitor
using it.
See also
- Source on GitHub — the library, examples, and tests.
- Getting started — beginner walkthrough, no hardware required.
- Protocol — byte offsets and the resync algorithm.
- Glossary — NED, declination, heave, NIS, and friends.
- Troubleshooting — organised by symptom.
- imud — the daemon this library talks to, and its Debian packages.
Author
Written by Richard Simpson. Released under the MIT license.