forked from pd0mz/go-dmr
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request pd0mz#1 from yeyus/jft_talker_alias
refactor link control and add talker alias support
- Loading branch information
Showing
7 changed files
with
518 additions
and
193 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
Oops, something went wrong.