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 ¶
- 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) InjectStatusForTest(channel uint32, rxFrames, rxBadFCS, txFrames uint64, ...)
- func (b *Bridge) IsRunning() bool
- func (b *Bridge) LastModemStdout() []string
- func (b *Bridge) PlayTestTone(ctx context.Context, deviceID uint32, deviceName string, ...) 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()
- type ChannelStats
- type Config
- type DeviceLevel
- type InputLevel
- type State
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 (*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) 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) 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 ¶
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).
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.