m17

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Feb 27, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

README

M17 library, gateway and clients, written in Go

M17 Packet Mode is defined in the spec, and messaging is one obvious application. This project started as a set of tools to jump-start messaging and data communications using the M17 ham radio mode. It has evolved to provide more general tool and library support for M17.

There are several tools and a library here:

Tools

M17 Messaging Bridge

m17-bridge is an experimental network service that bridges M17 SMS messages to and from other messaging protocols. It speaks the M17 Internet protocol, so it looks like a reflector to hotspots/repeaters that connect to it.

It bridges to these systems:

  • Discord: An M17 module letter is connected to a Discord channel. All M17 messages are send to the channel and all channel messages are sent to connected hotspots/repeaters. Since all received traffic is sent over the air, membertship in the Discord channels should be restricted to licensed hams.
  • IRC: An M17 module letter is connected to an IRC server and allows sending and receiving private messages via M17 SMS. Because received traffic is sent over the air, accounts on the server should probably be restricted to licensed hams.
  • APRS: An M17 module letter is connected to an APRS-IS server. Radios can use M17 SMS messaging to send APRS messages. When it sees M17 GNSS data, the bridge will also send periodiic APRS position reports. The bridge will send APRS messages to connected radios. A radio must send a message in order to register to receive APRS messages.
M17 Gateway

m17-gateway allows a computer and modem to act as a repeater/hotspot. It also connects RF clients to Internet services such as reflectors. It currently supports the CC1200 Pi HAT, the SX1255 Pi HAT and MMDVM-compatible hotspots and modems. When run on a Raspberry Pi with a compatible HAT or modem, it can forward M17 voice and packet traffic to and from a reflector, making it an M17 voice and packet hotspot. When used with the SX1255 HAT or an MMDVM modem it can form the heart of an M17 repeater.

The easiest way to get a working hotspot, including m17-gateway and a web dashboard is using DK1MI's excellent installer script. Highly recommended!

Another way to install just the gateway is using the APT package from a release on Github. To install it:

  1. Copy the URL for the latest deb package from https://github.com/jancona/m17/releases
  2. From a shell on the Pi do:
wget <latest deb URL>
sudo dpkg -i m17-gateway_<version>_arm64.deb

To build it just run go build in the m17-gateway directory. Because Go natively supports cross-compilation, you can build a Raspberry Pi executable on any machine by running GOOS=linux GOARCH=arm64 go build, the using scp to copy the resulting executable to the Pi.

Usage of gateway:
  -config string
    	Configuration file (default "./gateway.ini")
  -h	Print arguments
  -in string
    	M17 symbol input (default stdin)
  -out string
    	M17 symbol output (default stdout)
Configuration

Bu default, the gateway looks for configuration in gateway.ini in the working directory. See m17-gateway/gateway.ini.sample for details.

GUI Messaging Client

m17-message is a cross-platform GUI network messaging client. It's based on Fybro, a messaging app built using Fyne, a framework for building multi-platform GUI apps in Go. To build the client just run go build in the m17-message directory. For more packaging options, see the Fyne docs.

CLI Messaging Client

m17-text-cli is a rudimentary network messaging client--think Droidstar but for text messages (and not nearly as nice looking). Note that since I started writing this a number of tools (including DroidStar) have added M17 messaging support.

To build the client just run go build in the m17-text-cli directory.

Example: ./m17-text-cli -server m17.openquad.net -callsign N1ADJ

The program will respond with a prompt > . To send a message, enter callsign: message. Incoming messages for you will appear starting with < . To quit, enter /quit.

Sample session:

$ ./m17-text-cli -server m17.openquad.net
> N1ADJ: Hi from my other window!
>
2025-02-06 14:45:45 N0CALL>@ALL: Hi back
> /quit

Command line arguments:

Usage of ./m17-text-cli:
  -callsign string
    	User's callsign (default "N0CALL")
  -h	Print arguments
  -module string
      Module to connect to (default "P")
  -port uint
    	Port the reflector listens on (default 17000)
  -server string
    	Reflector server
CC1200 Modem Emulator

This program emulates the CC1200 Modem firmware. It accepts samples from the gateway and echos them back. It was used for development of the gateway until I had a real CC1200 hat to test with.

Library

The root directory of the project contains the Go library (github.com/jancona/m17) used to implement the M17 protocol parts of the tools. It's pretty rough right now, but I hope to improve it and make it more general and useful over time.

Documentation

Index

Constants

