m17

package module
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Jul 26, 2025 License: Apache-2.0 Imports: 21 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 jumpstart 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 Gateway

m17-gateway bridges between RF clients and relays/reflectors. It currently supports the CC1200 Pi HAT. When run on a Raspberry Pi with a CC1200 HAT, it can forward M17 voice and packet traffic to and from a reflector/relay, making the Pi/CC1200 HAT an M17 voice and packet hotspot.

To build the gateway 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 fraemwork 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 relay.kc1awv.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 relay.kc1awv.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
	BitsPerSymbol      = 2
	BitsPerPayload     = SymbolsPerPayload * BitsPerSymbol
)
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 (
	LSFLen = 30
	LSDLen = 28
)
View Source
const (
	//CC1200 User's Guide, p. 24
	//0xAD is `DEVIATION_M`, 2097152=2^21
	//+1.0 is the symbol for +0.8kHz
	//40.0e3 is F_TCXO in kHz
	//129 is `CFM_RX_DATA_OUT` register value at max. F_DEV
	//datasheet might have this wrong (it says 64)
	RXSymbolScalingCoeff = (1.0 / (0.8 / (40.0e3 / 2097152 * 0xAD) * 129.0))
	//0xAD is `DEVIATION_M`, 2097152=2^21
	//+0.8kHz is the deviation for symbol +1
	//40.0e3 is F_TCXO in kHz
	//64 is `CFM_TX_DATA_IN` register value for max. F_DEV
	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}
	ExtLSFSyncSymbols  = append(LSFPreambleSymbols, LSFSyncSymbols...)
	StreamSyncSymbols  = []float64{-3, -3, -3, -3, +3, +3, -3, +3}
	PacketSyncSymbols  = []float64{+3, -3, +3, +3, -3, -3, -3, -3}
	BERTSyncSymbols    = []float64{-3, +3, -3, -3, +3, +3, +3, +3}
)
View Source
var EncodedDestinationAllBytes = [6]byte{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 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 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 EncodeCallsign

func EncodeCallsign(callsign string) (*[6]byte, error)

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 Bit

type Bit bool

func (*Bit) Byte

func (b *Bit) Byte() byte

func (*Bit) Set

func (b *Bit) Set(by byte)

type Bits

type Bits [BitsPerPayload]Bit

func InterleaveBits

func InterleaveBits(in *Bits) *Bits

Interleave payload bits.

func NewBits

func NewBits(bs *[]Bit) *Bits

func RandomizeBits

func RandomizeBits(bits *Bits) *Bits

type CC1200Modem

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

func NewCC1200Modem

func NewCC1200Modem(
	port string,
	nRSTPin int,
	paEnablePin int,
	boot0Pin int,
	baudRate int) (*CC1200Modem, error)

func (*CC1200Modem) Close

func (m *CC1200Modem) Close() error

Close the modem

func (*CC1200Modem) Read

func (m *CC1200Modem) Read(buf []byte) (n int, err error)

Read received symbols

func (*CC1200Modem) Reset

func (m *CC1200Modem) Reset() error

Reset the modem

func (*CC1200Modem) SetAFC

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

func (*CC1200Modem) SetFreqCorrection

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

func (*CC1200Modem) SetRXFreq

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

func (*CC1200Modem) SetTXFreq

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

func (*CC1200Modem) SetTXPower

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

func (*CC1200Modem) Start

func (m *CC1200Modem) Start() error

func (*CC1200Modem) TransmitPacket

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

func (*CC1200Modem) TransmitVoiceStream

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

type DCFilter

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

Filter DC from int8 samples by subtracting a moving average

func NewDCFilter

func NewDCFilter(sink chan int8, averageN int) (DCFilter, error)

type Decoder

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

func NewDecoder

func NewDecoder(dashLog *slog.Logger) *Decoder

func (*Decoder) DecodeSymbols

func (d *Decoder) DecodeSymbols(in io.Reader, sendToNetwork func(lsf *LSF, payload []byte, sid, fn uint16) error) error

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

type EncodedCallsign [EncodedCallsignLen]byte

func (EncodedCallsign) Callsign

func (e EncodedCallsign) Callsign() string

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

func (l *LSF) LSFType() LSFType

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
)

type LSFEncryptionSubtype

type LSFEncryptionSubtype byte

type LSFEncryptionType

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

type LSFType

type LSFType byte
const (
	LSFTypePacket LSFType = iota
	LSFTypeStream
)

type Line

type Line interface {
	SetValue(value int) error
	Close() error
}

type Modem

type Modem interface {
	io.ReadCloser
	TransmitPacket(Packet) error
	TransmitVoiceStream(StreamDatagram) error
	Start() error
	Reset() error
	SetAFC(afc bool) error
	SetFreqCorrection(corr int16) error
	SetRXFreq(freq uint32) error
	SetTXFreq(freq uint32) error
	SetTXPower(dbm float32) 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) 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 Preamble

type Preamble byte

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

type PuncturePattern

type PuncturePattern []Bit

type Relay

type Relay struct {
	Server          string
	Port            uint
	Module          byte
	EncodedCallsign [6]byte
	Callsign        string
	// contains filtered or unexported fields
}

func NewRelay

func NewRelay(server string, port uint, module string, callsign string, dashLog *slog.Logger, packetHandler func(Packet) error, streamHandler func(StreamDatagram) error) (*Relay, error)

func (*Relay) Close

func (c *Relay) Close() error

func (*Relay) Connect

func (c *Relay) Connect() error

func (*Relay) Handle

func (c *Relay) Handle()

func (*Relay) SendPacket

func (c *Relay) SendPacket(p Packet) error

func (*Relay) SendStream

func (c *Relay) SendStream(lsf LSF, sid uint16, fn uint16, payload []byte) error

type SampleToSymbol

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

Transform int8 samples to float32 symbols by RRC filtering them

func NewSampleToSymbol

func NewSampleToSymbol(sink chan int8, rrcTaps []float32, scalingCoeff float32) 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

type StreamDatagram

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

func NewStreamDatagram

func NewStreamDatagram(encodedCallsign [6]byte, buffer []byte) (StreamDatagram, error)

type Symbol

type Symbol float32

func AppendBits

func AppendBits(out []Symbol, data *Bits) []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 AppendSyncword

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

AppendSyncword 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 []float32, scalingCoeff float32, phaseInvert bool, samplesPerSymbol int) SymbolToSample

func (*SymbolToSample) Transform

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

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

func (*ViterbiDecoder) Init

func (v *ViterbiDecoder) Init(l int)

Directories

Path Synopsis
cmd
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