Skip to content

Commit

Permalink
Merge pull request pd0mz#1 from yeyus/jft_talker_alias
Browse files Browse the repository at this point in the history
refactor link control and add talker alias support
  • Loading branch information
tehmaze authored Sep 4, 2019
2 parents f3e5caa + e270d6a commit ecea90f
Show file tree
Hide file tree
Showing 7 changed files with 518 additions and 193 deletions.
70 changes: 70 additions & 0 deletions lc/gpsinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package lc

import (
"fmt"

dmr "github.com/pd0mz/go-dmr"
)

// Data Format
// ref: ETSI TS 102 361-2 7.2.18
const (
ErrorLT2m uint8 = iota
ErrorLT20m
ErrorLT200m
ErrorLT2km
ErrorLT20km
ErrorLE200km
ErrorGT200km
ErrorUnknown
)

// PositionErrorName is a map of position error to string.
var PositionErrorName = map[uint8]string{
ErrorLT2m: "< 2m",
ErrorLT20m: "< 20m",
ErrorLT200m: "< 200m",
ErrorLT2km: "< 2km",
ErrorLT20km: "< 20km",
ErrorLE200km: "<= 200km",
ErrorGT200km: "> 200km",
ErrorUnknown: "unknown",
}

// GpsInfoPDU Conforms to ETSI TS 102 361-2 7.1.1.3
type GpsInfoPDU struct {
PositionError uint8
Longitude uint32
Latitude uint32
}

// ParseGpsInfoPDU parse gps info pdu
func ParseGpsInfoPDU(data []byte) (*GpsInfoPDU, error) {
if len(data) != 7 {
return nil, fmt.Errorf("dmr/lc/talkeralias: expected 7 bytes, got %d", len(data))
}

return &GpsInfoPDU{
PositionError: (data[0] & dmr.B00001110) >> 1,
Longitude: uint32(data[0]&dmr.B00000001)<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3]),
Latitude: uint32(data[4])<<16 | uint32(data[5])<<8 | uint32(data[6]),
}, nil
}

// Bytes returns GpsInfoPDU as bytes
func (g *GpsInfoPDU) Bytes() []byte {
return []byte{
uint8((g.PositionError&dmr.B00000111)<<1) | uint8((g.Longitude>>24)&dmr.B00000001),
uint8(g.Longitude >> 16),
uint8(g.Longitude >> 8),
uint8(g.Longitude),
uint8(g.Latitude >> 16),
uint8(g.Latitude >> 8),
uint8(g.Latitude),
}
}

func (g *GpsInfoPDU) String() string {
return fmt.Sprintf("GpsInfo: [ error: %s lon: %d lat: %d ]",
PositionErrorName[g.PositionError], g.Longitude, g.Latitude)
}
173 changes: 173 additions & 0 deletions lc/lc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package lc

import (
"errors"
"fmt"

"github.com/pd0mz/go-dmr"
"github.com/pd0mz/go-dmr/fec"
)

// Full Link Control Opcode
const (
GroupVoiceChannelUser uint8 = 0x00 // B000000
UnitToUnitVoiceChannelUser uint8 = 0x03 // B000011
TalkerAliasHeader uint8 = 0x04 // B000100
TalkerAliasBlk1 uint8 = 0x05 // B000101
TalkerAliasBlk2 uint8 = 0x06 // B000110
TalkerAliasBlk3 uint8 = 0x07 // B000111
GpsInfo uint8 = 0x08 // B001000
)

// LC is a Link Control message.
type LC struct {
CallType uint8
Opcode uint8
FeatureSetID uint8
VoiceChannelUser *VoiceChannelUserPDU
GpsInfo *GpsInfoPDU
TalkerAliasHeader *TalkerAliasHeaderPDU
TalkerAliasBlocks [3]*TalkerAliasBlockPDU
}

// Bytes packs the Link Control message to bytes.
func (lc *LC) Bytes() []byte {
var (
lcHeader = []byte{
lc.Opcode,
lc.FeatureSetID,
}
innerPdu []byte
)

switch lc.Opcode {
case GroupVoiceChannelUser:
fallthrough
case UnitToUnitVoiceChannelUser:
innerPdu = lc.VoiceChannelUser.Bytes()
case TalkerAliasHeader:
innerPdu = lc.TalkerAliasHeader.Bytes()
case TalkerAliasBlk1:
innerPdu = lc.TalkerAliasBlocks[0].Bytes()
case TalkerAliasBlk2:
innerPdu = lc.TalkerAliasBlocks[1].Bytes()
case TalkerAliasBlk3:
innerPdu = lc.TalkerAliasBlocks[2].Bytes()
case GpsInfo:
innerPdu = lc.GpsInfo.Bytes()
}

return append(lcHeader, innerPdu...)
}

func (lc *LC) String() string {
var (
header = fmt.Sprintf("opcode %d, call type %s, feature set id %d",
lc.Opcode, dmr.CallTypeName[lc.CallType], lc.FeatureSetID)
r string
)

switch lc.Opcode {
case GroupVoiceChannelUser:
fallthrough
case UnitToUnitVoiceChannelUser:
r = fmt.Sprintf("%s %v", header, lc.VoiceChannelUser)
case GpsInfo:
r = fmt.Sprintf("%s %v", header, lc.GpsInfo)
case TalkerAliasHeader:
r = fmt.Sprintf("%s %v", header, lc.TalkerAliasHeader)
case TalkerAliasBlk1:
r = fmt.Sprintf("%s %v", header, lc.TalkerAliasBlocks[0])
case TalkerAliasBlk2:
r = fmt.Sprintf("%s %v", header, lc.TalkerAliasBlocks[1])
case TalkerAliasBlk3:
r = fmt.Sprintf("%s %v", header, lc.TalkerAliasBlocks[2])
}

return r
}

