modembridge

package
v0.13.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2026 License: GPL-2.0 Imports: 20 Imported by: 0

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 three 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

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)
}

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 New

func New(cfg Config) *Bridge

New builds a bridge. Call Start to run it.

func (*Bridge) ConfigStore

func (b *Bridge) ConfigStore() configstore.ConfigStore

ConfigStore returns the attached configstore (may be nil).

func (*Bridge) DcdEvents

func (b *Bridge) DcdEvents() <-chan *pb.DcdChange

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

func (b *Bridge) DcdSubscribe() <-chan *pb.DcdChange

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

func (b *Bridge) DcdUnsubscribe(ch <-chan *pb.DcdChange)

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) 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

func (b *Bridge) IsRunning() bool

IsRunning reports whether the bridge is actively exchanging messages with the Rust modem child. A bridge is running when both:

  1. the supervisor is in StateRunning (socket connected, configuration pushed, StartAudio sent), and
  2. 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

func (b *Bridge) LastModemStdout() []string

LastModemStdout returns a snapshot of the last ring-buffer lines the modem child wrote to stdout, for crash diagnostics.

func (*Bridge) PlayTestTone

func (b *Bridge) PlayTestTone(ctx context.Context, deviceID uint32, deviceName string, sampleRate, channels uint32) error

PlayTestTone asks the Rust modem to play a test tone on the named output device and waits for the result. Follows the same request/response pattern as EnumerateAudioDevices.

func (*Bridge) ReconfigureAudioDevice

func (b *Bridge) ReconfigureAudioDevice(ctx context.Context, _ uint32) error

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

func (b *Bridge) ReloadConfiguration(ctx context.Context) error

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

func (b *Bridge) SetDeviceGain(deviceID uint32, gainDB float32) error

SetDeviceGain sends a live gain adjustment to the modem (fire-and-forget).

func (*Bridge) Start

func (b *Bridge) Start(ctx context.Context) error

Start launches the supervisor goroutine. It returns immediately.

func (*Bridge) State

func (b *Bridge) State() State

State returns the current supervisor state.

func (*Bridge) Stop

func (b *Bridge) Stop()

Stop cancels the supervisor and waits for it to exit.

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).
	BinaryPath string
	// SocketDir is where the Unix socket file lives. Defaults to os.TempDir().
	SocketDir string
	// ReadinessTimeout bounds the wait for the child's stdout readiness byte.
	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 State

type State int

State names the current supervisor state.

const (
	StateStopped State = iota
	StateStarting
	StateConfiguring
	StateRunning
	StateRestarting
)

func (State) String

func (s State) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL