Cleanup protocols that are not ready for use
This commit is contained in:
@@ -1,158 +0,0 @@
|
|||||||
package fields
|
|
||||||
|
|
||||||
// SurveillanceStatus is the surveillance status
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / A.2.3.2.6
|
|
||||||
type SurveillanceStatus byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
SSNoCondition SurveillanceStatus = iota // No aircraft category information
|
|
||||||
SSPermanentAlert // Permanent alert (emergency condition)
|
|
||||||
SSTemporaryAlert // Temporary alert (change in Mode A identity code other than emergency condition)
|
|
||||||
SSSPICondition // SPI condition
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
// AltitudeSource is the type of source of the Altitude: Barometric or GNSS.
|
|
||||||
type AltitudeSource byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
AltitudeBarometric AltitudeSource = iota // Altitude is barometric altitude
|
|
||||||
AltitudeGNSS // Altitude is GNSS height (HAE)
|
|
||||||
)
|
|
||||||
|
|
||||||
// AltitudeReportMethod defines how the altitude is reported.
|
|
||||||
type AltitudeReportMethod byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
AltitudeReport100FootIncrements AltitudeReportMethod = iota // 100-foot increments
|
|
||||||
AltitudeReport25FootIncrements // 25-foot increments
|
|
||||||
)
|
|
||||||
|
|
||||||
// CompactPositionReportingFormat is the CPR (Compact Position Reporting) format definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / A.2.3.3.3
|
|
||||||
type CompactPositionReportingFormat byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
CPRFormatEven CompactPositionReportingFormat = iota // Even format coding
|
|
||||||
CPRFormatOdd // Odd format coding
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseCompactPositioningReportFormat(data []byte) CompactPositionReportingFormat {
|
|
||||||
bits := (data[2] & 0x04) >> 2
|
|
||||||
return CompactPositionReportingFormat(bits)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseAltitude(data []byte) (altitude int32, source AltitudeSource, method AltitudeReportMethod, err error) {
|
|
||||||
source = AltitudeBarometric
|
|
||||||
if format := (data[0] & 0xF8) >> 3; 20 <= format && format <= 22 {
|
|
||||||
source = AltitudeGNSS
|
|
||||||
}
|
|
||||||
|
|
||||||
// Altitude code is a 12 bits fields, so read a uint16
|
|
||||||
// bit | 8 9 10 11 12 13 14 15| 16 17 18 19 20 21 22 23 |
|
|
||||||
// message | x x x x x x x x| x x x x _ _ _ _ |
|
|
||||||
// 100 foot |C1 A1 C2 A2 C4 A4 B1 Q| B2 D2 B4 D4 _ _ _ _ |
|
|
||||||
// Get the Q bit
|
|
||||||
if qBit := (data[1] & 0x01) != 0; qBit {
|
|
||||||
// If the Q bit equals 1, the 11-bit field represented by bits 8 to 14 and 16 to 18
|
|
||||||
n := uint16(0)
|
|
||||||
n |= uint16(data[1]&0xFE) << 3
|
|
||||||
n |= uint16(data[2]&0xF0) >> 4
|
|
||||||
return 25*int32(n) - 1000, source, AltitudeReport25FootIncrements, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, altitude is reported in 100 foot increments
|
|
||||||
c1 := (data[1] & 0x80) != 0
|
|
||||||
a1 := (data[1] & 0x40) != 0
|
|
||||||
c2 := (data[1] & 0x20) != 0
|
|
||||||
a2 := (data[1] & 0x10) != 0
|
|
||||||
c4 := (data[1] & 0x08) != 0
|
|
||||||
a4 := (data[1] & 0x04) != 0
|
|
||||||
b1 := (data[1] & 0x02) != 0
|
|
||||||
b2 := (data[2] & 0x80) != 0
|
|
||||||
d2 := (data[2] & 0x40) != 0
|
|
||||||
b4 := (data[2] & 0x20) != 0
|
|
||||||
d4 := (data[2] & 0x10) != 0
|
|
||||||
if altitude, err = GillhamToAltitude(false, d2, d4, a1, a2, a4, b1, b2, b4, c1, c2, c4); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MovementStatus is the status of the Movement information
|
|
||||||
type MovementStatus int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MSNoInformation indicates no information
|
|
||||||
MSNoInformation MovementStatus = 0 // No information
|
|
||||||
MSAircraftStopped MovementStatus = 1 // The aircraft is stopped
|
|
||||||
MSAboveMaximum MovementStatus = 124 // The Movement is above the maximum
|
|
||||||
MSReservedDecelerating MovementStatus = 125 // Reserved
|
|
||||||
MSReservedAccelerating MovementStatus = 126 // Reserved
|
|
||||||
MSReservedBackingUp MovementStatus = 127 // Reserved
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseMovementStatus(data []byte) (float64, MovementStatus) {
|
|
||||||
bits := (data[0]&0x07)<<4 + (data[1]&0xF0)>>4
|
|
||||||
status := MovementStatus(bits)
|
|
||||||
return status.Speed(), status
|
|
||||||
}
|
|
||||||
|
|
||||||
// Speed in knots.
|
|
||||||
func (status MovementStatus) Speed() float64 {
|
|
||||||
if status == 0 || status == 1 || status > 124 {
|
|
||||||
return 0
|
|
||||||
} else if 2 <= status && status <= 8 {
|
|
||||||
return 0.125 + float64(status-2)*0.125
|
|
||||||
} else if 9 <= status && status <= 12 {
|
|
||||||
return 1 + float64(status-9)*0.25
|
|
||||||
} else if 13 <= status && status <= 38 {
|
|
||||||
return 2 + float64(status-13)*0.5
|
|
||||||
} else if 39 <= status && status <= 93 {
|
|
||||||
return 15 + float64(status-39)*1.0
|
|
||||||
} else if 94 <= status && status <= 108 {
|
|
||||||
return 70 + float64(status-94)*2.0
|
|
||||||
} else if 109 <= status && status <= 123 {
|
|
||||||
return 100 + float64(status-109)*5.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Movement max
|
|
||||||
return 175
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodedLatitude is the encoded latitude
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / C.2.6
|
|
||||||
type EncodedLatitude uint32
|
|
||||||
|
|
||||||
func ParseEncodedLatitude(data []byte) EncodedLatitude {
|
|
||||||
return EncodedLatitude((data[2]&0x02)>>1)<<16 |
|
|
||||||
EncodedLatitude((data[2]&0x01)<<7+(data[3]&0xFE)>>1)<<8 |
|
|
||||||
EncodedLatitude((data[3]&0x01)<<7+(data[4]&0xFE)>>1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodedLongitude is the encoded longitude
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / C.2.6
|
|
||||||
type EncodedLongitude uint32
|
|
||||||
|
|
||||||
func ParseEncodedLongitude(data []byte) EncodedLongitude {
|
|
||||||
return EncodedLongitude(data[4]&0x01)<<16 |
|
|
||||||
EncodedLongitude(data[5])<<8 |
|
|
||||||
EncodedLongitude(data[6])
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseGroundTrackStatus(data []byte) (track float64, status bool) {
|
|
||||||
status = (data[1] & 0x08) != 0
|
|
||||||
|
|
||||||
allBits := (uint16(data[1]&0x07)<<8 | uint16(data[2]&0xF0)) >> 4
|
|
||||||
track = float64(allBits) * 360.0 / 128.0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseTimeSynchronizedToUTC(data []byte) bool {
|
|
||||||
return ((data[2] & 0x08) >> 3) != 0
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
package fields
|
|
||||||
|
|
||||||
// NumericValueStatus is the status of the numeric values, such as air speed, velocity, etc.
|
|
||||||
type NumericValueStatus byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
NVSNoInformation NumericValueStatus = iota // No velocity information
|
|
||||||
NVSRegular // Velocity is computed on the linear scale value of field * factor
|
|
||||||
NVSMaximum // Velocity field value indicates velocity greater the maximum of the scale
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseHeightDifference reads the Height difference from a 56 bits data field
|
|
||||||
func ParseHeightDifference(data []byte) (int16, NumericValueStatus) {
|
|
||||||
negative := data[6]&0x80 != 0
|
|
||||||
difference := int16(data[6] & 0x7F)
|
|
||||||
|
|
||||||
if difference == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if difference >= 127 {
|
|
||||||
if negative {
|
|
||||||
return -3150, NVSMaximum
|
|
||||||
} else {
|
|
||||||
return 3150, NVSMaximum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
difference = (difference - 1) * 25
|
|
||||||
if negative {
|
|
||||||
difference = -difference
|
|
||||||
}
|
|
||||||
|
|
||||||
return difference, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerticalRateSource is the Source Bit for Vertical Rate definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / Table A-2-9
|
|
||||||
type VerticalRateSource byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
VerticalRateSourceGNSS VerticalRateSource = iota // GNSS source
|
|
||||||
VerticalRateSourceBarometric // Barometric source
|
|
||||||
)
|
|
||||||
|
|
||||||
// DirectionNorthSouth is the Direction Bit NS Velocity definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / Table A-2-9
|
|
||||||
type DirectionNorthSouth byte
|
|
||||||
|
|
||||||
func ParseDirectionNorthSouth(data []byte) DirectionNorthSouth {
|
|
||||||
return DirectionNorthSouth((data[3] & 0x80) >> 7)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseVelocityNorthSouthNormal(data []byte) (velocity uint16, status NumericValueStatus) {
|
|
||||||
velocity = (uint16(data[3]&0x7F)<<8 | uint16(data[4]&0xE0)) >> 5
|
|
||||||
if velocity == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if velocity >= 1023 {
|
|
||||||
return 1023, NVSMaximum
|
|
||||||
}
|
|
||||||
return velocity - 1, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseVelocityNorthSouthSupersonic(data []byte) (velocity uint16, status NumericValueStatus) {
|
|
||||||
velocity = (uint16(data[3]&0x7F)<<8 | uint16(data[4]&0xE0)) >> 5
|
|
||||||
if velocity == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if velocity >= 1023 {
|
|
||||||
return 4088, NVSMaximum
|
|
||||||
}
|
|
||||||
return (velocity - 1) * 4, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
DNSNorth DirectionNorthSouth = iota // North
|
|
||||||
DNSSouth // South
|
|
||||||
)
|
|
||||||
|
|
||||||
// DirectionEastWest is the Direction Bit EW Velocity definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / Table A-2-9
|
|
||||||
type DirectionEastWest byte
|
|
||||||
|
|
||||||
func ParseDirectionEastWest(data []byte) DirectionEastWest {
|
|
||||||
return DirectionEastWest((data[1] & 0x04) >> 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseVelocityEastWestNormal(data []byte) (velocity uint16, status NumericValueStatus) {
|
|
||||||
velocity = (uint16(data[1]&0x03) | uint16(data[2]))
|
|
||||||
if velocity == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if velocity >= 1023 {
|
|
||||||
return 1023, NVSMaximum
|
|
||||||
}
|
|
||||||
return velocity - 1, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseVelocityEastWestSupersonic(data []byte) (velocity uint16, status NumericValueStatus) {
|
|
||||||
velocity = (uint16(data[1]&0x03) | uint16(data[2]))
|
|
||||||
if velocity == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if velocity >= 1023 {
|
|
||||||
return 4088, NVSMaximum
|
|
||||||
}
|
|
||||||
return (velocity - 1) * 4, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
DEWEast DirectionEastWest = iota // East
|
|
||||||
DEWWest // West
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseIntentChange(data []byte) bool {
|
|
||||||
return (data[1]&0x80)>>7 != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseIFRCapability(data []byte) bool {
|
|
||||||
return (data[1]&0x40)>>6 != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// NavigationUncertaintyCategory is the Navigation Uncertainty Category definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / Table A-2-9
|
|
||||||
type NavigationUncertaintyCategory byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
NUCPUnknown NavigationUncertaintyCategory = iota // Unknown
|
|
||||||
NUCPHorizontalLowerThan10VerticalLowerThan15Point2 // Horizontal < 10m/s and Vertical < 15.2m/s
|
|
||||||
NUCPHorizontalLowerThan3VerticalLowerThan4Point6 // Horizontal < 3m/s and Vertical < 4.6m/s
|
|
||||||
NUCPHorizontalLowerThan1VerticalLowerThan1Point5 // Horizontal < 1m/s and Vertical < 1.5m/s
|
|
||||||
NUCPHorizontalLowerThan0Point3VerticalLowerThan0Point46 // Horizontal < 0.3m/s and Vertical < 0.46m/s
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseeNavigationUncertaintyCategory(data []byte) NavigationUncertaintyCategory {
|
|
||||||
return NavigationUncertaintyCategory((data[1] & 0x38) >> 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseMagneticHeading(data []byte) (heading float64, status bool) {
|
|
||||||
status = (data[1]&0x04)>>2 != 0
|
|
||||||
value := uint16(data[1]&0x03)<<8 | uint16(data[2])
|
|
||||||
heading = float64(value) * 360 / 1024.0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseAirspeedNormal(data []byte) (speed uint16, status NumericValueStatus) {
|
|
||||||
velocity := (uint16(data[3]&0x7F)<<8 | uint16(data[4]&0xE0)) >> 5
|
|
||||||
if velocity == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if velocity >= 1023 {
|
|
||||||
return 1023, NVSMaximum
|
|
||||||
}
|
|
||||||
return velocity - 1, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseAirspeedSupersonic(data []byte) (speed uint16, status NumericValueStatus) {
|
|
||||||
velocity := (uint16(data[3]&0x7F)<<8 | uint16(data[4]&0xE0)) >> 5
|
|
||||||
if velocity == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if velocity >= 1023 {
|
|
||||||
return 4088, NVSMaximum
|
|
||||||
}
|
|
||||||
return (velocity - 1) * 4, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseVerticalRateSource(data []byte) VerticalRateSource {
|
|
||||||
return VerticalRateSource((data[4] & 0x10) >> 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseVerticalRate(data []byte) (rate int16, status NumericValueStatus) {
|
|
||||||
negative := data[4]&0x08 != 0
|
|
||||||
rate = int16(uint16(data[4]&0x07)<<8 | uint16(data[5]&0xFC))
|
|
||||||
if rate == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if rate >= 511 {
|
|
||||||
if negative {
|
|
||||||
return -32640, NVSMaximum
|
|
||||||
}
|
|
||||||
return 32640, NVSMaximum
|
|
||||||
}
|
|
||||||
|
|
||||||
rate = (rate - 1) * 64
|
|
||||||
if negative {
|
|
||||||
rate = -rate
|
|
||||||
}
|
|
||||||
return rate, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseHeightDifferenceFromBaro(data []byte) (difference int16, status NumericValueStatus) {
|
|
||||||
negative := data[6]&0x80 != 0
|
|
||||||
difference = int16(data[6] & 0x7F)
|
|
||||||
|
|
||||||
if difference == 0 {
|
|
||||||
return 0, NVSNoInformation
|
|
||||||
} else if difference >= 127 {
|
|
||||||
if negative {
|
|
||||||
return -3150, NVSMaximum
|
|
||||||
} else {
|
|
||||||
return 3150, NVSMaximum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
difference = (difference - 1) * 25
|
|
||||||
if negative {
|
|
||||||
difference = -difference
|
|
||||||
}
|
|
||||||
|
|
||||||
return difference, NVSRegular
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package fields
|
|
||||||
|
|
||||||
// EmergencyPriorityStatus is the Emergency Priority Status definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / Table B-2-97a
|
|
||||||
type EmergencyPriorityStatus byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
EPSNoEmergency EmergencyPriorityStatus = iota // No emergency
|
|
||||||
EPSGeneralEmergency // General emergency
|
|
||||||
EPSLifeguardMedical // Lifeguard/medical emergency
|
|
||||||
EPSMinimumFuel // Minimum fuel
|
|
||||||
EPSNoCommunication // No communications
|
|
||||||
EPSUnlawfulInterference // Unlawful interference
|
|
||||||
EPSDownedAircraft // Downed aircraft
|
|
||||||
EPSReserved7 // Reserved
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseEmergencyPriorityStatus(data []byte) EmergencyPriorityStatus {
|
|
||||||
return EmergencyPriorityStatus((data[1] & 0xE0) >> 5)
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
package fields
|
|
||||||
|
|
||||||
// VerticalDataAvailableSourceIndicator is the Vertical Data Available / Source Indicator definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.3
|
|
||||||
type VerticalDataAvailableSourceIndicator byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
VDANoValidDataAvailable VerticalDataAvailableSourceIndicator = iota // No data available
|
|
||||||
VDAAutopilot // Autopilot control panel selected value, such as Mode Control Panel (MCP) or Flight Control Unit (FCU)
|
|
||||||
VDAHoldingAltitude // Holding altitude
|
|
||||||
VDAFMS // FMS/RNAV system
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseVerticalDataAvailableSourceIndicator(data []byte) VerticalDataAvailableSourceIndicator {
|
|
||||||
return VerticalDataAvailableSourceIndicator((data[0]&0x01)<<1 + (data[1]&0x80)>>7)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TargetAltitudeType is the Target Altitude Type definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.4
|
|
||||||
type TargetAltitudeType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
TATReferencedToPressureAltitude TargetAltitudeType = iota // Referenced to pressure-altitude (flight level)
|
|
||||||
TATReferencedToBarometricAltitude // Referenced to barometric corrected altitude (mean sea level)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadTargetAltitudeType reads the TargetAltitudeType from a 56 bits data field
|
|
||||||
func ParseTargetAltitudeType(data []byte) TargetAltitudeType {
|
|
||||||
return TargetAltitudeType((data[1] & 0x40) >> 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TargetAltitudeCapability is the Target Altitude Capability definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.5
|
|
||||||
type TargetAltitudeCapability byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
TACAltitudeOnly TargetAltitudeCapability = iota // Holding altitude only
|
|
||||||
TACAltitudeOrAutopilot // Either holding altitude or autopilot control panel selected altitude
|
|
||||||
TACAltitudeOrAutopilotOrFMS // Either holding altitude, autopilot control panel selected altitude, or any FMS/RNAV level-off altitude
|
|
||||||
TACReserved3 // Reserved
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseTargetAltitudeCapability reads the TargetAltitudeCapability from a 56 bits data field
|
|
||||||
func ParseTargetAltitudeCapability(data []byte) TargetAltitudeCapability {
|
|
||||||
return TargetAltitudeCapability((data[1] & 0x18) >> 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerticalModeIndicator is the Vertical Mode Indicator definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.6
|
|
||||||
type VerticalModeIndicator byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
VMIUnknown VerticalModeIndicator = iota // Unknown mode or information unavailable
|
|
||||||
VMIAcquiringMode // Acquiring Mode
|
|
||||||
VMICapturingMode // Capturing or Maintaining Mode
|
|
||||||
VMIReserved3 // Reserved
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseVerticalModeIndicator reads the VerticalModeIndicator from a 56 bits data field
|
|
||||||
func ParseVerticalModeIndicator(data []byte) VerticalModeIndicator {
|
|
||||||
return VerticalModeIndicator((data[1] & 0x06) >> 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HorizontalDataAvailableSourceIndicator is the Horizontal Data Available / Source Indicator definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.8
|
|
||||||
type HorizontalDataAvailableSourceIndicator byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
HDANoValidDataAvailable HorizontalDataAvailableSourceIndicator = iota // No data available
|
|
||||||
HDAAutopilot // Autopilot control panel selected value, such as Mode Control Panel (MCP) or Flight Control Unit (FCU)
|
|
||||||
HDAHoldingAltitude // Maintaining current heading or track angle (e.g. autopilot mode select)
|
|
||||||
HDAFMS // FMS/RNAV system (indicates track angle specified by leg type)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseHorizontalDataAvailableSourceIndicator reads the HorizontalDataAvailableSourceIndicator from a 56 bits data field
|
|
||||||
func ParseHorizontalDataAvailableSourceIndicator(data []byte) HorizontalDataAvailableSourceIndicator {
|
|
||||||
return HorizontalDataAvailableSourceIndicator((data[3] & 0x60) >> 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TargetHeadingTrackIndicator is the Target Heading / Track Angle Indicator definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.10
|
|
||||||
type TargetHeadingTrackIndicator byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
TargetTrackHeadingAngle TargetHeadingTrackIndicator = iota // Target heading angle is being reported
|
|
||||||
TargetTrackAngle // Track angle is being reported
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseTargetHeadingTrackIndicator reads the TargetHeadingTrackIndicator from a 56 bits data field
|
|
||||||
func ParseTargetHeadingTrackIndicator(data []byte) TargetHeadingTrackIndicator {
|
|
||||||
return TargetHeadingTrackIndicator((data[4] & 0x08) >> 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadTargetHeadingTrackAngle reads the TargetAltitude from a 56 bits data field
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.9
|
|
||||||
func ReadTargetHeadingTrackAngle(data []byte) (uint16, NumericValueStatus) {
|
|
||||||
heading := (uint16(data[3]&0x1F)<<8 | uint16(data[2]&0xF0)) >> 4
|
|
||||||
if heading > 359 {
|
|
||||||
return 0, NVSMaximum
|
|
||||||
}
|
|
||||||
return heading, NVSRegular
|
|
||||||
}
|
|
||||||
|
|
||||||
// HorizontalModeIndicator is the Horizontal Mode Indicator definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.11
|
|
||||||
type HorizontalModeIndicator byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
HMIUnknown HorizontalModeIndicator = iota // Unknown mode or information unavailable
|
|
||||||
HMIAcquiringMode // Acquiring Mode
|
|
||||||
HMICapturingMode // Capturing or Maintaining Mode
|
|
||||||
HMIReserved3 // Reserved
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseHorizontalModeIndicator reads the HorizontalModeIndicator from a 56 bits data field
|
|
||||||
func ParseHorizontalModeIndicator(data []byte) HorizontalModeIndicator {
|
|
||||||
return HorizontalModeIndicator((data[4] & 0x06) >> 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NavigationalAccuracyCategoryPositionV1 is the Navigational Accuracy Category Position definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.12
|
|
||||||
type NavigationalAccuracyCategoryPositionV1 byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
NACPV1EPUGreaterThan18Point52Km NavigationalAccuracyCategoryPositionV1 = iota // EPU >= 18.52 km (10 NM) - Unknown accuracy
|
|
||||||
NACPV1EPULowerThan18Point52Km // EPU < 18.52 km (10 NM) - RNP-10 accuracy
|
|
||||||
NACPV1EPULowerThan7Point408Km // EPU < 7.408 km (4 NM) - RNP-4 accuracy
|
|
||||||
NACPV1EPULowerThan3Point704Km // EPU < 3.704 km (2 NM) - RNP-2 accuracy
|
|
||||||
NACPV1EPUGreaterThan1852M // EPU < 1 852 m (1 NM) - RNP-1 accuracy
|
|
||||||
NACPV1EPULowerThan926M // EPU < 926 m (0.5 NM) - RNP-0.5 accuracy
|
|
||||||
NACPV1EPUGreaterThan555Point6M // EPU < 555.6 m ( 0.3 NM) - RNP-0.3 accuracy
|
|
||||||
NACPV1EPULowerThan185Point2M // EPU < 185.2 m (0.1 NM) - RNP-0.1 accuracy
|
|
||||||
NACPV1EPUGreaterThan92Point6M // EPU < 92.6 m (0.05 NM) - e.g. GPS (with SA)
|
|
||||||
NACPV1EPULowerThan30MAndVEPULowerThan45M // EPU < 30 m and VEPU < 45 m - e.g. GPS (SA off)
|
|
||||||
NACPV1EPULowerThan10MAndVEPULowerThan15M // EPU < 10 m and VEPU < 15 m - e.g. WAAS
|
|
||||||
NACPV1EPULowerThan4MAndVEPULowerThan3M // EPU < 3 m and VEPU < 4 m - e.g. LAAS
|
|
||||||
NACPV1Reserved12 // Reserved
|
|
||||||
NACPV1Reserved13 // Reserved
|
|
||||||
NACPV1Reserved14 // Reserved
|
|
||||||
NACPV1Reserved15 // Reserved
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseNavigationalAccuracyCategoryPositionV1 reads the NavigationalAccuracyCategoryPositionV1 from a 56 bits data field
|
|
||||||
func ParseNavigationalAccuracyCategoryPositionV1(data []byte) NavigationalAccuracyCategoryPositionV1 {
|
|
||||||
return NavigationalAccuracyCategoryPositionV1((data[4]&0x01)<<3 + (data[5]&0xE0)>>5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NICBaro is the NIC Baro definition
|
|
||||||
//
|
|
||||||
// Specified in Doc 9871 / B.2.3.9.13
|
|
||||||
type NICBaro byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NICBGilhamNotCrossChecked indicates that the barometric altitude that is being reported in the Airborne
|
|
||||||
// Position Message is based on a Gilham coded input that has not been cross-checked against another source of
|
|
||||||
// pressure-altitude
|
|
||||||
NICBGilhamNotCrossChecked NICBaro = iota
|
|
||||||
// NICBGilhamCrossCheckedOrNonGilham indicates that the barometric altitude that is being reported in the Airborne
|
|
||||||
// Position Message is either based on a Gilham code input that has been cross-checked against another source of
|
|
||||||
// pressure-altitude and verified as being consistent, or is based on a non-Gilham coded source
|
|
||||||
NICBGilhamCrossCheckedOrNonGilham
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseNICBaro reads the NICBaro from a 56 bits data field
|
|
||||||
func ParseNICBaro(data []byte) NICBaro {
|
|
||||||
return NICBaro((data[5] & 0x10) >> 4)
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package fields
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
// GillhamToAltitude convert an altitude given in Gillham bits to an altitude in feet.
|
|
||||||
func GillhamToAltitude(d1, d2, d4, a1, a2, a4, b1, b2, b4, c1, c2, c4 bool) (int32, error) {
|
|
||||||
fiveHundredBits := uint16(0)
|
|
||||||
if d1 {
|
|
||||||
fiveHundredBits |= 0x0100
|
|
||||||
}
|
|
||||||
if d2 {
|
|
||||||
fiveHundredBits |= 0x0080
|
|
||||||
}
|
|
||||||
if d4 {
|
|
||||||
fiveHundredBits |= 0x0040
|
|
||||||
}
|
|
||||||
if a1 {
|
|
||||||
fiveHundredBits |= 0x0020
|
|
||||||
}
|
|
||||||
if a2 {
|
|
||||||
fiveHundredBits |= 0x0010
|
|
||||||
}
|
|
||||||
if a4 {
|
|
||||||
fiveHundredBits |= 0x0008
|
|
||||||
}
|
|
||||||
if b1 {
|
|
||||||
fiveHundredBits |= 0x0004
|
|
||||||
}
|
|
||||||
if b2 {
|
|
||||||
fiveHundredBits |= 0x0002
|
|
||||||
}
|
|
||||||
if b4 {
|
|
||||||
fiveHundredBits |= 0x0001
|
|
||||||
}
|
|
||||||
|
|
||||||
oneHundredBits := uint16(0)
|
|
||||||
if c1 {
|
|
||||||
oneHundredBits |= 0x0004
|
|
||||||
}
|
|
||||||
if c2 {
|
|
||||||
oneHundredBits |= 0x0002
|
|
||||||
}
|
|
||||||
if c4 {
|
|
||||||
oneHundredBits |= 0x0001
|
|
||||||
}
|
|
||||||
|
|
||||||
oneHundred := int32(grayToBinary(oneHundredBits))
|
|
||||||
fiveHundred := int32(grayToBinary(fiveHundredBits))
|
|
||||||
|
|
||||||
// Check for invalid codes.
|
|
||||||
if oneHundred == 5 || oneHundred == 6 || oneHundred == 0 {
|
|
||||||
return 0, errors.New("the bits C1 to to C4 are incorrect")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove 7s from OneHundreds.
|
|
||||||
if oneHundred == 7 {
|
|
||||||
oneHundred = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// Correct order of OneHundreds.
|
|
||||||
if fiveHundred%2 != 0 {
|
|
||||||
oneHundred = 6 - oneHundred
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to feet and apply altitude datum offset.
|
|
||||||
return (int32(fiveHundred)*500 + int32(oneHundred)*100) - 1300, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func grayToBinary(num uint16) uint16 {
|
|
||||||
temp := uint16(0)
|
|
||||||
|
|
||||||
temp = num ^ (num >> 8)
|
|
||||||
temp ^= temp >> 4
|
|
||||||
temp ^= temp >> 2
|
|
||||||
temp ^= temp >> 1
|
|
||||||
|
|
||||||
return temp
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package fields
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestGillhamToAltitude(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
Test string
|
|
||||||
Want int32
|
|
||||||
}{
|
|
||||||
{"00000000010", -1000},
|
|
||||||
{"00000001010", -500},
|
|
||||||
{"00000011010", 0},
|
|
||||||
{"00000011110", 100},
|
|
||||||
{"00000010011", 600},
|
|
||||||
{"00000110010", 1000},
|
|
||||||
{"00001001001", 5800},
|
|
||||||
{"00011100100", 10300},
|
|
||||||
{"01100011010", 32000},
|
|
||||||
{"01110000100", 46300},
|
|
||||||
{"10000000011", 126600},
|
|
||||||
{"10000000001", 126700},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.Test, func(t *testing.T) {
|
|
||||||
alt, err := GillhamToAltitude(testString2Bits(test.Test))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
if alt != test.Want {
|
|
||||||
t.Errorf("expected altitude %d, got %d", test.Want, alt)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testString2Bits(s string) (bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool) {
|
|
||||||
d2 := s[0] == '1'
|
|
||||||
d4 := s[1] == '1'
|
|
||||||
a1 := s[2] == '1'
|
|
||||||
a2 := s[3] == '1'
|
|
||||||
a4 := s[4] == '1'
|
|
||||||
b1 := s[5] == '1'
|
|
||||||
b2 := s[6] == '1'
|
|
||||||
b4 := s[7] == '1'
|
|
||||||
c1 := s[8] == '1'
|
|
||||||
c2 := s[9] == '1'
|
|
||||||
c4 := s[10] == '1'
|
|
||||||
|
|
||||||
return false, d2, d4, a1, a2, a4, b1, b2, b4, c1, c2, c4
|
|
||||||
}
|
|
||||||
@@ -1,527 +0,0 @@
|
|||||||
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', '#', '#', '#', '#', '#', '#',
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
package adsb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResolutionAdvisory is an ACAS message providing information about ResolutionAdvisory
|
|
||||||
//
|
|
||||||
// Defined at 3.1.2.8.3.1 and 4.3.8.4.2.4
|
|
||||||
type ResolutionAdvisory struct {
|
|
||||||
ActiveRA ActiveResolutionAdvisory
|
|
||||||
RAComplement RAComplement
|
|
||||||
RATerminatedIndicator RATerminatedIndicator
|
|
||||||
MultipleThreatEncounter MultipleThreatEncounter
|
|
||||||
ThreatTypeIndicator ThreatTypeIndicator
|
|
||||||
ThreatIdentityAddress *ThreatIdentityAddress
|
|
||||||
ThreatIdentityAltitude *ThreatIdentityAltitude
|
|
||||||
ThreatIdentityRange *ThreatIdentityRange
|
|
||||||
ThreatIdentityBearing *ThreatIdentityBearing
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThreatTypeIndicator indicates the type of information contained in the TID part of the message
|
|
||||||
type ThreatTypeIndicator int
|
|
||||||
|
|
||||||
// ThreatIdentityAddress is a 3 bytes ICAO Address. The Most Significant Byte of the address
|
|
||||||
// is always 0.
|
|
||||||
type ThreatIdentityAddress uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
ThreatTypeNoIdentity ThreatTypeIndicator = iota // No identity data in TID
|
|
||||||
ThreatTypeModeS // Contains a Mode S transponder address
|
|
||||||
ThreatTypeAltitudeRangeBearing // Contains altitude, range and bearing data
|
|
||||||
ThreatTypeReserved3 // Reserved
|
|
||||||
)
|
|
||||||
|
|
||||||
// ThreatIdentityAltitude is the altitude of the threat. It is given in 100 feet increment
|
|
||||||
type ThreatIdentityAltitude struct {
|
|
||||||
AltitudeValid bool
|
|
||||||
AltitudeInFeet int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseThreatTypeIndicator(data []byte) ThreatTypeIndicator {
|
|
||||||
return ThreatTypeIndicator((data[2] & 0x0C) >> 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseThreatIdentityAltitude reads the altitude code from a message
|
|
||||||
func parseThreatIdentityAltitude(data []byte) (ThreatIdentityAltitude, error) {
|
|
||||||
|
|
||||||
// Altitude code is a 13 bits fields, so read a uint16
|
|
||||||
// byte data[2] | data[3] | data[4]
|
|
||||||
// bit 19 20 21 22 23|24 25 26 27 28 29 30 31|32 33 34 35 36
|
|
||||||
// value _ _ _ C1 A1|C2 A2 C4 A4 0 B1 D1 B2|D2 B4 D4 _ _
|
|
||||||
|
|
||||||
// Start by D2 B4 D4
|
|
||||||
altitudeCode := uint16(data[4]&0xE0) >> 5
|
|
||||||
// Then pack B1 D1 B2
|
|
||||||
altitudeCode += uint16(data[3]&0x07) << 3
|
|
||||||
// Then C2 A2 C4 A4
|
|
||||||
altitudeCode += uint16(data[3]&0xF0) << 2
|
|
||||||
// Then C1 A1
|
|
||||||
altitudeCode += uint16(data[2]&0x03) << 2
|
|
||||||
|
|
||||||
// Detect invalid altitude
|
|
||||||
if altitudeCode == 0 {
|
|
||||||
return ThreatIdentityAltitude{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c1 := (altitudeCode & 0x0800) != 0
|
|
||||||
a1 := (altitudeCode & 0x0400) != 0
|
|
||||||
c2 := (altitudeCode & 0x0200) != 0
|
|
||||||
a2 := (altitudeCode & 0x0100) != 0
|
|
||||||
c4 := (altitudeCode & 0x0080) != 0
|
|
||||||
a4 := (altitudeCode & 0x0040) != 0
|
|
||||||
b1 := (altitudeCode & 0x0020) != 0
|
|
||||||
d1 := (altitudeCode & 0x0010) != 0
|
|
||||||
b2 := (altitudeCode & 0x0008) != 0
|
|
||||||
d2 := (altitudeCode & 0x0004) != 0
|
|
||||||
b4 := (altitudeCode & 0x0002) != 0
|
|
||||||
d4 := (altitudeCode & 0x0001) != 0
|
|
||||||
|
|
||||||
altitudeFeet, err := gillhamToAltitude(d1, d2, d4, a1, a2, a4, b1, b2, b4, c1, c2, c4)
|
|
||||||
if err != nil {
|
|
||||||
return ThreatIdentityAltitude{}, errors.New("adsb: the altitude field is malformed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ThreatIdentityAltitude{true, altitudeFeet}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThreatIdentityRange is TIDR (threat identity data range subfield). This 7-bit subfield (76-82) shall contain the
|
|
||||||
// most recent threat range estimated by ACAS.
|
|
||||||
type ThreatIdentityRange byte
|
|
||||||
|
|
||||||
func parseThreatIdentityRange(data []byte) ThreatIdentityRange {
|
|
||||||
return ThreatIdentityRange((data[4]&0x1F)<<2 + (data[5]&0xA0)>>6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThreatIdentityBearing is TIDR (threat identity data bearing subfield). This 6-bit subfield (83-88) shall contain the
|
|
||||||
// most recent estimated bearing of the threat aircraft, relative to the ACAS aircraft heading.
|
|
||||||
type ThreatIdentityBearing byte
|
|
||||||
|
|
||||||
func parseThreatIdentityBearing(data []byte) ThreatIdentityBearing {
|
|
||||||
return ThreatIdentityBearing(data[5] & 0x3F)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseResolutionAdvisory(data []byte) (*ResolutionAdvisory, error) {
|
|
||||||
if len(data) != 6 {
|
|
||||||
return nil, errors.New("adsb: data for ACAS ResolutionAdvisory must be 6 bytes long")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format of the message is as follows:
|
|
||||||
// 0 1 2 3 4 5
|
|
||||||
// | RAC | R R M T TID | TID | TID | TID |
|
|
||||||
// ARA | ARA RAC | A A T T d d | d d d d d d d d | d d d d d d d d | d d d d d d _ _ |
|
|
||||||
// | RAC | C T E I TIDA| TIDA | TIDA TIDR |TIDR TIDB |
|
|
||||||
// a a a a a a a a | a a a a a a c c | c c t m i i a a | a a a a a a a a | a a a r r r r r | r r b b b b b b |
|
|
||||||
|
|
||||||
ra := new(ResolutionAdvisory)
|
|
||||||
switch ra.ThreatTypeIndicator = parseThreatTypeIndicator(data); ra.ThreatTypeIndicator {
|
|
||||||
case ThreatTypeModeS:
|
|
||||||
addr := ThreatIdentityAddress((binary.BigEndian.Uint32(data[2:]) >> 2) & 0xFFFFFF)
|
|
||||||
ra.ThreatIdentityAddress = &addr
|
|
||||||
|
|
||||||
case ThreatTypeAltitudeRangeBearing:
|
|
||||||
altitude, err := parseThreatIdentityAltitude(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ra.ThreatIdentityAltitude = &altitude
|
|
||||||
|
|
||||||
threatRange := parseThreatIdentityRange(data)
|
|
||||||
ra.ThreatIdentityRange = &threatRange
|
|
||||||
|
|
||||||
bearing := parseThreatIdentityBearing(data)
|
|
||||||
ra.ThreatIdentityBearing = &bearing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
package meshtastic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NodeID uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
// BroadcastNodeID is the special NodeID used when broadcasting a packet to a channel.
|
|
||||||
BroadcastNodeID NodeID = math.MaxUint32
|
|
||||||
|
|
||||||
// BroadcastNodeIDNoLora is a special broadcast address that excludes LoRa transmission.
|
|
||||||
// Used for MQTT-only broadcasts. This is ^all with the NO_LORA flag (0x40) cleared.
|
|
||||||
BroadcastNodeIDNoLora NodeID = math.MaxUint32 ^ 0x40
|
|
||||||
|
|
||||||
// ReservedNodeIDThreshold is the threshold at which NodeIDs are considered reserved. Random NodeIDs should not
|
|
||||||
// be generated below this threshold.
|
|
||||||
// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L461
|
|
||||||
reservedNodeIDThreshold NodeID = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseNodeID parses a NodeID from various string formats:
|
|
||||||
// - "!abcd1234" (Meshtastic format with ! prefix)
|
|
||||||
// - "0xabcd1234" (hex with 0x prefix)
|
|
||||||
// - "abcd1234" (plain hex)
|
|
||||||
// - "12345678" (decimal)
|
|
||||||
func ParseNodeID(s string) (NodeID, error) {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if s == "" {
|
|
||||||
return 0, fmt.Errorf("empty node ID string")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle !prefix format
|
|
||||||
if strings.HasPrefix(s, "!") {
|
|
||||||
s = s[1:]
|
|
||||||
n, err := strconv.ParseUint(s, 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("invalid node ID %q: %w", s, err)
|
|
||||||
}
|
|
||||||
return NodeID(n), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle 0x prefix
|
|
||||||
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
|
|
||||||
n, err := strconv.ParseUint(s[2:], 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("invalid node ID %q: %w", s, err)
|
|
||||||
}
|
|
||||||
return NodeID(n), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try hex first if it looks like hex (contains a-f)
|
|
||||||
sLower := strings.ToLower(s)
|
|
||||||
if strings.ContainsAny(sLower, "abcdef") {
|
|
||||||
n, err := strconv.ParseUint(s, 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("invalid node ID %q: %w", s, err)
|
|
||||||
}
|
|
||||||
return NodeID(n), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try decimal
|
|
||||||
n, err := strconv.ParseUint(s, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
// Fall back to hex for 8-char strings
|
|
||||||
if len(s) == 8 {
|
|
||||||
n, err = strconv.ParseUint(s, 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("invalid node ID %q: %w", s, err)
|
|
||||||
}
|
|
||||||
return NodeID(n), nil
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("invalid node ID %q: %w", s, err)
|
|
||||||
}
|
|
||||||
return NodeID(n), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint32 returns the underlying uint32 value of the NodeID.
|
|
||||||
func (n NodeID) Uint32() uint32 {
|
|
||||||
return uint32(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String converts the NodeID to a hex formatted string.
|
|
||||||
// This is typically how NodeIDs are displayed in Meshtastic UIs.
|
|
||||||
func (n NodeID) String() string {
|
|
||||||
return fmt.Sprintf("!%08x", uint32(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes converts the NodeID to a byte slice
|
|
||||||
func (n NodeID) Bytes() []byte {
|
|
||||||
bytes := make([]byte, 4) // uint32 is 4 bytes
|
|
||||||
binary.BigEndian.PutUint32(bytes, n.Uint32())
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultLongName returns the default long node name based on the NodeID.
|
|
||||||
// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L382
|
|
||||||
func (n NodeID) DefaultLongName() string {
|
|
||||||
bytes := make([]byte, 4) // uint32 is 4 bytes
|
|
||||||
binary.BigEndian.PutUint32(bytes, n.Uint32())
|
|
||||||
return fmt.Sprintf("Meshtastic %04x", bytes[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultShortName returns the default short node name based on the NodeID.
|
|
||||||
// Last two bytes of the NodeID represented in hex.
|
|
||||||
// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L382
|
|
||||||
func (n NodeID) DefaultShortName() string {
|
|
||||||
bytes := make([]byte, 4) // uint32 is 4 bytes
|
|
||||||
binary.BigEndian.PutUint32(bytes, n.Uint32())
|
|
||||||
return fmt.Sprintf("%04x", bytes[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler for use with config parsers like Viper.
|
|
||||||
func (n *NodeID) UnmarshalText(text []byte) error {
|
|
||||||
parsed, err := ParseNodeID(string(text))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*n = parsed
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText implements encoding.TextMarshaler.
|
|
||||||
func (n NodeID) MarshalText() ([]byte, error) {
|
|
||||||
return []byte(n.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReservedID returns true if this is a reserved or broadcast NodeID.
|
|
||||||
func (n NodeID) IsReservedID() bool {
|
|
||||||
return n < reservedNodeIDThreshold || n >= BroadcastNodeIDNoLora
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsBroadcast returns true if this is any form of broadcast address.
|
|
||||||
func (n NodeID) IsBroadcast() bool {
|
|
||||||
return n == BroadcastNodeID || n == BroadcastNodeIDNoLora
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToMacAddress returns a MAC address string derived from the NodeID.
|
|
||||||
// This creates a locally administered unicast MAC address.
|
|
||||||
func (n NodeID) ToMacAddress() string {
|
|
||||||
bytes := n.Bytes()
|
|
||||||
// Use 0x02 as the first octet (locally administered, unicast)
|
|
||||||
// Then 0x00 as padding, followed by the 4 bytes of the NodeID
|
|
||||||
return fmt.Sprintf("02:00:%02x:%02x:%02x:%02x", bytes[0], bytes[1], bytes[2], bytes[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
// RandomNodeID returns a randomised NodeID.
|
|
||||||
// It's recommended to call this the first time a node is started and persist the result.
|
|
||||||
//
|
|
||||||
// Hardware meshtastic nodes first try a NodeID of the last four bytes of the BLE MAC address. If that ID is already in
|
|
||||||
// use or invalid, a random NodeID is generated.
|
|
||||||
// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L466
|
|
||||||
func RandomNodeID() (NodeID, error) {
|
|
||||||
// Generates a random uint32 between reservedNodeIDThreshold and math.MaxUint32
|
|
||||||
randomInt, err := rand.Int(
|
|
||||||
rand.Reader,
|
|
||||||
big.NewInt(
|
|
||||||
int64(math.MaxUint32-reservedNodeIDThreshold.Uint32()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return NodeID(0), fmt.Errorf("reading entropy: %w", err)
|
|
||||||
}
|
|
||||||
r := uint32(randomInt.Uint64()) + reservedNodeIDThreshold.Uint32()
|
|
||||||
return NodeID(r), nil
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package meshtastic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
minPacketSize = 4 + 4 + 4 + 1 + 1 + 1 + 1
|
|
||||||
maxPayloadSize = 237
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidPacket signals the source buffer does not contain a valid packet.
|
|
||||||
ErrInvalidPacket = errors.New("meshtastic: invalid packet")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Packet struct {
|
|
||||||
Destination NodeID
|
|
||||||
Source NodeID
|
|
||||||
ID uint32
|
|
||||||
Flags uint8
|
|
||||||
ChannelHash uint8
|
|
||||||
NextHop uint8
|
|
||||||
RelayNode uint8
|
|
||||||
PayloadLength int
|
|
||||||
Payload [maxPayloadSize]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (packet *Packet) Decode(data []byte) error {
|
|
||||||
if len(data) < minPacketSize {
|
|
||||||
return ErrInvalidPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.Destination = parseNodeID(data[0:])
|
|
||||||
packet.Source = parseNodeID(data[4:])
|
|
||||||
packet.ID = binary.LittleEndian.Uint32(data[8:])
|
|
||||||
packet.Flags = data[12]
|
|
||||||
packet.ChannelHash = data[13]
|
|
||||||
packet.NextHop = data[14]
|
|
||||||
packet.RelayNode = data[15]
|
|
||||||
packet.PayloadLength = len(data[16:])
|
|
||||||
copy(packet.Payload[:], data[16:])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (packet *Packet) HopLimit() int {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseNodeID(data []byte) NodeID {
|
|
||||||
return NodeID(binary.LittleEndian.Uint32(data))
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user