View Source
const (
	SymbolsPerSyncword = 8   //symbols per syncword
	SymbolsPerPayload  = 184 //symbols per payload in a frame
	SymbolsPerFrame    = 192 //symbols per whole 40 ms frame, 40ms * 4800 = 192
	BytesPerFrame      = SymbolsPerFrame * BitsPerSymbol / 8
	BitsPerSymbol      = 2
	BitsPerPayload     = SymbolsPerPayload * BitsPerSymbol
	FrameTime          = 40 * time.Millisecond
	FramesPerSecond    = time.Second / FrameTime
)
View Source
const (
	PacketModeFinalBit = 5 // use 6 bits of final byte
	LSFFinalBit        = 7 // use entire final byte
)
View Source
const (
	ConvolutionK      = 5                         //constraint length K=5
	ConvolutionStates = (1 << (ConvolutionK - 1)) //number of states of the convolutional encoder
)
View Source
const (
	LSFSync    = uint16(0x55F7)
	StreamSync = uint16(0xFF5D)
	PacketSync = uint16(0x75FF)
	BERTSync   = uint16(0xDF55)
	EOTMarker  = uint16(0x555D)
)
View Source
const (
	EncodedCallsignLen    = 6
	MaxCallsignLen        = 9
	DestinationAll        = "@ALL"
	EncodedDestinationAll = 0xFFFFFFFFFFFF
	MaxEncodedCallsign    = 0xEE6B27FFFFFF
	SpecialEncodedRange   = 268697600000000 //40^9+40^8
)
View Source
const (
	// SoftZero represents a confident 0 bit
	SoftZero = 0x0000
	// SoftOne represents a confident 1 bit
	SoftOne = 0xFFFF
	// SoftErasure represents an uncertain/erased bit
	SoftErasure = 0x7FFF
	// SoftThreshold is the decision boundary
	SoftThreshold = 0x7FFF
)

Constants for soft-decision logic

View Source
const (
	MagicLen = 4

	MagicACKN      = "ACKN"
	MagicCONN      = "CONN"
	MagicDISC      = "DISC"
	MagicLSTN      = "LSTN"
	MagicNACK      = "NACK"
	MagicPING      = "PING"
	MagicPONG      = "PONG"
	MagicM17Stream = "M17 "
	MagicM17Packet = "M17P"
)
View Source
const (
	LSFLen = 30
	LSDLen = 28
)
View Source
const (
	RXSymbolScalingCoeff = (1.0 / (0.8 / (40.0e3 / 2097152 * 0xAD) * 130.0))

	TXSymbolScalingCoeff = (0.8 / ((40.0e3 / 2097152) * 0xAD) * 64.0)
)
View Source
const CRCLen = 2
View Source
const ErrorDetectionFailed = 0xFFFFFFFF

ErrorDetectionFailed indicates the decoder could not correct the errors

Variables

