Files
ham/protocol/adsb/message.go
maze 32f6c38c13
Some checks failed
Run tests / test (1.25) (push) Failing after 15s
Run tests / test (stable) (push) Failing after 17s
Fixed code smells
2026-02-22 21:14:58 +01:00

528 lines
16 KiB
Go

package adsb
import (
"errors"
"fmt"
"io"
"git.maze.io/go/ham/protocol/adsb/fields"
)
var (
ErrLength = errors.New("adsb: the supplied data must be 7 bytes long")
ErrFormatType = errors.New("adsb: invalid format type code")
ErrSubType = errors.New("adsb: invalid sub type code")
)
type Payload interface {
UnmarshalBytes([]byte) error
}
type Message []byte
// FormatType returns the format type code
//
// Code BDS Description V0 V1 V2
//
// 0 ? No Position
// 1 0,8 Aircraft Id OK OK OK
// 2 0,8 Aircraft Id OK OK OK
// 3 0,8 Aircraft Id OK OK OK
// 4 0,8 Aircraft Id OK OK OK
// 5 0,6 Surface position OK OK OK
// 6 0,6 Surface position OK OK OK
// 7 0,6 Surface position OK OK OK
// 8 0,6 Surface position OK OK OK
// 9 0,5 Airborne position OK OK OK
//
// 10 0,5 Airborne position OK OK OK
// 14 0,5 Airborne position OK OK OK
// 12 0,5 Airborne position OK OK OK
// 13 0,5 Airborne position OK OK OK
// 14 0,5 Airborne position OK OK OK
// 15 0,5 Airborne position OK OK OK
// 16 0,5 Airborne position OK OK OK
// 17 0,5 Airborne position OK OK OK
// 18 0,5 Airborne position OK OK OK
// 19 0,9 Airborne velocity OK OK OK
// 20 0,5 Airborne position OK OK OK
// 21 0,5 Airborne position OK OK OK
// 22 0,5 Airborne position OK OK OK
// 23 Reserved
// 24 Reserved
// 25 Reserved
// 26 Reserved
// 27 Reserved
// 28 6,1 Emergency report OK OK OK
// 29 6,2 Target and status __ OK OK
// 30 Reserved
// 31 6,5 Operational status OK OK OK
func (msg Message) FormatType() byte {
if len(msg) < 7 {
return 0
}
return (msg[0] & 0xF8) >> 3
}
func (msg Message) Payload() (Payload, error) {
var payload Payload
switch formatType := msg.FormatType(); formatType {
case 0:
if msg[2]&0x0F == 0 && msg[3] == 0 && msg[4] == 0 && msg[5] == 0 && msg[6] == 0 {
payload = new(NoPositionInformation)
} else {
payload = new(AirbornePositionType0)
}
case 1, 2, 3, 4:
payload = new(AircraftIdentificationAndCategory)
case 5, 6, 7, 8:
payload = new(SurfacePosition)
case 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22:
payload = new(AirbornePosition)
case 19:
// check sub type
switch subType := msg[0] & 0x07; subType {
case 1:
payload = new(AirborneVelocityAirSpeedNormal)
case 2:
payload = new(AirborneVelocityAirSpeedSupersonic)
case 3:
payload = new(AirborneVelocityGroundSpeedNormal)
case 4:
payload = new(AirborneVelocityGroundSpeedSupersonic)
default:
return nil, fmt.Errorf("adsb: invalid airborne velocity sub type %d", subType)
}
case 28:
switch subType := msg[0] & 0x07; subType {
case 0:
payload = new(AircraftStatusNoInformation)
case 1:
payload = new(AircraftStatusEmergency)
case 2:
payload = new(AircraftStatusACAS)
default:
return nil, fmt.Errorf("adsb: invalid aircraft status sub type %d", subType)
}
case 29:
switch subType := (msg[0] & 0x06) >> 1; subType {
case 0:
payload = new(TargetStateAndStatus0)
case 1:
payload = new(TargetStateAndStatus1)
default:
return nil, fmt.Errorf("adsb: invalid target state and status sub type %d", subType)
}
case 31:
payload = new(AircraftOperationalStatus)
default:
return nil, fmt.Errorf("adsb: the format type code %d is not supported", formatType)
}
if err := payload.UnmarshalBytes(msg); err != nil {
return nil, err
}
return payload, nil
}
type NoPositionInformation struct {
FormatType byte
AltitudeBarometric int32
NavigationIntegrityCategory byte
}
func (msg *NoPositionInformation) UnmarshalBytes(data []byte) error {
if len(data) != 7 {
return ErrLength
}
// Update the first byte to have a standard BDS code 0,5 (Airborne position with baro altitude) - format 15
temp := []byte{0x78, data[1], data[2], 0, 0, 0, 0}
var pos AirbornePosition
if err := pos.UnmarshalBytes(temp); err != nil {
return err
}
msg.FormatType = pos.FormatType
msg.AltitudeBarometric = pos.AltitudeInFeet
msg.NavigationIntegrityCategory = 0
return nil
}
// AirbornePosition is a message at the format BDS 0,5
type AirbornePosition struct {
FormatType byte
SurveillanceStatus fields.SurveillanceStatus
HasDualAntenna bool
AltitudeSource fields.AltitudeSource
AltitudeReportMethod fields.AltitudeReportMethod
AltitudeInFeet int32
TimeSynchronizedToUTC bool
CompactPositionReportingFormat fields.CompactPositionReportingFormat
EncodedLatitude uint32
EncodedLongitude uint32
}
func (msg *AirbornePosition) UnmarshalBytes(data []byte) error {
if len(data) != 7 {
return ErrLength
}
msg.FormatType = Message(data).FormatType()
if msg.FormatType < 9 || msg.FormatType > 22 || msg.FormatType == 19 {
return ErrFormatType
}
var err error
if msg.AltitudeInFeet, msg.AltitudeSource, msg.AltitudeReportMethod, err = fields.ParseAltitude(data); err != nil {
return err
}
return nil
}
type AirbornePositionType0 struct {
FormatTypeCode byte
NavigationIntegrityCategory byte
AirbornePosition
}
func (msg *AirbornePositionType0) UnmarshalBytes(data []byte) error {
// Update the first byte to have a standard BDS code 0,5 (Airborne position with baro altitude) - format 22
temp := []byte{0xB0, data[1], data[2], data[3], data[4], data[5], data[6]}
if err := msg.AirbornePosition.UnmarshalBytes(temp); err != nil {
return err
}
msg.NavigationIntegrityCategory = 0
return nil
}
type AircraftIdentificationAndCategory struct {
FormatType byte
CategorySet byte
Category byte
Identification string
}
func (msg *AircraftIdentificationAndCategory) UnmarshalBytes(data []byte) error {
if len(data) < 7 {
return io.ErrUnexpectedEOF
}
msg.FormatType = Message(data).FormatType()
for msg.FormatType < 1 || msg.FormatType > 4 {
return ErrFormatType
}
msg.Category = data[0] & 0x07
switch msg.FormatType {
case 1:
msg.CategorySet = 'D'
case 2:
msg.CategorySet = 'C'
case 3:
msg.CategorySet = 'B'
default:
msg.CategorySet = 'A'
}
// Get the codes
codes := make([]byte, 8)
codes[0] = (data[1] & 0xFC) >> 2
codes[1] = (data[1]&0x03)<<4 + (data[2]&0xF0)>>4
codes[2] = (data[2]&0x0F)<<2 + (data[3]&0xC0)>>6
codes[3] = data[3] & 0x3F
codes[4] = (data[4] & 0xFC) >> 2
codes[5] = (data[4]&0x03)<<4 + (data[5]&0xF0)>>4
codes[6] = (data[5]&0x0F)<<2 + (data[6]&0xC0)>>6
codes[7] = data[6] & 0x3F
// Convert the codes to actual char
chars := make([]byte, 8)
for i, code := range codes {
chars[i] = identificationCharacterCoding[code]
}
msg.Identification = string(chars)
return nil
}
type SurfacePosition struct {
FormatType byte
MovementStatus fields.MovementStatus
MovementSpeed float64
GroundTrackStatus bool
GroundTrack float64
TimeSynchronizedToUTC bool
CompactPositionReportingFormat fields.CompactPositionReportingFormat
EncodedLatitude fields.EncodedLatitude
EncodedLongitude fields.EncodedLongitude
}
func (msg *SurfacePosition) UnmarshalBytes(data []byte) error {
if len(data) != 7 {
return ErrLength
}
msg.FormatType = (data[0] & 0xF8) >> 3
if msg.FormatType < 5 || msg.FormatType > 8 {
return ErrFormatType
}
msg.MovementSpeed, msg.MovementStatus = fields.ParseMovementStatus(data)
msg.GroundTrack, msg.GroundTrackStatus = fields.ParseGroundTrackStatus(data)
msg.TimeSynchronizedToUTC = fields.ParseTimeSynchronizedToUTC(data)
msg.CompactPositionReportingFormat = fields.ParseCompactPositioningReportFormat(data)
msg.EncodedLatitude = fields.ParseEncodedLatitude(data)
msg.EncodedLongitude = fields.ParseEncodedLongitude(data)
return nil
}
type AirborneVelocity struct {
FormatType byte
SubType byte
IntentChange bool
IFRCapability bool
NavigationUncertaintyCategory fields.NavigationUncertaintyCategory
MagneticHeadingStatus bool
MagneticHeading float64
AirspeedStatus fields.NumericValueStatus
Airspeed uint16
VerticalRateSource fields.VerticalRateSource
VerticalRateStatus fields.NumericValueStatus
VerticalRate int16
}
// AirborneVelocityGroundSpeedNormal is a message at the format BDS 9,0
type AirborneVelocityGroundSpeedNormal struct {
AirborneVelocity
DirectionEastWest fields.DirectionEastWest
VelocityEWStatus fields.NumericValueStatus
VelocityEW uint16
DirectionNorthSouth fields.DirectionNorthSouth
VelocityNSStatus fields.NumericValueStatus
VelocityNS uint16
DifferenceAltitudeGNSSBaroStatus fields.NumericValueStatus
DifferenceAltitudeGNSSBaro int16
}
func (msg *AirborneVelocityGroundSpeedNormal) UnmarshalBytes(data []byte) error {
if err := msg.unmarshalBytes(data, 1); err != nil {
return err
}
msg.Airspeed, msg.AirspeedStatus = fields.ParseAirspeedSupersonic(data)
msg.DirectionEastWest = fields.ParseDirectionEastWest(data)
msg.VelocityEW, msg.VelocityEWStatus = fields.ParseVelocityEastWestNormal(data)
msg.DirectionNorthSouth = fields.ParseDirectionNorthSouth(data)
msg.VelocityNS, msg.VelocityNSStatus = fields.ParseVelocityNorthSouthNormal(data)
msg.DifferenceAltitudeGNSSBaro, msg.DifferenceAltitudeGNSSBaroStatus = fields.ParseHeightDifferenceFromBaro(data)
return nil
}
// AirborneVelocityGroundSpeedSupersonic is a message at the format BDS 9,0
type AirborneVelocityGroundSpeedSupersonic struct {
AirborneVelocity
DirectionEastWest fields.DirectionEastWest
VelocityEWStatus fields.NumericValueStatus
VelocityEW uint16
DirectionNorthSouth fields.DirectionNorthSouth
VelocityNSStatus fields.NumericValueStatus
VelocityNS uint16
DifferenceAltitudeGNSSBaroStatus fields.NumericValueStatus
DifferenceAltitudeGNSSBaro int16
}
func (msg *AirborneVelocityGroundSpeedSupersonic) UnmarshalBytes(data []byte) error {
if err := msg.unmarshalBytes(data, 2); err != nil {
return err
}
msg.Airspeed, msg.AirspeedStatus = fields.ParseAirspeedSupersonic(data)
msg.DirectionEastWest = fields.ParseDirectionEastWest(data)
msg.VelocityEW, msg.VelocityEWStatus = fields.ParseVelocityEastWestSupersonic(data)
msg.DirectionNorthSouth = fields.ParseDirectionNorthSouth(data)
msg.VelocityNS, msg.VelocityNSStatus = fields.ParseVelocityNorthSouthSupersonic(data)
msg.DifferenceAltitudeGNSSBaro, msg.DifferenceAltitudeGNSSBaroStatus = fields.ParseHeightDifferenceFromBaro(data)
return nil
}
type AirborneVelocityAirSpeedNormal struct {
AirborneVelocity
HeightDifferenceFromBaroStatus fields.NumericValueStatus
HeightDifferenceFromBaro int16
}
func (msg *AirborneVelocity) unmarshalBytes(data []byte, subType byte) error {
if len(data) != 7 {
return ErrLength
}
msg.FormatType = (data[0] & 0xF8) >> 3
if msg.FormatType != 19 {
return ErrFormatType
}
msg.SubType = data[0] & 0x07
if msg.SubType != subType {
return ErrSubType
}
msg.IntentChange = fields.ParseIntentChange(data)
msg.IFRCapability = fields.ParseIFRCapability(data)
msg.NavigationUncertaintyCategory = fields.ParseeNavigationUncertaintyCategory(data)
msg.MagneticHeading, msg.MagneticHeadingStatus = fields.ParseMagneticHeading(data)
msg.VerticalRateSource = fields.ParseVerticalRateSource(data)
msg.VerticalRate, msg.VerticalRateStatus = fields.ParseVerticalRate(data)
return nil
}
func (msg *AirborneVelocityAirSpeedNormal) UnmarshalBytes(data []byte) error {
if err := msg.unmarshalBytes(data, 3); err != nil {
return err
}
msg.Airspeed, msg.AirspeedStatus = fields.ParseAirspeedNormal(data)
msg.HeightDifferenceFromBaro, msg.HeightDifferenceFromBaroStatus = fields.ParseHeightDifferenceFromBaro(data)
return nil
}
type AirborneVelocityAirSpeedSupersonic struct {
AirborneVelocity
HeightDifferenceFromBaroStatus fields.NumericValueStatus
HeightDifferenceFromBaro int16
}
func (msg *AirborneVelocityAirSpeedSupersonic) UnmarshalBytes(data []byte) error {
if err := msg.unmarshalBytes(data, 4); err != nil {
return err
}
msg.Airspeed, msg.AirspeedStatus = fields.ParseAirspeedSupersonic(data)
msg.HeightDifferenceFromBaro, msg.HeightDifferenceFromBaroStatus = fields.ParseHeightDifferenceFromBaro(data)
return nil
}
type AircraftStatusNoInformation struct {
FormatType byte
SubType byte
EmergencyPriorityStatus fields.EmergencyPriorityStatus
}
func (msg *AircraftStatusNoInformation) UnmarshalBytes(data []byte) error {
if len(data) != 7 {
return ErrLength
}
msg.FormatType = (data[0] & 0xF8) >> 3
if msg.FormatType != 28 {
return ErrFormatType
}
msg.SubType = data[0] & 0x07
if msg.SubType != 0 {
return ErrSubType
}
msg.EmergencyPriorityStatus = fields.ParseEmergencyPriorityStatus(data)
return nil
}
type AircraftStatusEmergency struct {
FormatType byte
SubType byte
EmergencyPriorityStatus fields.EmergencyPriorityStatus
ModeACode uint16
}
func (msg *AircraftStatusEmergency) UnmarshalBytes(data []byte) error {
if len(data) != 7 {
return ErrLength
}
msg.FormatType = (data[0] & 0xF8) >> 3
if msg.FormatType != 28 {
return ErrFormatType
}
msg.SubType = data[0] & 0x07
if msg.SubType != 1 {
return ErrSubType
}
msg.EmergencyPriorityStatus = fields.ParseEmergencyPriorityStatus(data)
msg.ModeACode = uint16((data[1]&0x1F)<<8) | uint16(data[2])
return nil
}
type AircraftStatusACAS struct {
FormatType byte
SubType byte
ResolutionAdvisory ResolutionAdvisory
}
type TargetStateAndStatus0 struct {
FormatType byte
SubType byte
VerticalDataAvailableSourceIndicator fields.VerticalDataAvailableSourceIndicator
TargetAltitudeType fields.TargetAltitudeType
TargetAltitudeCapability fields.TargetAltitudeCapability
VerticalModeIndicator fields.VerticalModeIndicator
TargetAltitudeStatus fields.NumericValueStatus
TargetAltitude int32
HorizontalDataAvailableSourceIndicator fields.HorizontalDataAvailableSourceIndicator
TargetHeadingTrackAngleStatus fields.NumericValueStatus
TargetHeadingTrackAngle uint16
TargetHeadingTrackIndicator fields.TargetHeadingTrackIndicator
HorizontalModeIndicator fields.HorizontalModeIndicator
NavigationalAccuracyCategoryPosition fields.NavigationalAccuracyCategoryPositionV1
NICBaro fields.NICBaro
SurveillanceIntegrityLevel fields.SurveillanceIntegrityLevel
CapabilityModeCode fields.CapabilityModeCode
EmergencyPriorityStatus fields.EmergencyPriorityStatus
}
func (msg *TargetStateAndStatus0) UnmarshalBytes(data []byte) error {
if len(data) != 7 {
return ErrLength
}
msg.FormatType = (data[0] & 0xF8) >> 3
if msg.FormatType != 29 {
return ErrFormatType
}
msg.SubType = (data[0] & 0x06) >> 1
if msg.SubType != 0 {
return ErrSubType
}
}
var identificationCharacterCoding = []byte{
'#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#', '#', '#', '#', '#',
' ', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '#', '#', '#', '#', '#',
}