Fixed code smells
This commit is contained in:
158
protocol/adsb/fields/bds05.go
Normal file
158
protocol/adsb/fields/bds05.go
Normal file
@@ -0,0 +1,158 @@
|
||||
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
|
||||
}
|
||||
208
protocol/adsb/fields/bds09.go
Normal file
208
protocol/adsb/fields/bds09.go
Normal file
@@ -0,0 +1,208 @@
|
||||
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
|
||||
}
|
||||
21
protocol/adsb/fields/bds61.go
Normal file
21
protocol/adsb/fields/bds61.go
Normal file
@@ -0,0 +1,21 @@
|
||||
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)
|
||||
}
|
||||
176
protocol/adsb/fields/bds62.go
Normal file
176
protocol/adsb/fields/bds62.go
Normal file
@@ -0,0 +1,176 @@
|
||||
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)
|
||||
}
|
||||
78
protocol/adsb/fields/util.go
Normal file
78
protocol/adsb/fields/util.go
Normal file
@@ -0,0 +1,78 @@
|
||||
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
|
||||
}
|
||||
52
protocol/adsb/fields/util_test.go
Normal file
52
protocol/adsb/fields/util_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
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
|
||||
}
|
||||
527
protocol/adsb/message.go
Normal file
527
protocol/adsb/message.go
Normal file
@@ -0,0 +1,527 @@
|
||||
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', '#', '#', '#', '#', '#', '#',
|
||||
}
|
||||
137
protocol/adsb/ra.go
Normal file
137
protocol/adsb/ra.go
Normal file
@@ -0,0 +1,137 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package aprs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func base91Decode(s string) (n int, err error) {
|
||||
@@ -18,6 +17,7 @@ func base91Decode(s string) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
func base91Encode(n int) string {
|
||||
var s []string
|
||||
for n > 0 {
|
||||
@@ -27,3 +27,4 @@ func base91Encode(n int) string {
|
||||
}
|
||||
return strings.Join(s, "")
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -310,7 +310,7 @@ func (p *Packet) parse() error {
|
||||
return err
|
||||
}
|
||||
p.Position = &pos
|
||||
p.parseMicEData()
|
||||
_ = p.parseMicEData()
|
||||
|
||||
return nil // there is no additional data to parse
|
||||
default:
|
||||
|
||||
@@ -53,7 +53,7 @@ func (priv *PrivateKey) Sign(_ io.Reader, digest []byte, _ crypto.SignerOpts) (s
|
||||
|
||||
func (priv *PrivateKey) Public() crypto.PublicKey {
|
||||
p := &PublicKey{}
|
||||
newPublicKey(p, priv.pub[:])
|
||||
_, _ = newPublicKey(p, priv.pub[:])
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -118,7 +118,9 @@ func GenerateKey() (*PublicKey, *PrivateKey, error) {
|
||||
}
|
||||
|
||||
func generateKey(priv *PrivateKey) (*PrivateKey, error) {
|
||||
rand.Read(priv.seed[:])
|
||||
if _, err := rand.Read(priv.seed[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
precomputePrivateKey(priv)
|
||||
return priv, nil
|
||||
}
|
||||
|
||||
@@ -357,15 +357,6 @@ func (drv *companionDriver) wait(expect ...byte) ([]byte, error) {
|
||||
return wait.Wait()
|
||||
}
|
||||
|
||||
func bytesContains(b byte, slice []byte) bool {
|
||||
for _, v := range slice {
|
||||
if v == b {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (drv *companionDriver) handlePushFrame(b []byte) {
|
||||
if len(b) < 1 {
|
||||
return // illegal
|
||||
|
||||
@@ -10,10 +10,7 @@ import (
|
||||
"git.maze.io/go/ham/protocol/meshcore/crypto"
|
||||
)
|
||||
|
||||
var (
|
||||
zeroTime time.Time
|
||||
zeroPositionBytes = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
)
|
||||
var zeroPositionBytes = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
type Payload interface {
|
||||
fmt.Stringer
|
||||
|
||||
@@ -47,10 +47,6 @@ func decodeTime(b []byte) time.Time {
|
||||
return time.Unix(int64(binary.LittleEndian.Uint32(b)), 0).UTC()
|
||||
}
|
||||
|
||||
func encodeFrequency(b []byte, f float64) {
|
||||
binary.LittleEndian.PutUint32(b, uint32(f*1e3))
|
||||
}
|
||||
|
||||
func decodeFrequency(b []byte) float64 {
|
||||
return float64(int64(binary.LittleEndian.Uint32(b))) / 1e3
|
||||
}
|
||||
|
||||
173
protocol/meshtastic/node.go
Normal file
173
protocol/meshtastic/node.go
Normal file
@@ -0,0 +1,173 @@
|
||||
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
|
||||
}
|
||||
54
protocol/meshtastic/packet.go
Normal file
54
protocol/meshtastic/packet.go
Normal file
@@ -0,0 +1,54 @@
|
||||
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))
|
||||
}
|
||||
@@ -88,7 +88,7 @@ var parseLocatorMult = []struct {
|
||||
{upper[:18], lower[:18], 20.0},
|
||||
{upper[:18], lower[:18], 10.0},
|
||||
{digit[:10], digit[:10], 20.0 / 10.0},
|
||||
{digit[:10], digit[:10], 10.0 / 10.0},
|
||||
{digit[:10], digit[:10], 1.0},
|
||||
{upper[:24], lower[:24], 20.0 / (10.0 * 24.0)},
|
||||
{upper[:24], lower[:24], 10.0 / (10.0 * 24.0)},
|
||||
{digit[:10], digit[:10], 20.0 / (10.0 * 24.0 * 10.0)},
|
||||
|
||||
Reference in New Issue
Block a user