View Source
var (
	// TX symbols
	SymbolMap = []Symbol{+1, +3, -1, -3}

	// symbol list (RX)
	SymbolList = []Symbol{-3, -1, +1, +3}

	// End of Transmission symbol pattern
	EOTSymbols = []Symbol{+3, +3, +3, +3, +3, +3, -3, +3}
)
View Source
var (
	LSFPreambleSymbols = []float64{+3, -3, +3, -3, +3, -3, +3, -3}

	LSFSyncSymbols    = []float64{+3, +3, +3, +3, -3, -3, +3, -3} // 0x55F7
	ExtLSFSyncSymbols = append(LSFPreambleSymbols, LSFSyncSymbols...)
	StreamSyncSymbols = []float64{-3, -3, -3, -3, +3, +3, -3, +3} // 0xFF5D
	PacketSyncSymbols = []float64{+3, -3, +3, +3, -3, -3, -3, -3} // 0x75FF
	BERTSyncSymbols   = []float64{-3, +3, -3, -3, +3, +3, +3, +3} // 0xDF55
	EOTMarkerSymbols  = []float64{+3, +3, +3, +3, +3, +3, -3, +3} // 0x555D
)
View Source
var (
	LSFSyncBytes    = []byte{0x55, 0xF7}
	StreamSyncBytes = []byte{0xFF, 0x5D}
	PacketSyncBytes = []byte{0x75, 0xFF}
	BERTSyncBytes   = []byte{0xDF, 0x55}
	EOTMarkerBytes  = []byte{0x55, 0x5D}
)
View Source
var (
	ErrMMDVMReadTimeout         = errors.New("read timeout")
	ErrUnsupportedModemProtocol = errors.New("unsupported MMDVM protocol version")
	ErrModemNAK                 = errors.New("modem returned NAK")
)
View Source
var CallsignRegex = regexp.MustCompile(`([a-zA-Z0-9]{1,3}/)?[a-zA-Z0-9]{1,3}[0-9][a-zA-Z0-9]{0,3}[a-zA-Z](/[a-zA-Z0-9]{1,3})?`)
View Source
var EncodedDestinationAllBytes = EncodedCallsign{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
View Source
var PacketPuncturePattern = PuncturePattern{true, true, true, true, true, true, true, false}
View Source
var StreamPuncturePattern = PuncturePattern{true, true, true, true, true, true, true, true, true, true, true, false}

Functions

func CRC

func CRC(in []byte) uint16

Calculate CRC value.

func CalculateHammingDistance

func CalculateHammingDistance(a, b uint32) int

CalculateHammingDistance calculates the Hamming distance between two codewords

func CalculateSyndrome

func CalculateSyndrome(codeword uint32) uint16

CalculateSyndrome calculates the syndrome for error detection

func DecodeCallsign

func DecodeCallsign(encoded []byte) (string, error)

func DecodeLICH

func DecodeLICH(inp []SoftBit) []byte

DecodeLICH decodes LICH into a 6-byte array inp: pointer to an array of 96 soft bits

func Encode24

func Encode24(data uint16) uint32

Encode24 encodes a 12-bit value with Golay(24, 12) data: 12-bit input value (right justified) returns: 24-bit Golay codeword

func EncodeLICH

func EncodeLICH(inp []uint8) []byte

EncodeLICH encodes 6 bytes into 12 bytes using Golay encoding

func EuclNorm

func EuclNorm(s1, s2 []Symbol, n int) float64

func GetErrorCorrectionCapability

func GetErrorCorrectionCapability() int

GetErrorCorrectionCapability returns the maximum number of errors that can be corrected

func GetMinimumDistance

func GetMinimumDistance() int

GetMinimumDistance returns the minimum Hamming distance of the code

func HardDecode24

func HardDecode24(codeword uint32) (uint16, error)

HardDecode24 performs hard-decision decoding of a Golay(24,12) codeword This is a simpler version that works with hard bits (0 or 1)

func IntToSoft

func IntToSoft(out []SoftBit, value uint16, size uint8)

IntToSoft converts an integer to soft-valued bit array

func IsValidCodeword

func IsValidCodeword(codeword uint32) bool

IsValidCodeword checks if a 24-bit word is a valid Golay codeword

func NormalizeCallsignModule

func NormalizeCallsignModule(callsign string) string

For regular callsigns, if the callsign ends with a space followed by a module letter, put the module letter in position 9 (the convention) with spaces preceding it

func SoftCalcChecksum

func SoftCalcChecksum(out, value []SoftBit)

SoftCalcChecksum calculates checksum for soft-valued data This follows the C implementation exactly

func SoftDecode24

func SoftDecode24(codeword [24]SoftBit) uint16

SoftDecode24 performs soft decode of Golay(24, 12) codeword

func SoftDetectErrors

func SoftDetectErrors(codeword []SoftBit) uint32

SoftDetectErrors detects errors in a soft-valued Golay(24, 12) codeword

func SoftPopCount

func SoftPopCount(in []uint16, size uint8) uint32

SoftPopCount performs soft-valued equivalent of popcount

func SoftToInt

func SoftToInt(in []SoftBit, size uint8) uint16

SoftToInt converts soft-valued bit array to integer

func SoftXOR

func SoftXOR(out, a, b []SoftBit, size uint8)

SoftXOR performs XOR operation on arrays of soft-valued bits

Types

type BatchFMModulator added in v0.5.0

type BatchFMModulator struct {
	// contains filtered or unexported fields
}

BatchFMModulator converts a real-valued baseband signal (representing frequency deviation) to complex IQ samples via FM modulation. Phase accumulates continuously across calls for seamless multi-frame TX.

The input signal is scaled such that a value of 1.0 corresponds to deviationHz Hz of frequency deviation. For M17 4FSK:

symbol +1 → +800 Hz, symbol +3 → +2400 Hz

So deviationHz should be 800.0, and the RRC output (which equals the symbol values at optimal sample points) maps directly to the correct deviation.

func NewBatchFMModulator added in v0.5.0

func NewBatchFMModulator(sampleRate float64) *BatchFMModulator

NewBatchFMModulator creates an FM modulator for the given sample rate.

func (*BatchFMModulator) Modulate added in v0.5.0

func (fm *BatchFMModulator) Modulate(baseband []float64, deviationHz float64) []complex128

Modulate converts baseband samples to complex IQ via FM modulation. deviationHz is the deviation in Hz per unit of input (e.g., 800.0 for M17). Output length = len(baseband).

func (*BatchFMModulator) Reset added in v0.5.0

func (fm *BatchFMModulator) Reset()

Reset clears the phase accumulator for a new transmission.

type BatchResampler added in v0.5.0

type BatchResampler struct {
	// contains filtered or unexported fields
}

BatchResampler performs polyphase rational resampling on a batch of samples. Output rate = input rate * interpFactor / decimFactor. State persists across calls for waveform continuity.

func NewBatchResampler added in v0.5.0

func NewBatchResampler(interpFactor, decimFactor int) *BatchResampler

NewBatchResampler creates a batch rational resampler. For 24000 → 125000 Hz, use interp=125, decim=24.

func (*BatchResampler) Process added in v0.5.0

func (r *BatchResampler) Process(input []float64) []float64

Process resamples a batch of input samples. Output length ≈ len(input) * interpFactor / decimFactor.

func (*BatchResampler) Reset added in v0.5.0

func (r *BatchResampler) Reset()

Reset clears the delay line for a new transmission.

type Bit

type Bit bool

func ConvolutionalEncode

func ConvolutionalEncode(in []byte, puncturePattern PuncturePattern, finalBit byte) ([]Bit, error)

ConvolutionalEncode takes a slice of bytes and a puncture pattern and returns an a slice of bool with each element representing one bit in the encoded message

in Input bytes puncturePattern the puncture pattern to use finalBit The last bit of the final byte to encode. A number between 0 and 7. (That is, the number of bits from the last byte to use minus one.)

func ConvolutionalEncodeStream

func ConvolutionalEncodeStream(lichBits []Bit, sd StreamDatagram) ([]Bit, error)

func (*Bit) Byte

func (b *Bit) Byte() byte

func (*Bit) Set

func (b *Bit) Set(by byte)

type CC1200Modem

type CC1200Modem struct {
	// contains filtered or unexported fields
}

func NewCC1200Modem

func NewCC1200Modem(
	rxFrequency uint32,
	txFrequency uint32,
	power int8,
	frequencyCorr int16,
	afc bool,
	modemCfg *ini.Section) (*CC1200Modem, error)

func (*CC1200Modem) Close

func (m *CC1200Modem) Close() error

Close the modem

func (*CC1200Modem) Reset

func (m *CC1200Modem) Reset() error

Reset the modem

func (*CC1200Modem) Start

func (m *CC1200Modem) Start() error

func (*CC1200Modem) StartDecoding added in v0.2.0

func (m *CC1200Modem) StartDecoding(sink func(typ uint16, softBits []SoftBit))

func (*CC1200Modem) TransmitPacket

func (m *CC1200Modem) TransmitPacket(p Packet) error

func (*CC1200Modem) TransmitVoiceStream

func (m *CC1200Modem) TransmitVoiceStream(sd StreamDatagram) error

type ComplexDCRemoval added in v0.5.0

type ComplexDCRemoval struct {
	Transform[complex128, complex128]
	// contains filtered or unexported fields
}

ComplexDCRemoval removes DC offset from a complex IQ stream using an exponential moving average high-pass filter applied independently to I and Q.

func NewComplexDCRemoval added in v0.5.0

func NewComplexDCRemoval(sink chan complex128, alpha float64) ComplexDCRemoval

type Converter added in v0.2.10

type Converter[T Number, U Number] struct {
	Transform[T, U]
}

scale samples by a factor

func NewConverter added in v0.2.10

func NewConverter[T Number, U Number](sink chan T) Converter[T, U]

type DCFilter

type DCFilter struct {
	Transform[float64, float64]
	// contains filtered or unexported fields
}

Filter DC from int8 samples by subtracting a moving average

func NewDCFilter

func NewDCFilter(sink chan float64, averageCnt int) (DCFilter, error)

type DashboardLogger added in v0.3.3

type DashboardLogger struct {
	*slog.Logger
	// contains filtered or unexported fields
}

func NewDashboardLogger added in v0.3.3

func NewDashboardLogger(l *slog.Logger) *DashboardLogger

func (*DashboardLogger) Log added in v0.3.3

func (l *DashboardLogger) Log(logType string, logSubtype string, addlArgs ...any)

func (*DashboardLogger) LogFrame added in v0.3.3

func (l *DashboardLogger) LogFrame(lsf *LSF, logType string, logSubtype string, addlArgs ...any)

func (*DashboardLogger) LogGNSS added in v0.3.3

func (l *DashboardLogger) LogGNSS(lsf *LSF, logType string)

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

func NewDecoder

func NewDecoder(
	receivedRFLSF func(lsf LSF, ber float64) error,
	receivedRFStream func(lsf LSF, payload []byte, sid, fn uint16, ber float64) error,
	receivedRFStreamLICH func(lsf LSF, ber float64) error,
	receivedRFStreamEOT func(lsf LSF, sid, fn uint16, ber float64) error,
	receivedRFPacket func(lsf LSF, payload []byte, ber float64) error,
) *Decoder

func (*Decoder) DecodeFrame added in v0.2.0

func (d *Decoder) DecodeFrame(typ uint16, softBits []SoftBit)

type Downsampler

type Downsampler[T any] struct {
	Transform[T, T]
	// contains filtered or unexported fields
}

Downsample a stream by returning one out of each N values

func NewDownsampler

func NewDownsampler[T any](sink chan T, factor int, offset int) (Downsampler[T], error)

type DummyModem

type DummyModem struct {
	In  io.ReadCloser
	Out io.WriteCloser
	// contains filtered or unexported fields
}

func (*DummyModem) Close

func (m *DummyModem) Close() error

func (*DummyModem) Read

func (m *DummyModem) Read(p []byte) (n int, err error)

func (*DummyModem) Reset

func (m *DummyModem) Reset() error

func (*DummyModem) SetAFC

func (m *DummyModem) SetAFC(afc bool) error

func (*DummyModem) SetFreqCorrection

func (m *DummyModem) SetFreqCorrection(corr int16) error

func (*DummyModem) SetRXFreq

func (m *DummyModem) SetRXFreq(freq uint32) error

func (*DummyModem) SetTXFreq

func (m *DummyModem) SetTXFreq(freq uint32) error

func (*DummyModem) SetTXPower

func (m *DummyModem) SetTXPower(dbm float32) error

func (*DummyModem) Start

func (m *DummyModem) Start() error

func (*DummyModem) StartDecoding added in v0.2.0

func (m *DummyModem) StartDecoding(sink func(typ uint16, softBits []SoftBit))

func (*DummyModem) TransmitPacket

func (m *DummyModem) TransmitPacket(p Packet) error

func (*DummyModem) TransmitVoiceStream

func (m *DummyModem) TransmitVoiceStream(sd StreamDatagram) error

func (*DummyModem) Write

func (m *DummyModem) Write(buf []byte) (n int, err error)

type ECD added in v0.1.21

type ECD struct {
	Callsign1 *EncodedCallsign
	Callsign2 *EncodedCallsign
}

func NewECDFromMeta added in v0.1.21

func NewECDFromMeta(meta [14]byte) ECD

func (ECD) String added in v0.1.21

func (e ECD) String() string

type EncodedCallsign

type EncodedCallsign [EncodedCallsignLen]byte

func EncodeCallsign

func EncodeCallsign(callsign string) (*EncodedCallsign, error)

func (EncodedCallsign) Callsign

func (e EncodedCallsign) Callsign() string

type FMDemodulator added in v0.5.0

type FMDemodulator struct {
	Transform[complex128, float64]
	// contains filtered or unexported fields
}

FMDemodulator extracts instantaneous frequency from a complex IQ stream using the conjugate-multiply-and-arg method. Output is in radians per sample.

func NewFMDemodulator added in v0.5.0

func NewFMDemodulator(sink chan complex128) FMDemodulator

type GNSS added in v0.1.21

type GNSS struct {
	DataSource        byte
	StationType       byte
	Radius            byte
	Bearing           uint16
	Latitude          float32
	Longitude         float32
	Altitude          float32
	Speed             float32
	ValidLatLon       bool
	ValidAltitude     bool
	ValidBearingSpeed bool
	ValidRadius       bool
}

func NewGNSSFromMeta added in v0.1.21

func NewGNSSFromMeta(meta [14]byte) *GNSS

func (GNSS) String added in v0.1.21

func (g GNSS) String() string

type Host added in v0.1.7

type Host struct {
	Name   string
	Server string
	Port   uint
}

type Hostfile added in v0.1.7

type Hostfile struct {
	Hosts map[string]Host
}

func NewHostfile added in v0.1.7

func NewHostfile(name string) (*Hostfile, error)

type IIRFilter added in v0.2.10

type IIRFilter struct {
	Transform[float64, float64]
	// contains filtered or unexported fields
}

IIRFilter represents a simple first-order IIR filter

func NewIIRFilter added in v0.2.10

func NewIIRFilter(sink chan float64, b, a []float64) (IIRFilter, error)

NewIIRFilter initializes and returns a new IIR filter

func (*IIRFilter) Source added in v0.2.10

func (t *IIRFilter) Source() chan float64

type InetClient added in v0.4.0

type InetClient struct {
	Name        string
	Server      string
	Port        uint
	Module      byte
	EncodedName *EncodedCallsign
	// contains filtered or unexported fields
}

func NewInetClient added in v0.4.0

func NewInetClient(name string, server string, port uint, module string, callsign string, dashLog *DashboardLogger, packetHandler func(Packet) error, streamHandler func(StreamDatagram) error) (*InetClient, error)

func (*InetClient) Close added in v0.4.0

func (r *InetClient) Close() error

func (*InetClient) Connect added in v0.4.0

func (r *InetClient) Connect() error

func (*InetClient) SendPacket added in v0.4.0

func (r *InetClient) SendPacket(p Packet) error

func (*InetClient) SendStream added in v0.4.0

func (r *InetClient) SendStream(sd StreamDatagram) error

type LSF

type LSF struct {
	Dst  EncodedCallsign
	Src  EncodedCallsign
	Type [typeLen]byte
	Meta [metaLen]byte
	CRC  [CRCLen]byte
}

Link Setup Frame

func NewEmptyLSF

func NewEmptyLSF() LSF

func NewLSF

func NewLSF(destCall, sourceCall string, t LSFType, dt LSFDataType, can byte) (LSF, error)

func NewLSFFromBytes

func NewLSFFromBytes(buf []byte) *LSF

func NewLSFFromLSD

func NewLSFFromLSD(lsd []byte) *LSF

func (*LSF) CAN

func (l *LSF) CAN() byte

func (*LSF) CalcCRC

func (l *LSF) CalcCRC() uint16

Calculate CRC for this LSF

func (*LSF) CheckCRC

func (l *LSF) CheckCRC() bool

Check if the CRC is correct

func (*LSF) DataType added in v0.1.21

func (l *LSF) DataType() LSFDataType

func (*LSF) ECD added in v0.1.21

func (l *LSF) ECD() *ECD

func (*LSF) EncryptionSubtype added in v0.1.21

func (l *LSF) EncryptionSubtype() byte

func (*LSF) EncryptionType added in v0.1.21

func (l *LSF) EncryptionType() LSFEncryptionType

func (*LSF) GNSS added in v0.1.21

func (l *LSF) GNSS() *GNSS

func (*LSF) LSFType

func (l *LSF) LSFType() LSFType

func (*LSF) SetECD added in v0.2.11

func (l *LSF) SetECD(slot1, slot2 *EncodedCallsign)

Replace META with Extended Callsign Data

func (LSF) String

func (l LSF) String() string

func (*LSF) ToBytes

func (l *LSF) ToBytes() []byte

Convert this LSF to a byte slice suitable for transmission

func (*LSF) ToLSDBytes

func (l *LSF) ToLSDBytes() []byte

Convert this LSF to a byte slice suitable for transmission

type LSFDataType

type LSFDataType byte
const (
	LSFDataTypeReserved LSFDataType = iota
	LSFDataTypeData
	LSFDataTypeVoice
	LSFDataTypeVoiceData
)

func (LSFDataType) String added in v0.1.21

func (t LSFDataType) String() string

type LSFEncryptionType

type LSFEncryptionType byte
const (
	LSFEncryptionTypeNone LSFEncryptionType = iota
	LSFEncryptionTypeScrambler
	LSFEncryptionTypeAES
	LSFEncryptionTypeOther
)

func (LSFEncryptionType) String added in v0.1.21

func (t LSFEncryptionType) String() string

type LSFType

type LSFType byte
const (
	LSFTypePacket LSFType = iota
	LSFTypeStream
)

func (LSFType) String added in v0.1.21

func (t LSFType) String() string

type MMDVMConfig added in v0.2.0

type MMDVMConfig struct {
	// contains filtered or unexported fields
}

type MMDVMModem added in v0.2.0

type MMDVMModem struct {
	// contains filtered or unexported fields
}

func NewMMDVMModem added in v0.2.0

func NewMMDVMModem(
	rxFrequency uint32,
	txFrequency uint32,
	power float32,
	frequencyCorr int16,
	afc bool,
	modemCfg *ini.Section,
	duplex bool) (*MMDVMModem, error)

func (*MMDVMModem) Close added in v0.2.0

func (m *MMDVMModem) Close() error

Close the modem

func (*MMDVMModem) Reset added in v0.2.0

func (m *MMDVMModem) Reset() error

Reset the modem

func (*MMDVMModem) Start added in v0.2.0

func (m *MMDVMModem) Start() error

func (*MMDVMModem) StartDecoding added in v0.2.0

func (m *MMDVMModem) StartDecoding(sink func(typ uint16, softBits []SoftBit))

func (*MMDVMModem) TransmitPacket added in v0.2.0

func (m *MMDVMModem) TransmitPacket(p Packet) error

func (*MMDVMModem) TransmitVoiceStream added in v0.2.0

func (m *MMDVMModem) TransmitVoiceStream(sd StreamDatagram) error

type MaxAbsDecimator added in v0.5.0

type MaxAbsDecimator struct {
	Transform[float32, float32]
	// contains filtered or unexported fields
}

MaxAbsDecimator reduces the sample rate by picking the sample with the largest absolute value from each group of N input samples. This performs a simple form of symbol clock recovery: the RRC-filtered symbol peak has the largest magnitude, so picking max-abs approximates optimal timing.

func NewMaxAbsDecimator added in v0.5.0

func NewMaxAbsDecimator(sink chan float32, factor int) MaxAbsDecimator

type Modem

type Modem interface {
	StartDecoding(sink func(typ uint16, softBits []SoftBit))
	Start() error
	Reset() error
	Close() error
	TransmitPacket(Packet) error
	TransmitVoiceStream(StreamDatagram) error
}

type Number

type Number interface {
	constraints.Integer | constraints.Float
}

type Packet

type Packet struct {
	LSF     *LSF
	Type    PacketType
	Payload []byte
	CRC     uint16
}

M17 packet

func NewPacket

func NewPacket(dst, src string, t PacketType, data []byte) (*Packet, error)

func NewPacketFromBytes

func NewPacketFromBytes(buf []byte) Packet

func (*Packet) CalcCRC added in v0.2.20

func (p *Packet) CalcCRC() uint16

Calculate CRC

func (*Packet) CheckCRC

func (p *Packet) CheckCRC() bool

Check if the CRC is correct

func (*Packet) Encode

func (p *Packet) Encode() ([]Symbol, error)

func (*Packet) PayloadBytes

func (p *Packet) PayloadBytes() []byte

Convert the payload (type, message and CRC) to a byte slice suitable for transmission

func (Packet) String

func (p Packet) String() string

func (*Packet) ToBytes

func (p *Packet) ToBytes() []byte

Convert this Packet to a byte slice suitable for transmission

type PacketType

type PacketType rune
const (
	PacketTypeRAW     PacketType = 0x00
	PacketTypeAX25    PacketType = 0x01
	PacketTypeAPRS    PacketType = 0x02
	PacketType6LoWPAN PacketType = 0x03
	PacketTypeIPv4    PacketType = 0x04
	PacketTypeSMS     PacketType = 0x05
	PacketTypeWinlink PacketType = 0x06
)

type PayloadBits added in v0.2.0

type PayloadBits [BitsPerPayload]Bit

func InterleaveBits

func InterleaveBits(in *PayloadBits) *PayloadBits

Interleave payload bits.

func NewPayloadBits added in v0.2.0

func NewPayloadBits(bs []Bit) *PayloadBits

func RandomizeBits

func RandomizeBits(bits *PayloadBits) *PayloadBits

type PolyphaseDecimator added in v0.5.0

type PolyphaseDecimator struct {
	Transform[complex128, complex128]
	// contains filtered or unexported fields
}

PolyphaseDecimator performs efficient decimation with FIR filtering using a polyphase decomposition. The input is complex IQ; the output is complex IQ at a lower sample rate. The FIR acts as a lowpass anti-alias/channel filter.

The full-rate FIR H(z) is decomposed into M subfilters E_0..E_{M-1}. For each block of M input samples, ONE output is produced:

y[n] = Σ_{p=0}^{M-1} Σ_{k} E_p[k] · x[nM − p − kM]

All M input samples contribute to the output through their respective phase subfilters, providing proper anti-alias filtering before decimation.

func NewPolyphaseDecimator added in v0.5.0

func NewPolyphaseDecimator(sink chan complex128, taps []float64, decimFactor int) PolyphaseDecimator

NewPolyphaseDecimator creates a polyphase decimating FIR filter. taps is the full prototype lowpass FIR filter (length should be a multiple of decimFactor). decimFactor is the decimation ratio.

type Preamble

type Preamble byte

Preamble type (0 for LSF, 1 for BERT).

type PuncturePattern

type PuncturePattern []Bit

type RationalResampler added in v0.5.0

type RationalResampler struct {
	Transform[float64, float64]
	// contains filtered or unexported fields
}

RationalResampler converts between sample rates using a polyphase FIR filter. Output rate = input rate * interpFactor / decimFactor. The prototype lowpass filter runs at interpFactor × the input rate.

func NewRationalResampler added in v0.5.0

func NewRationalResampler(sink chan float64, interpFactor, decimFactor int) RationalResampler

NewRationalResampler creates a rational resampler. interpFactor/decimFactor is the rate change ratio. For 12500 → 24000 Hz, use interp=48, decim=25.

type SX1255Modem added in v0.5.0

type SX1255Modem struct {
	// contains filtered or unexported fields
}

SX1255Modem implements the Modem interface for the SX1255 RF transceiver HAT. Unlike MMDVM and CC1200 modems which have on-board microcontrollers, the SX1255 is a raw IQ analog front-end — all baseband DSP is performed in software.

The SX1255 supports full-duplex operation: RX runs continuously even during TX.

func NewSX1255Modem added in v0.5.0

func NewSX1255Modem(
	rxFrequency uint32,
	txFrequency uint32,
	modemCfg *ini.Section,
) (*SX1255Modem, error)

NewSX1255Modem creates and initializes an SX1255 modem from INI configuration.

func (*SX1255Modem) Close added in v0.5.0

func (m *SX1255Modem) Close() error

Close shuts down the SX1255 modem, releasing all resources.

func (*SX1255Modem) Reset added in v0.5.0

func (m *SX1255Modem) Reset() error

Reset performs a hardware reset of the SX1255 chip and re-initializes it.

func (*SX1255Modem) Start added in v0.5.0

func (m *SX1255Modem) Start() error

Start enables the RX path.

func (*SX1255Modem) StartDecoding added in v0.5.0

func (m *SX1255Modem) StartDecoding(sink func(typ uint16, softBits []SoftBit))

StartDecoding registers the frame sink callback and starts the symbol processing goroutine.

func (*SX1255Modem) TransmitPacket added in v0.5.0

func (m *SX1255Modem) TransmitPacket(p Packet) error

TransmitPacket sends a packet over RF. Full-duplex: RX continues running during TX.

func (*SX1255Modem) TransmitVoiceStream added in v0.5.0

func (m *SX1255Modem) TransmitVoiceStream(sd StreamDatagram) error

TransmitVoiceStream sends a voice stream frame over RF. Full-duplex: RX continues running during TX.

type SampleToSymbol

type SampleToSymbol struct {
	Transform[float64, float32]
	// contains filtered or unexported fields
}

Transform int8 samples to float32 symbols by RRC filtering them

func NewSampleToSymbol

func NewSampleToSymbol(sink chan float64, rrcTaps []float64, scalingCoeff float64) SampleToSymbol

func (*SampleToSymbol) Source

func (t *SampleToSymbol) Source() chan float32

type Scaler

type Scaler[T Number] struct {
	Transform[T, T]
	// contains filtered or unexported fields
}

scale samples by a factor

func NewScaler

func NewScaler[T Number](sink chan T, factor T) Scaler[T]

type SoftBit

type SoftBit uint16

func DeinterleaveSoftBits

func DeinterleaveSoftBits(softBits []SoftBit) []SoftBit

func DerandomizeSoftBits

func DerandomizeSoftBits(softBits []SoftBit) []SoftBit

func SoftBitXOR

func SoftBitXOR(a, b SoftBit) SoftBit

SoftBitXOR performs XOR operation on soft-valued bits This should match the C test expectations exactly

func (SoftBit) String added in v0.2.0

func (b SoftBit) String() string

type StreamDatagram

type StreamDatagram struct {
	StreamID    uint16
	FrameNumber uint16
	LastFrame   bool
	LSF         *LSF
	Payload     [16]byte
}

func NewStreamDatagram

func NewStreamDatagram(streamID uint16, frameNumber uint16, lsf *LSF, payload []byte) StreamDatagram

func NewStreamDatagramFromBytes added in v0.1.23

func NewStreamDatagramFromBytes(buffer []byte) (StreamDatagram, error)

func (StreamDatagram) String added in v0.1.23

func (sd StreamDatagram) String() string

func (StreamDatagram) ToBytes added in v0.1.23

func (sd StreamDatagram) ToBytes() []byte

type Symbol

type Symbol float32

func AppendBits

func AppendBits(out []Symbol, data *PayloadBits) []Symbol

func AppendEOT

func AppendEOT(out []Symbol) []Symbol

Generate symbol stream for the End of Transmission marker.

func AppendPreamble

func AppendPreamble(out []Symbol, typ Preamble) []Symbol

AppendPreamble generates symbol stream for a preamble.

func AppendSyncwordSymbols added in v0.2.0

func AppendSyncwordSymbols(out []Symbol, syncword uint16) []Symbol

AppendSyncwordSymbols generates the symbol stream for a syncword.

type SymbolToSample

type SymbolToSample struct {
	// contains filtered or unexported fields
}

Transform float32 symbols to int8 samples

func NewSymbolToSample

func NewSymbolToSample(rrcTaps []float64, scalingCoeff float32, phaseInvert bool, samplesPerSymbol int) SymbolToSample

func (*SymbolToSample) Transform

func (t *SymbolToSample) Transform(symbols []Symbol) []byte

type TXPulseShaper added in v0.5.0

type TXPulseShaper struct {
	// contains filtered or unexported fields
}

TXPulseShaper performs RRC pulse shaping on M17 symbols, producing samplesPerSymbol output samples per input symbol. State persists across calls so that consecutive invocations produce a continuous waveform.

Algorithm: for each symbol, insert the symbol value followed by (samplesPerSymbol-1) zeros into a delay line, then convolve with the RRC FIR taps to produce each output sample.

func NewTXPulseShaper added in v0.5.0

func NewTXPulseShaper(taps []float64, sps int) *TXPulseShaper

NewTXPulseShaper creates a new TX pulse shaper. taps is the RRC filter (e.g., rrcTaps5), sps is samples per symbol (e.g., 5).

func (*TXPulseShaper) Process added in v0.5.0

func (p *TXPulseShaper) Process(symbols []Symbol) []float64

Process converts a batch of symbols to pulse-shaped baseband samples. Output length = len(symbols) * samplesPerSymbol.

func (*TXPulseShaper) Reset added in v0.5.0

func (p *TXPulseShaper) Reset()

Reset clears the delay line for a new transmission.

type Transform

type Transform[I any, O any] struct {
	// contains filtered or unexported fields
}

Generic transformation

func NewTransform

func NewTransform[I any, O any](sink chan I, transform func(I) []O, sourceSize int) Transform[I, O]

func (*Transform[I, O]) Source

func (t *Transform[I, O]) Source() chan O

type ViterbiDecoder

type ViterbiDecoder struct {
	// contains filtered or unexported fields
}

func (*ViterbiDecoder) DecodePunctured

func (v *ViterbiDecoder) DecodePunctured(puncturedSoftBits []SoftBit, puncturePattern PuncturePattern) ([]byte, int)

func (*ViterbiDecoder) Init

func (v *ViterbiDecoder) Init(l int)

Directories

Path Synopsis
cmd
m17-bridge command
m17-gateway command
m17-message command
m17-text-cli command
modem-emulator command

Jump to

Keyboard shortcuts

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