528 lines
16 KiB
Go
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', '#', '#', '#', '#', '#', '#',
|
|
}
|