Documentation
¶
Overview ¶
Package modembridge supervises the Rust graywolf-modem child process and runs the IPC state machine that drives it from the Go side.
Bridge is a thin composition of purpose-built pieces:
- supervisor owns the child process lifecycle and stdout ring buffer.
- ipcLoop owns per-session framing-level send/recv.
- dispatcher correlates request IDs with reply channels for the two request/response IPC exchanges.
- dcdPublisher fans out DcdChange events with slow-subscriber drop accounting.
- statusCache holds per-channel stats and per-device audio levels, reset on every restart.
Bridge methods themselves are either lifecycle code (Start / Stop / supervise) or one-line delegates to the pieces above.
Index ¶
- type AvailableDevice
- type Bridge
- func (b *Bridge) ConfigStore() configstore.ConfigStore
- func (b *Bridge) DcdEvents() <-chan *pb.DcdChange
- func (b *Bridge) DcdSubscribe() <-chan *pb.DcdChange
- func (b *Bridge) DcdUnsubscribe(ch <-chan *pb.DcdChange)
- func (b *Bridge) EnumerateAudioDevices(ctx context.Context) ([]AvailableDevice, error)
- func (b *Bridge) Frames() <-chan *pb.ReceivedFrame
- func (b *Bridge) GetAllChannelStats() map[uint32]*ChannelStats
- func (b *Bridge) GetAllDeviceLevels() map[uint32]*DeviceLevel
- func (b *Bridge) GetChannelStats(channel uint32) (*ChannelStats, bool)
- func (b *Bridge) InjectSendFnForTest(fn func(*pb.IpcMessage) error) func()
- func (b *Bridge) InjectStatusForTest(channel uint32, rxFrames, rxBadFCS, txFrames uint64, ...)
- func (b *Bridge) IsRunning() bool
- func (b *Bridge) LastModemStdout() []string
- func (b *Bridge) ManualPtt(channel uint32, keyed bool) error
- func (b *Bridge) ManualPttWithWatchdog(channel uint32, keyed bool) error
- func (b *Bridge) ReconfigureAudioDevice(ctx context.Context, _ uint32) error
- func (b *Bridge) ReloadConfiguration(ctx context.Context) error
- func (b *Bridge) ScanInputLevels(ctx context.Context) ([]InputLevel, error)
- func (b *Bridge) SendTransmitFrame(tf *pb.TransmitFrame) error
- func (b *Bridge) SetDeviceGain(deviceID uint32, gainDB float32) error
- func (b *Bridge) Start(ctx context.Context) error
- func (b *Bridge) State() State
- func (b *Bridge) Stop()
- func (b *Bridge) TransmitTestSignal(ctx context.Context, p TestSignalParams) error
- type ChannelStats
- type Config
- type DeviceLevel
- type InputLevel
- type State
- type TestSignalParams
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AvailableDevice ¶
type AvailableDevice struct {
Name string `json:"name"`
Description string `json:"description"` // human-friendly name (e.g. USB product string)
Path string `json:"path"` // pcm_id (used as device_path in config)
SampleRates []uint32 `json:"sample_rates"`
Channels []uint32 `json:"channels"`
HostAPI string `json:"host_api"`
IsDefault bool `json:"is_default"`
IsInput bool `json:"is_input"`
Recommended bool `json:"recommended"` // true for plughw: devices (ALSA software conversion)
// EnhancementsEnabled is Windows-only: the endpoint has audio
// "enhancements" (system effects / APOs) active, which corrupt
// AFSK/packet audio. Always false on Linux/macOS.
EnhancementsEnabled bool `json:"enhancements_enabled"`
}
AvailableDevice describes an audio device discovered by cpal enumeration. Field names match the frontend's expected shape.
type Bridge ¶
type Bridge struct {
// contains filtered or unexported fields
}
Bridge supervises the Rust modem child and exposes received frames to consumers. See the package comment for the overall composition.
func (*Bridge) ConfigStore ¶
func (b *Bridge) ConfigStore() configstore.ConfigStore
ConfigStore returns the attached configstore (may be nil).
func (*Bridge) DcdEvents ¶
DcdEvents returns the long-lived primary DCD subscription. Deprecated in favor of DcdSubscribe for new callers; retained as a compat shim for the existing txgovernor wiring. Closed when Stop completes.
func (*Bridge) DcdSubscribe ¶
DcdSubscribe returns a new buffered channel that will receive every DcdChange event seen by the bridge. Slow subscribers drop events (non-blocking send). The channel is closed when Stop completes or when the caller passes it to DcdUnsubscribe.
func (*Bridge) DcdUnsubscribe ¶
DcdUnsubscribe removes a previously Subscribed channel and closes it so the caller's range loop exits.
func (*Bridge) EnumerateAudioDevices ¶
func (b *Bridge) EnumerateAudioDevices(ctx context.Context) ([]AvailableDevice, error)
EnumerateAudioDevices asks the Rust modem to list available audio devices via cpal and waits for the response. Returns nil slice if the bridge is not running or the request times out.
func (*Bridge) Frames ¶
func (b *Bridge) Frames() <-chan *pb.ReceivedFrame
Frames returns a channel of received AX.25 frames. The channel is closed when Stop completes.
func (*Bridge) GetAllChannelStats ¶
func (b *Bridge) GetAllChannelStats() map[uint32]*ChannelStats
GetAllChannelStats returns cached stats for all channels.
func (*Bridge) GetAllDeviceLevels ¶
func (b *Bridge) GetAllDeviceLevels() map[uint32]*DeviceLevel
GetAllDeviceLevels returns the latest cached audio levels for all devices.
func (*Bridge) GetChannelStats ¶
func (b *Bridge) GetChannelStats(channel uint32) (*ChannelStats, bool)
GetChannelStats returns cached stats for a single channel.
func (*Bridge) InjectSendFnForTest ¶ added in v0.13.6
func (b *Bridge) InjectSendFnForTest(fn func(*pb.IpcMessage) error) func()
InjectSendFnForTest installs a fake send function so tests can capture IPC messages without running a real modem child. The previous value is restored when the returned cleanup function is called. Test-only.
func (*Bridge) InjectStatusForTest ¶
func (b *Bridge) InjectStatusForTest(channel uint32, rxFrames, rxBadFCS, txFrames uint64, markLevel, spaceLevel, peakLevel float32, dcd bool)
InjectStatusForTest populates the status cache directly. Test-only.
func (*Bridge) IsRunning ¶
IsRunning reports whether the bridge is actively exchanging messages with the Rust modem child. A bridge is running when both:
- the supervisor is in StateRunning (socket connected, configuration pushed, StartAudio sent), and
- the most recent inbound IPC message (ReceivedFrame, StatusUpdate, DcdChange, DeviceLevelUpdate, or any dispatcher reply) was received within the last bridgeHeartbeatTimeout (30 s).
A disconnected socket, a session that is still configuring, or a session that has gone silent for more than 30 s all return false. Callers (e.g. the messages sender deciding whether RF is available for fallback) should treat false as "modem currently unreliable; route via an alternate path or wait".
func (*Bridge) LastModemStdout ¶
LastModemStdout returns a snapshot of the last ring-buffer lines the modem child wrote to stdout, for crash diagnostics.
func (*Bridge) ManualPtt ¶ added in v0.13.6
ManualPtt sends a ManualPtt IPC message to the modem to directly key or unkey the PTT driver for the channel. The driver must already be registered via ConfigurePtt. Returns an error if no session is active.
func (*Bridge) ManualPttWithWatchdog ¶ added in v0.13.6
ManualPttWithWatchdog keys or unkeys the radio and maintains the 10-second watchdog: a keyed:true call (re)starts the timer; keyed:false cancels it. The REST handler calls this method; direct tests can use ManualPtt to exercise IPC dispatch without the timer side-effects.
func (*Bridge) ReconfigureAudioDevice ¶
ReconfigureAudioDevice performs a hot-swap of an audio device's configuration. It stops all audio, re-reads the full config from the database, and restarts. This handles both updates and deletes correctly.
func (*Bridge) ReloadConfiguration ¶
ReloadConfiguration stops all modem audio processing, re-reads the full configuration from the database, and restarts. Safe to call after deletes.
func (*Bridge) ScanInputLevels ¶
func (b *Bridge) ScanInputLevels(ctx context.Context) ([]InputLevel, error)
ScanInputLevels asks the Rust modem to briefly open each input device, measure peak levels, and return the results.
func (*Bridge) SendTransmitFrame ¶
func (b *Bridge) SendTransmitFrame(tf *pb.TransmitFrame) error
SendTransmitFrame queues a TransmitFrame IPC message to the currently connected modem session. Returns an error if no session is active. Callers (e.g. the txgovernor) retry or drop on error.
func (*Bridge) SetDeviceGain ¶
SetDeviceGain sends a live gain adjustment to the modem (fire-and-forget).
func (*Bridge) TransmitTestSignal ¶ added in v0.13.11
func (b *Bridge) TransmitTestSignal(ctx context.Context, p TestSignalParams) error
TransmitTestSignal asks the Rust modem to queue a TX test signal on a channel. It returns once the modem has accepted the job for transmission; PTT keying, audio play-out, and unkey then happen asynchronously on the TX worker thread (so the 5s wait below is for the IPC round-trip, not the signal's duration).
type ChannelStats ¶
type ChannelStats struct {
Channel uint32 `json:"channel"`
RxFrames uint64 `json:"rx_frames"`
RxBadFCS uint64 `json:"rx_bad_fcs"`
TxFrames uint64 `json:"tx_frames"`
DcdTransitions uint64 `json:"dcd_transitions"`
AudioLevelMark float32 `json:"audio_level_mark"`
AudioLevelSpace float32 `json:"audio_level_space"`
AudioLevelPeak float32 `json:"audio_level_peak"`
DcdState bool `json:"dcd_state"`
}
ChannelStats holds per-channel statistics sourced from StatusUpdate messages.
type Config ¶
type Config struct {
// BinaryPath is the path to graywolf-modem. Defaults to
// "./target/release/graywolf-modem" (the workspace-shared cargo
// output directory at the repo root). Ignored when ExistingSocket
// is set.
BinaryPath string
// SocketDir is where the Unix socket file lives. Defaults to
// os.TempDir(). Ignored when ExistingSocket is set.
SocketDir string
// ExistingSocket, when non-empty, switches the supervisor into
// connect-only mode: it skips fork+exec and the readiness
// handshake and dials this UDS path directly. Used on Android
// where the modem cdylib is loaded in-process by the Kotlin
// Service and exposed at a Service-allocated socket path. The
// caller (Service) owns binary lifecycle and supervises restarts
// at the OS process level; the in-Go supervisor still
// reconnects on session-end so a transient socket close doesn't
// kill the Go process unnecessarily.
ExistingSocket string
// ReadinessTimeout bounds the wait for the child's stdout readiness byte.
// Ignored when ExistingSocket is set.
ReadinessTimeout time.Duration
// ShutdownTimeout bounds graceful shutdown after a Shutdown IPC is sent.
ShutdownTimeout time.Duration
// Store supplies the channel/audio/ptt configuration to push to the child.
Store configstore.ConfigStore
// Metrics receives status updates and frame counts. Optional.
Metrics *metrics.Metrics
// Logger is used for structured logging. Defaults to slog.Default().
Logger *slog.Logger
// FrameBufferSize controls the capacity of the Frames() channel.
FrameBufferSize int
// DcdBufferSize is retained for backwards compatibility but is not
// currently consulted; dcdPublisher uses a fixed per-subscriber buffer.
DcdBufferSize int
}
Config drives a Bridge.
type DeviceLevel ¶
type DeviceLevel struct {
DeviceID uint32 `json:"device_id"`
PeakDBFS float32 `json:"peak_dbfs"`
RmsDBFS float32 `json:"rms_dbfs"`
Clipping bool `json:"clipping"`
}
DeviceLevel holds the latest per-device audio level from the modem.
type InputLevel ¶
type InputLevel struct {
Name string `json:"name"`
PeakDBFS float32 `json:"peak_dbfs"`
HasSignal bool `json:"has_signal"`
Error string `json:"error,omitempty"`
}
InputLevel holds the level scan result for a single input device.
type TestSignalParams ¶ added in v0.13.11
type TestSignalParams struct {
Channel uint32
Kind uint32
Callsign string
CwWpm uint32
FreqAHz uint32
FreqBHz uint32
DurationMs uint32
AltPeriodMs uint32
}
TestSignalParams describes one TX test signal. Kind: 0=CW callsign, 1=steady tone, 2=alternating tone. Unused fields for a given kind are ignored by the modem.