Transceiver control library, serial multiplexing service, and command analysis toolset.
Developed primarily for the Xiegu G90 transceiver. Detent executes protocol definitions parsed from INI data files rather than hardcoded routines. It is not a complete drop-in replacement for OmniRig.
Platform support is strictly limited to Windows. Requests regarding Linux compatibility, support, or ports will be ignored and redirected to the depths of the freedesktop ABI.
Detent isolates protocol definitions from application logic. Transceiver command sets, reply validation masks, field offsets, and timing constraints are defined in standalone description files.
The codebase compiles to:
detent.lib/detent.dll: C-compatible shared and static libraries along with the native Rust crate.detentd: Standalone sharing service allowing multiple client processes to multiplex access to a single serial port.detent-capture: Diagnostic utility to sweep, log, diff, and generate description files from hardware exchanges.
- Foreign calls: No callbacks into client code. Device events accumulate in a thread-safe ring buffer drained on demand by the caller.
- Exception boundary: Internal panics are intercepted at the ABI boundary and returned as negative
DetentResulterror codes. - Extensibility: Structures use Vulkan-style extensible chains (
sTypetag andpNextpointer). Unrecognized structure tags are skipped. - Memory isolation: Handles (
DetentInstance,DetentDevice) are opaque pointers tagged internally to prevent use-after-free conditions.
The core engine (detent::engine) does not spawn internal threads by default. It can be advanced manually within a host application frame loop or wrapped inside a dedicated background worker thread (detent::worker).
- State model: Tracks frequency, VFO registers, split status, transmit state, RIT/XIT offsets, and modulation modes. Unread parameters remain explicitly undefined (
None) rather than defaulting to zero. - Scheduling: Prioritizes initialization commands, followed by pending user writes, followed by round-robin status polling. High-velocity parameters (frequency, PTT) are polled every pass; static conditions (mode) are throttled.
- Stream matching: Uses bitmasked pattern recognition across a sliding byte buffer. Unrelated bus traffic or delayed echo replies are stepped over without stalling the link.
Serial ports cannot be accessed concurrently by separate programs. detentd opens the hardware COM port, executes the protocol engine, and serves client instances over a local Windows Named Pipe (\\.\pipe\detent-{user}-{port}).
- Concurrent sessions: Multiplexes read status to all connected clients while serializing write commands.
- Conflict resolution: Write collisions resolve on an arrival-time basis; the resulting state is broadcast to all attached processes.
- Port retention: Stays active for a configurable linger window (default 30 seconds) after the last client detaches to eliminate re-initialization overhead during application restarts.
- Auto-start: Clients can launch the daemon process on demand if the pipe endpoint is not detected.
A CLI utility used to inspect unknown transceivers or verify command timings:
- Frame inspection: Measures turnaround latency, inter-byte delays, and half-duplex command echo.
- Range sweep: Probes single-byte and multi-byte subcommand ranges, identifying acknowledged (
0xFB), rejected (0xFA), or silent commands. - State diffing: Compares command spaces between physical states (e.g. before and after toggling an attenuator) to locate specific control bytes.
- Section drafting: Generates draft INI profile sections directly from live replies, masking data bytes while preserving framing patterns.
Profiles are standard INI files defining sequences and expected packet formats.
; Example Xiegu G90 profile definition
[pmFreq]
Command = FEFE88E0.05.0000000000.FD
Value = 5|5|vfBcdLU|1|0
ReplyLength = 17
Validate = FEFE88E0050000000000FD.FEFEE088FBFD
[STATUS1]
Command = FEFE88E0.03.FD
ReplyLength = 17
Validate = FEFE88E003FD.FEFEE088.03.0000000000.FD
Value1 = 11|5|vfBcdLU|1|0|pmFreq
[DETENT]
TimeoutMs = 200
MaxPollHz = 8
GapMs = 10- Command formatting: Raw hexadecimal notation, space-separated, dot-separated, or literal ASCII wrapped in parentheses (
(FA00000000000;)). - Validation: Single-string bitmasks (where
00denotes an unconstrained data byte) or explicit two-string mask/value pairs (mask|value). - Encodings:
vfText,vfTextUD,vfBinL,vfBinB,vfBcdLU,vfBcdBU,vfBcdLS,vfBcdBS,vfDPIcom, andvfYaesu.
#include <stdio.h>
#include "detent/detent.h"
int main() {
DetentInstanceCreateInfo create_info = {0};
create_info.sType = DETENT_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.pProfileDirectory = "./rigs";
DetentInstance instance = NULL;
if (detentCreateInstance(&create_info, &instance) != DETENT_SUCCESS) {
return 1;
}
DetentSerialTransport serial = {0};
serial.sType = DETENT_STRUCTURE_TYPE_SERIAL_TRANSPORT;
serial.pPortName = "COM5";
serial.baud = 19200;
serial.dataBits = 8;
serial.stopBits = 1;
serial.parity = DETENT_PARITY_NONE;
DetentDeviceOpenInfo open_info = {0};
open_info.sType = DETENT_STRUCTURE_TYPE_DEVICE_OPEN_INFO;
open_info.pNext = &serial;
open_info.pProfileName = "g90-detent";
DetentDevice device = NULL;
if (detentOpenDevice(instance, &open_info, &device) == DETENT_SUCCESS) {
// Poll events or write values
detentSetFrequency(device, 14025000);
detentCloseDevice(device);
}
detentDestroyInstance(instance);
return 0;
}Requires a standard Rust toolchain. Compiling C examples requires MSVC or Clang.
# Build Rust crate and C libraries (detent.lib, detent.dll)
cargo build --release
# Build daemon and capture tool
cargo build --release --bin detentd --bin detent-capture
# Run unit and integration tests
cargo testThis software is released under the Zero-Clause BSD (0BSD) License. Refer to the LICENSE file for details.