// ParseLC parses a packed Link Control message.
func ParseLC(data []byte) (*LC, error) {
if data == nil {
return nil, errors.New("dmr/lc: data can't be nil")
}
if len(data) != 9 {
return nil, fmt.Errorf("dmr/lc: expected 9 LC bytes, got %d", len(data))
}

if data[0]&dmr.B10000000 > 0 {
return nil, errors.New("dmr/lc: protect flag is not 0")
}

var (
err error
fclo = data[0] & dmr.B00111111
lc = &LC{
Opcode: fclo,
FeatureSetID: data[1],
}
)
switch fclo {
case GroupVoiceChannelUser:
var pdu *VoiceChannelUserPDU
lc.CallType = dmr.CallTypeGroup
pdu, err = ParseVoiceChannelUserPDU(data[2:9])
lc.VoiceChannelUser = pdu
case UnitToUnitVoiceChannelUser:
var pdu *VoiceChannelUserPDU
lc.CallType = dmr.CallTypePrivate
pdu, err = ParseVoiceChannelUserPDU(data[2:9])
lc.VoiceChannelUser = pdu
case TalkerAliasHeader:
var pdu *TalkerAliasHeaderPDU
pdu, err = ParseTalkerAliasHeaderPDU(data[2:9])
lc.TalkerAliasHeader = pdu
case TalkerAliasBlk1:
var pdu *TalkerAliasBlockPDU
pdu, err = ParseTalkerAliasBlockPDU(data[2:9])
lc.TalkerAliasBlocks[0] = pdu
case TalkerAliasBlk2:
var pdu *TalkerAliasBlockPDU
pdu, err = ParseTalkerAliasBlockPDU(data[2:9])
lc.TalkerAliasBlocks[1] = pdu
case TalkerAliasBlk3:
var pdu *TalkerAliasBlockPDU
pdu, err = ParseTalkerAliasBlockPDU(data[2:9])
lc.TalkerAliasBlocks[2] = pdu
case GpsInfo:
var pdu *GpsInfoPDU
pdu, err = ParseGpsInfoPDU(data[2:9])
lc.GpsInfo = pdu
default:
return nil, fmt.Errorf("dmr/lc: unknown FCLO %06b (%d)", fclo, fclo)
}

if err != nil {
return nil, fmt.Errorf("error parsing link control header pdu: %s", err)
}

return lc, nil
}

// ParseFullLC parses a packed Link Control message and checks/corrects the Reed-Solomon check data.
func ParseFullLC(data []byte) (*LC, error) {
if data == nil {
return nil, errors.New("dmr/full lc: data can't be nil")
}
if len(data) != 12 {
return nil, fmt.Errorf("dmr/full lc: expected 12 bytes, got %d", len(data))
}

syndrome := &fec.RS_12_9_Poly{}
if err := fec.RS_12_9_CalcSyndrome(data, syndrome); err != nil {
return nil, err
}
if !fec.RS_12_9_CheckSyndrome(syndrome) {
if _, err := fec.RS_12_9_Correct(data, syndrome); err != nil {
return nil, err
}
}

return ParseLC(data[:9])
}
87 changes: 87 additions & 0 deletions lc/serviceoptions/serviceoptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package serviceoptions

import (
"fmt"
"strings"

dmr "github.com/pd0mz/go-dmr"
)

// Priority Levels
const (
NoPriority uint8 = iota
Priority1
Priority2
Priority3
)

// PriorityName is a map of priority level to string.
var PriorityName = map[uint8]string{
NoPriority: "no priority",
Priority1: "priority 1",
Priority2: "priority 2",
Priority3: "priority 3",
}

// ServiceOptions Conforms to ETSI TS 102-361-2 7.2.1
type ServiceOptions struct {
// Emergency service
Emergency bool
// Not defined in document
Privacy bool
// Broadcast service (only defined in group calls)
Broadcast bool
// Open Voice Call Mode
OpenVoiceCallMode bool
// Priority 3 (0b11) is the highest priority
Priority uint8
}

// Byte packs the service options to a single byte.
func (so *ServiceOptions) Byte() byte {
var b byte
if so.Emergency {
b |= dmr.B00000001
}
if so.Privacy {
b |= dmr.B00000010
}
if so.Broadcast {
b |= dmr.B00010000
}
if so.OpenVoiceCallMode {
b |= dmr.B00100000
}
b |= (so.Priority << 6)
return b
}

// String representatation of the service options.
func (so *ServiceOptions) String() string {
var part = []string{}
if so.Emergency {
part = append(part, "emergency")
}
if so.Privacy {
part = append(part, "privacy")
}
if so.Broadcast {
part = append(part, "broadcast")
}
if so.OpenVoiceCallMode {
part = append(part, "Open Voice Call Mode")
}
part = append(part, fmt.Sprintf("%s (%d)", PriorityName[so.Priority], so.Priority))
return strings.Join(part, ", ")
}

// ParseServiceOptions parses the service options byte.
func ParseServiceOptions(data byte) ServiceOptions {
return ServiceOptions{
Emergency: (data & dmr.B00000001) > 0,
Privacy: (data & dmr.B00000010) > 0,
Broadcast: (data & dmr.B00010000) > 0,
OpenVoiceCallMode: (data & dmr.B00100000) > 0,
Priority: (data & dmr.B11000000) >> 6,
}
}
Loading

0 comments on commit ecea90f

Please sign in to comment.