13 Commits

Author SHA1 Message Date
27e2da1943 Added Radio.ID and refactored the Stats interface
Some checks failed
Run tests / test (1.25) (push) Failing after 1m0s
Run tests / test (stable) (push) Failing after 1m0s
2026-03-17 08:33:06 +01:00
8ec85821e4 New Radio.ID field 2026-03-17 08:32:51 +01:00
e2b69d92fd meshtastic: support
Some checks failed
Run tests / test (1.25) (push) Failing after 1m1s
Run tests / test (stable) (push) Failing after 1m0s
2026-03-06 09:24:56 +01:00
e6bda98b92 meshcore: support for multibyte path hashes
Some checks failed
Run tests / test (1.25) (push) Failing after 1m33s
Run tests / test (stable) (push) Failing after 1m41s
2026-03-06 09:16:58 +01:00
8f8a97300f protocol/aprs: extract comment to frame
Some checks failed
Run tests / test (1.25) (push) Failing after 1m36s
Run tests / test (stable) (push) Failing after 1m36s
2026-03-02 22:37:39 +01:00
d4a693365d protocol/aprs: extract position and symbol to frame
Some checks failed
Run tests / test (1.25) (push) Failing after 1m36s
Run tests / test (stable) (push) Failing after 1m36s
2026-03-02 22:33:15 +01:00
63040a44b3 protocol/aprs: refactored
Some checks failed
Run tests / test (1.25) (push) Failing after 1m37s
Run tests / test (stable) (push) Failing after 1m37s
2026-03-02 22:28:17 +01:00
452f521866 Added docs
Some checks failed
Run tests / test (1.25) (push) Failing after 1m55s
Run tests / test (stable) (push) Failing after 1m54s
2026-03-02 15:11:05 +01:00
396c8bb02e Changes
Some checks failed
Run tests / test (1.25) (push) Failing after 1m39s
Run tests / test (stable) (push) Failing after 1m38s
2026-02-27 16:26:48 +01:00
58564affa0 Use int 2026-02-27 16:26:37 +01:00
b23ad9a1fd Use int 2026-02-27 16:26:25 +01:00
a8f49f629e Added MeshCore repeater protocol
Some checks failed
Run tests / test (1.25) (push) Failing after 1m37s
Run tests / test (stable) (push) Failing after 1m37s
2026-02-24 18:20:30 +01:00
65f3fe39a9 Typofix
All checks were successful
Run tests / test (1.25) (push) Successful in 2m4s
Run tests / test (stable) (push) Successful in 2m5s
2026-02-23 21:15:07 +01:00
79 changed files with 32609 additions and 2002 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.pdf filter=lfs diff=lfs merge=lfs -text

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "protocol/meshtastic/internal/pbgen/upstream"]
path = protocol/meshtastic/internal/pbgen/upstream
url = https://github.com/meshtastic/protobufs.git

BIN
docs/APRS12c.pdf LFS Normal file

Binary file not shown.

3
go.mod
View File

@@ -1,12 +1,13 @@
module git.maze.io/go/ham
go 1.25.6
go 1.26
require (
filippo.io/edwards25519 v1.1.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/sirupsen/logrus v1.8.1
golang.org/x/crypto v0.48.0
google.golang.org/protobuf v1.36.11
)
require (

4
go.sum
View File

@@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
@@ -18,6 +20,8 @@ golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVo
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=

View 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
}

View 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
}

View 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)
}

View 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)
}

View 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
}

View 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
View 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
View 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
}
}

View File

@@ -0,0 +1,335 @@
package aprs
import (
"fmt"
"io"
"math"
"strings"
)
type MicE struct {
HasMessaging bool `json:"has_messaging"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Altitude float64 `json:"altitude,omitempty"` // Altitude (in meters)
Comment string `json:"comment"`
Symbol string `json:"symbol"`
Velocity *Velocity `json:"velocity,omitempty"` // Velocity encoded in the payload.
Telemetry *Telemetry `json:"telemetry,omitempty"` // Telemetry data
}
func (m MicE) String() string {
return m.Comment
}
type micEDecoder struct{}
func (d micEDecoder) CanDecode(frame Frame) bool {
switch frame.Raw.Type() {
case '`', '\'':
return len(frame.Raw) >= 9 && len(frame.Destination.Call) >= 6
default:
return false
}
}
func (d micEDecoder) Decode(frame Frame) (data Data, err error) {
lat, _, longOffset, longDir, err := decodeMicECallsign([]byte(frame.Destination.Call))
if err != nil {
return nil, err
}
info := []byte(frame.Raw[1:9])
long := decodeMicELongitude(info[:3], longOffset, longDir)
pos := &MicE{
Latitude: float64(lat),
Longitude: float64(long),
}
pos.Symbol = string([]byte{info[7], info[6]})
pos.Velocity = parseMicECourseAndSpeed(info[3:6])
var comment string
if comment, pos.HasMessaging = parseMicERadioModel(string(frame.Raw[9:])); comment != "" {
original := comment
if pos.Altitude, comment, err = parseMicEAltitude(comment); err != nil {
comment = original
}
if pos.Telemetry, comment, err = parseBase91Telemetry(comment); err != nil {
return nil, err
}
pos.Comment = comment
}
return pos, nil
}
type micEMessageBit uint8
const (
micEMessageBitZero micEMessageBit = iota
micEMessageBitCustom
micEMessageBitStandard
micEMessageBitInvalid
)
func decodeMicEMessageBit(v uint8) micEMessageBit {
switch {
case v >= '0' && v <= '9' || v == 'L':
return micEMessageBitZero
case v >= 'A' && v <= 'K':
return micEMessageBitCustom
case v >= 'P' && v <= 'Z':
return micEMessageBitStandard
default:
return micEMessageBitInvalid
}
}
type micEPositionCommentType uint8
const (
M0 micEPositionCommentType = iota
M1
M2
M3
M4
M5
M6
C0
C1
C2
C3
C4
C5
C6
Emergency
Invalid
)
var micEPositionCommentTypeMap = map[micEMessageBit]micEPositionCommentType{
(0 << 4) | (0 << 2) | 0: Emergency,
(1 << 4) | (1 << 2) | 1: C0,
(1 << 4) | (1 << 2) | 0: C1,
(1 << 4) | (0 << 2) | 1: C2,
(1 << 4) | (0 << 2) | 0: C3,
(0 << 4) | (1 << 2) | 1: C4,
(0 << 4) | (1 << 2) | 0: C5,
(0 << 4) | (0 << 2) | 1: C6,
(2 << 4) | (2 << 2) | 2: M0,
(2 << 4) | (2 << 2) | 0: M1,
(2 << 4) | (0 << 2) | 2: M2,
(2 << 4) | (0 << 2) | 0: M3,
(0 << 4) | (2 << 2) | 2: M4,
(0 << 4) | (2 << 2) | 0: M5,
(0 << 4) | (0 << 2) | 2: M6,
}
func decodeMicEPositionCommentType(bits []micEMessageBit) micEPositionCommentType {
if v, ok := micEPositionCommentTypeMap[(bits[0]&0x3)<<4|(bits[1]&0x03)<<2|(bits[2]&0x03)]; ok {
return v
}
return Invalid
}
func decodeMicECallsign(call []byte) (lat Latitude, kind micEPositionCommentType, longOffset int, longDir int, err error) {
if len(call) != 6 {
err = io.ErrUnexpectedEOF
return
}
var latDir byte = 'X'
if (call[3] >= '0' && call[3] <= '9') || call[3] == 'L' {
latDir = 'S'
} else if call[3] >= 'P' && call[3] <= 'Z' {
latDir = 'N'
}
latBytes := []byte{
decodeLatitudeDigit(call[0]),
decodeLatitudeDigit(call[1]),
decodeLatitudeDigit(call[2]),
decodeLatitudeDigit(call[3]),
'.',
decodeLatitudeDigit(call[4]),
decodeLatitudeDigit(call[5]),
latDir,
}
if err = lat.ParseUncompressed(latBytes); err != nil {
return
}
kind = decodeMicEPositionCommentType([]micEMessageBit{
decodeMicEMessageBit(call[0]),
decodeMicEMessageBit(call[1]),
decodeMicEMessageBit(call[2]),
})
switch {
case (call[4] >= '0' && call[4] <= '9') || call[4] == 'L':
longOffset = 0
case call[4] >= 'P' && call[4] <= 'Z':
longOffset = 100
}
switch {
case (call[5] >= '0' && call[5] <= '9') || call[5] == 'L':
longDir = -1
case call[5] >= 'P' && call[5] <= 'Z':
longDir = +1
}
return
}
func decodeLatitudeDigit(c uint8) uint8 {
switch {
case c >= '0' && c <= '9':
return c
case c >= 'A' && c <= 'J':
return c - 17
case c == 'K' || c == 'L' || c == 'Z':
return ' '
case c >= 'P' && c <= 'Y':
return c - 32
default:
return 0
}
}
func decodeMicELongitude(b []byte, offset, dir int) Longitude {
if len(b) != 3 {
return 0
}
d := int(b[0]) - 28 + offset
if d >= 180 && d <= 189 {
d -= 80
} else if d >= 190 && d <= 199 {
d -= 190
}
m := int(b[1] - 28)
if m >= 60 {
m -= 60
}
h := int(b[2] - 28)
return LongitudeFromDMH(d, m, h, dir < 0)
}
func parseMicECourseAndSpeed(data []byte) (out *Velocity) {
var (
sp = data[0] - 28
dc = data[1] - 28
se = data[2] - 28
speedKnots = float64(sp)*10 + math.Floor(float64(dc)/10)
courseDeg = ((int(dc) % 10) * 100) + int(se)
)
if speedKnots >= 800 {
speedKnots -= 800
}
if courseDeg >= 400 {
courseDeg -= 400
}
return &Velocity{
Course: float64(courseDeg),
Speed: knotsToMetersPerSecond(speedKnots),
}
}
func parseMicEAltitude(data string) (altitude float64, comment string, err error) {
if len(data) < 4 || data[3] != '}' {
return 0, data, nil
}
var value int
if value, err = base91Decode(data[:3]); err != nil {
return 0, "", fmt.Errorf("aprs: invalid altitude %q: %v", data, err)
}
altitude = feetToMeters(float64(value - 10000))
comment = data[4:]
return
}
func parseMicERadioModel(data string) (stripped string, hasMessaging bool) {
if len(data) == 0 {
return data, false
}
switch data[0] {
case '>', ']':
stripped = strings.TrimRight(data[1:], "=") // Kenwood TH-D72 / Kenwood TM-D710
stripped = strings.TrimRight(data[1:], "^") // Kenwood TH-D74
stripped = strings.TrimRight(data[1:], "&") // Kenwood TH-D75
case '`', '\'':
hasMessaging = data[0] == '`'
stripped = strings.TrimSuffix(data[1:], "_(") // Yaesu FT2D
stripped = strings.TrimSuffix(data[1:], "_0") // Yaesu FT3D
stripped = strings.TrimSuffix(data[1:], "_3") // Yaesu FT5D
stripped = strings.TrimSuffix(data[1:], "|3") // Byonics TinyTrack 3
stripped = strings.TrimSuffix(data[1:], "|4") // Byonics TinyTrack 4
default:
stripped = data
}
return
}
func parseMicEGridSquare(data string) (latitude, longitude, altitude float64, comment string, err error) {
return
}
var miceCodes = map[byte]map[int]string{
'0': {0: "0", 1: "0", 2: "S", 3: "0", 4: "E"},
'1': {0: "1", 1: "0", 2: "S", 3: "0", 4: "E"},
'2': {0: "2", 1: "0", 2: "S", 3: "0", 4: "E"},
'3': {0: "3", 1: "0", 2: "S", 3: "0", 4: "E"},
'4': {0: "4", 1: "0", 2: "S", 3: "0", 4: "E"},
'5': {0: "5", 1: "0", 2: "S", 3: "0", 4: "E"},
'6': {0: "6", 1: "0", 2: "S", 3: "0", 4: "E"},
'7': {0: "7", 1: "0", 2: "S", 3: "0", 4: "E"},
'8': {0: "8", 1: "0", 2: "S", 3: "0", 4: "E"},
'9': {0: "9", 1: "0", 2: "S", 3: "0", 4: "E"},
'A': {0: "0", 1: "1 (Custom)"},
'B': {0: "1", 1: "1 (Custom)"},
'C': {0: "2", 1: "1 (Custom)"},
'D': {0: "3", 1: "1 (Custom)"},
'E': {0: "4", 1: "1 (Custom)"},
'F': {0: "5", 1: "1 (Custom)"},
'G': {0: "6", 1: "1 (Custom)"},
'H': {0: "7", 1: "1 (Custom)"},
'I': {0: "8", 1: "1 (Custom)"},
'J': {0: "9", 1: "1 (Custom)"},
'K': {0: " ", 1: "1 (Custom)"},
'L': {0: " ", 1: "0", 2: "S", 3: "0", 4: "E"},
'P': {0: "0", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'Q': {0: "1", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'R': {0: "2", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'S': {0: "3", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'T': {0: "4", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'U': {0: "5", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'V': {0: "6", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'W': {0: "7", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'X': {0: "8", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'Y': {0: "9", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'Z': {0: " ", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
}
var miceMsgTypes = map[string]string{
"000": "Emergency",
"001 (Std)": "Priority",
"001 (Custom)": "Custom-6",
"010 (Std)": "Special",
"010 (Custom)": "Custom-5",
"011 (Std)": "Committed",
"011 (Custom)": "Custom-4",
"100 (Std)": "Returning",
"100 (Custom)": "Custom-3",
"101 (Std)": "In Service",
"101 (Custom)": "Custom-2",
"110 (Std)": "En Route",
"110 (Custom)": "Custom-1",
"111 (Std)": "Off Duty",
"111 (Custom)": "Custom-0",
}

View File

@@ -0,0 +1,219 @@
package aprs
import (
"errors"
"fmt"
)
var (
ErrLatitude = errors.New("aprs: invalid latitude")
ErrLongitude = errors.New("aprs: invalid longitude")
)
// Latitude is the north-south position. Positive values are North, negative are South.
type Latitude float64
func LatitudeFromDMH(degrees, minutes, hundreths int, north bool) Latitude {
v := float64(degrees) + float64(minutes)/60 + float64(hundreths)/6000
for v > 90 {
v -= 180
}
for v < -90 {
v += 180
}
if north {
return Latitude(v)
}
return -Latitude(v)
}
func (lat Latitude) DMH() (degrees, minutes, hundreths int, north bool) {
degrees = int(lat)
minutes = int((float64(lat) - float64(degrees)) * 60)
hundreths = int((float64(lat) - float64(degrees) - float64(minutes)/60) * 6000)
if hundreths == 100 {
hundreths = 0
minutes += 1
}
if minutes == 60 {
minutes = 0
degrees += 1
}
north = lat >= 0
return
}
func (lat *Latitude) ParseCompressed(b []byte) error {
if len(b) != 4 {
return ErrLatitude
}
n, err := base91Decode(string(b))
if err != nil {
return err
}
*lat = Latitude(90 - float64(n)/380926)
return nil
}
func (lat *Latitude) ParseUncompressed(b []byte) error {
if len(b) != 8 || b[4] != '.' {
return ErrLatitude
}
var north bool
switch b[7] {
case 'N':
north = true
case 'S':
north = false
default:
return ErrLatitude
}
var (
degrees, minutes, hundreths int
err error
)
if degrees, err = parseBytesWithSpaces(b[0:2]); err != nil {
return err
}
if minutes, err = parseBytesWithSpaces(b[2:4]); err != nil {
return err
}
if hundreths, err = parseBytesWithSpaces(b[5:7]); err != nil {
return err
}
*lat = LatitudeFromDMH(degrees, minutes, hundreths, north)
return nil
}
func (lat Latitude) Compressed(b []byte) {
v := int((90 - float64(lat)) * 380926.0)
base91Encode(b, v)
}
func (lat Latitude) Uncompressed(b []byte) {
var (
degrees, minutes, hundreths, north = lat.DMH()
v = fmt.Sprintf("%02d%02d.%02d", degrees, minutes, hundreths)
)
if north {
v += "N"
} else {
v += "S"
}
copy(b, []byte(b))
}
// Longitude is the east-west position. Positive values are East, negative are West.
type Longitude float64
func LongitudeFromDMH(degrees, minutes, hundreths int, east bool) Longitude {
v := float64(degrees) + float64(minutes)/60 + float64(hundreths)/6000
for v > 180 {
v -= 360
}
for v < -180 {
v += 360
}
if east {
return Longitude(v)
}
return -Longitude(v)
}
func (long Longitude) DMH() (degrees, minutes, hundreths int, east bool) {
degrees = int(long)
minutes = int((float64(long) - float64(degrees)) * 60)
hundreths = int((float64(long) - float64(degrees) - float64(minutes)/60) * 6000)
if hundreths == 100 {
hundreths = 0
minutes += 1
}
if minutes == 60 {
minutes = 0
degrees += 1
}
east = long >= 0
return
}
func (long *Longitude) ParseCompressed(b []byte) error {
if len(b) != 4 {
return ErrLatitude
}
n, err := base91Decode(string(b))
if err != nil {
return err
}
*long = Longitude(float64(n)/190463.0 - 180)
return nil
}
func (long *Longitude) ParseUncompressed(b []byte) error {
if len(b) != 9 || b[5] != '.' {
return ErrLongitude
}
var east bool
switch b[8] {
case 'E':
east = true
case 'W':
east = false
default:
return ErrLongitude
}
var (
degrees, minutes, hundreths int
err error
)
if degrees, err = parseBytesWithSpaces(b[0:3]); err != nil {
return err
}
if minutes, err = parseBytesWithSpaces(b[3:5]); err != nil {
return err
}
if hundreths, err = parseBytesWithSpaces(b[6:8]); err != nil {
return err
}
*long = LongitudeFromDMH(degrees, minutes, hundreths, east)
return nil
}
func (long Longitude) Compressed(b []byte) {
v := int((180 + float64(long)) * 190463)
base91Encode(b, v)
}
func (long Longitude) Uncompressed(b []byte) {
var (
degrees, minutes, hundreths, east = long.DMH()
v = fmt.Sprintf("%03d%02d.%02d", degrees, minutes, hundreths)
)
if east {
v += "E"
} else {
v += "W"
}
copy(b, []byte(b))
}

View File

@@ -0,0 +1,53 @@
package aprs
import "testing"
func TestLatitude(t *testing.T) {
tests := []struct {
Test string
Want Latitude
}{
{"4903.50N", 49.05833333333333},
{"4903.50S", -49.05833333333333},
{"4903.5 S", -49.05833333333333},
{"4903. S", -49.05},
{"490 . S", -49},
{"4 . S", -40},
}
for _, test := range tests {
t.Run(test.Test, func(t *testing.T) {
var lat Latitude
if err := lat.ParseUncompressed([]byte(test.Test)); err != nil {
t.Fatal(err)
}
if !testAlmostEqual(float64(test.Want), float64(lat)) {
t.Errorf("expected %f, got %f", test.Want, lat)
}
})
}
}
func TestLongitude(t *testing.T) {
tests := []struct {
Test string
Want Longitude
}{
{"00000.00E", 0},
{"00000.00W", 0},
{"00000.98W", -0.016333},
{"00098. W", -1.633333},
{"098 . W", -98.000000},
{"9 . W", -180.000000},
}
for _, test := range tests {
t.Run(test.Test, func(t *testing.T) {
var long Longitude
if err := long.ParseUncompressed([]byte(test.Test)); err != nil {
t.Fatal(err)
}
if !testAlmostEqual(float64(test.Want), float64(long)) {
t.Errorf("expected %f, got %f", test.Want, long)
}
})
}
}

View File

@@ -1,126 +1,82 @@
package aprs
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
var (
ErrAddressInvalid = errors.New(`aprs: invalid address`)
)
import "strings"
type Address struct {
Call string `json:"call"`
SSID string `json:"ssid,omitempty"`
IsRepeated bool `json:"is_repeated,omitempty"`
IsRepeated bool `json:"is_repeated"`
}
func ParseAddress(s string) Address {
r := strings.HasSuffix(s, "*")
if r {
s = strings.TrimSuffix(s, "*")
}
i := strings.IndexByte(s, '-')
if i < 1 {
return Address{
Call: s,
IsRepeated: r,
}
}
return Address{
Call: s[:i],
SSID: s[i+1:],
IsRepeated: r,
}
}
func (a Address) EqualTo(b Address) bool {
return a.Call == b.Call && a.SSID == b.SSID
return strings.EqualFold(a.Call, b.Call) && a.SSID == b.SSID
}
func (a Address) IsQConstruct() bool {
return len(a.Call) == 3 && len(a.SSID) == 0 && a.Call[0] == 'q'
}
func (a Address) Passcode() int16 {
v := int16(0x73e2)
for i, l := 0, len(a.Call); i < l; i = i + 2 {
v ^= int16(a.Call[i]) << 8
if i+1 < len(a.Call) {
v ^= int16(a.Call[i+1])
}
}
return v & 0x7fff
}
func (a Address) String() string {
var r = ""
var b strings.Builder
b.WriteString(a.Call)
if a.SSID != "" {
b.WriteByte('-')
b.WriteString(a.SSID)
}
if a.IsRepeated {
r = "*"
b.WriteByte('*')
}
if a.SSID == "" {
return a.Call + r
}
return fmt.Sprintf("%s-%s%s", a.Call, a.SSID, r)
}
func (a Address) Secret() int16 {
var h = int16(0x73e2)
var c = a.Call
if len(c)%2 > 0 {
c += "\x00"
}
for i := 0; i < len(c); i += 2 {
h ^= int16(c[i]) << 8
h ^= int16(c[i+1])
}
return h & 0x7fff
}
func (a Address) MarshalJSON() ([]byte, error) {
return json.Marshal(a.String())
}
func (a *Address) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
p, err := ParseAddress(s)
if err != nil {
return err
}
a.Call = p.Call
a.SSID = p.SSID
a.IsRepeated = p.IsRepeated
return nil
}
func ParseAddress(s string) (Address, error) {
r := strings.HasSuffix(s, "*")
if r {
s = s[:len(s)-1]
}
p := strings.Split(s, "-")
if len(p) == 0 || len(p) > 2 {
return Address{}, ErrAddressInvalid
}
a := Address{Call: p[0], IsRepeated: r}
if len(p) == 2 {
a.SSID = p[1]
}
return a, nil
}
func MustParseAddress(s string) Address {
a, err := ParseAddress(s)
if err != nil {
panic(err)
}
return a
}
func IsQConstruct(call string) bool {
return len(call) == 3 && call[0] == 'q'
return b.String()
}
type Path []Address
func (p Path) String() string {
var s = make([]string, len(p))
for i, a := range p {
s[i] = a.String()
func (path Path) EqualTo(other Path) bool {
if len(path) != len(other) {
return false
}
return strings.Join(s, ",")
for i, a := range path {
if !a.EqualTo(other[i]) {
return false
}
}
return true
}
func ParsePath(p string) (Path, error) {
ss := strings.Split(p, ",")
if len(ss) == 0 {
return nil, nil
func (path Path) String() string {
part := make([]string, len(path))
for i, a := range path {
part[i] = a.String()
}
var err error
as := make(Path, len(ss))
for i, s := range ss {
as[i], err = ParseAddress(s)
if err != nil {
return nil, err
}
}
return as, nil
return strings.Join(part, ",")
}

View File

@@ -217,6 +217,7 @@ func (client *ProxyClient) copy(dst, src net.Conn, host, dir string, call *strin
func (client *ProxyClient) Info() *radio.Info {
// We have very little information actually, but here we go:
return &radio.Info{
ID: client.myCall,
Name: client.myCall,
}
}

View File

@@ -2,12 +2,14 @@ package aprs
import (
"fmt"
"slices"
)
func base91Decode(s string) (n int, err error) {
for i, l := 0, len(s); i < l; i++ {
c := s[i]
if c < 33 || c > 122 {
// panic(fmt.Sprintf("aprs: invalid base-91 encoding char %q (%d)", c, c))
return 0, fmt.Errorf("aprs: invalid base-91 encoding char %q (%d)", c, c)
}
@@ -17,14 +19,13 @@ func base91Decode(s string) (n int, err error) {
return
}
/*
func base91Encode(n int) string {
var s []string
for n > 0 {
c := n % 91
func base91Encode(b []byte, n int) {
i := 0
for n > 1 {
x := n % 91
n /= 91
s = append([]string{string(byte(c) + 33)}, s...)
b[i] = byte(x) + 33
i++
}
return strings.Join(s, "")
slices.Reverse(b[:i])
}
*/

119
protocol/aprs/data.go Normal file
View File

@@ -0,0 +1,119 @@
package aprs
import (
"fmt"
"regexp"
"strconv"
"time"
)
type decoder interface {
CanDecode(*Frame) bool
Decode(*Frame) (Data, error)
}
var decoders []decoder
func init() {
decoders = []decoder{
positionDecoder{},
micEDecoder{},
messageDecoder{},
queryDecoder{},
}
}
// Raw is the string encoded raw frame payload.
type Raw string
func (p Raw) Type() Type {
var t Type
if len(p) > 0 {
t = Type(p[0])
}
return t
}
// Data represents a decoded payload data.
type Data interface {
String() string
}
// Type of payload.
type Type byte
var typeName = map[Type]string{
0x1c: "Current Mic-E Data (Rev 0 beta)",
0x1d: "Old Mic-E Data (Rev 0 beta)",
'!': "Position without timestamp (no APRS messaging), or Ultimeter 2000 WX Station",
'#': "Peet Bros U-II Weather Station",
'$': "Raw GPS data or Ultimeter 2000",
'%': "Agrelo DFJr / MicroFinder",
'"': "Old Mic-E Data (but Current data for TM-D700)",
')': "Item",
'*': "Peet Bros U-II Weather Station",
',': "Invalid data or test data",
'/': "Position with timestamp (no APRS messaging)",
':': "Message",
';': "Object",
'<': "Station Capabilities",
'=': "Position without timestamp (with APRS messaging)",
'>': "Status",
'?': "Query",
'@': "Position with timestamp (with APRS messaging)",
'T': "Telemetry data",
'[': "Maidenhead grid locator beacon (obsolete)",
'_': "Weather Report (without position)",
'`': "Current Mic-E Data (not used in TM-D700)",
'{': "User-Defined APRS packet format",
'}': "Third-party traffic",
}
func (t Type) String() string {
if s, ok := typeName[t]; ok {
return s
}
return fmt.Sprintf("unknown %02x", byte(t))
}
func (t Type) IsMessage() bool {
return t == ':'
}
func (t Type) IsThirdParty() bool {
return t == '}'
}
var matchTimestamp = regexp.MustCompile(`^[0-9]{6}[zh/]`)
func hasTimestamp(s string) bool {
return matchTimestamp.MatchString(s)
}
func parseTimestamp(s string) (t time.Time, comment string, err error) {
// log.Printf("parse timestamp %q", s)
var hh, mm, ss int
if hh, err = strconv.Atoi(s[0:2]); err != nil {
return
}
if mm, err = strconv.Atoi(s[2:4]); err != nil {
return
}
if ss, err = strconv.Atoi(s[4:6]); err != nil {
return
}
now := time.Now()
switch s[6] {
case 'z': // DDHHMMz zulu time
now = now.UTC()
return time.Date(now.Year(), now.Month(), hh, mm, ss, 0, 0, time.UTC), s[7:], nil
case '/': // DDHHMM/ local time
return time.Date(now.Year(), now.Month(), hh, mm, ss, 0, 0, now.Location()), s[7:], nil
case 'h': // HHMMSSh zulu time
now = now.UTC()
return time.Date(now.Year(), now.Month(), now.Day(), hh, mm, ss, 0, time.UTC), s[7:], nil
default:
return time.Time{}, "", fmt.Errorf("aprs: invalid/unknown timestamp marker %q", s[6])
}
}

View File

@@ -0,0 +1,29 @@
package aprs
import (
"reflect"
"testing"
)
func testCompareData(t *testing.T, want, test Data) {
t.Helper()
if want == nil && test != nil {
t.Errorf("expected no data, got %T", test)
return
}
if want != nil && test == nil {
t.Errorf("expected data %T, got nil", want)
return
}
if reflect.TypeOf(want) != reflect.TypeOf(test) {
t.Errorf("expected data %T, got %T", want, test)
return
}
switch want := want.(type) {
case *Position:
testComparePosition(t, want, test.(*Position))
}
}

View File

@@ -1,41 +0,0 @@
package aprs
import "fmt"
type DataType byte
var (
dataTypeName = map[DataType]string{
0x1c: "Current Mic-E Data (Rev 0 beta)",
0x1d: "Old Mic-E Data (Rev 0 beta)",
'!': "Position without timestamp (no APRS messaging), or Ultimeter 2000 WX Station",
'#': "Peet Bros U-II Weather Station",
'$': "Raw GPS data or Ultimeter 2000",
'%': "Agrelo DFJr / MicroFinder",
'"': "Old Mic-E Data (but Current data for TM-D700)",
')': "Item",
'*': "Peet Bros U-II Weather Station",
',': "Invalid data or test data",
'/': "Position with timestamp (no APRS messaging)",
':': "Message",
';': "Object",
'<': "Station Capabilities",
'=': "Position without timestamp (with APRS messaging)",
'>': "Status",
'?': "Query",
'@': "Position with timestamp (with APRS messaging)",
'T': "Telemetry data",
'[': "Maidenhead grid locator beacon (obsolete)",
'_': "Weather Report (without position)",
'`': "Current Mic-E Data (not used in TM-D700)",
'{': "User-Defined APRS packet format",
'}': "Third-party traffic",
}
)
func (t DataType) String() string {
if s, ok := dataTypeName[t]; ok {
return s
}
return fmt.Sprintf("Unknown packet type %#02x", byte(t))
}

70
protocol/aprs/frame.go Normal file
View File

@@ -0,0 +1,70 @@
package aprs
import (
"errors"
"strings"
)
var (
ErrPayloadMarker = errors.New("aprs: can't find payload marker")
ErrDestinationMarker = errors.New("aprs: can't find destination marker")
ErrPathLength = errors.New("aprs: invalid path length")
)
// Frame represents a single APRS frame.
type Frame struct {
// Addressing
Source Address `json:"source"`
Destination Address `json:"destination"`
Path Path `json:"path"`
// Raw payload
Raw Raw `json:"raw"`
// Data contained in the raw payload.
Data Data `json:"data,omitempty"`
// Data extracted
Latitude float64
Longitude float64
Altitude float64
Symbol string
Comment string
}
func Parse(s string) (*Frame, error) {
i := strings.IndexByte(s, ':')
if i == -1 {
return nil, ErrPayloadMarker
}
var (
route = s[:i]
frame = &Frame{Raw: Raw(s[i+1:])}
)
if i = strings.IndexByte(route, '>'); i == -1 {
return nil, ErrDestinationMarker
}
frame.Source, route = ParseAddress(route[:i]), route[i+1:]
path := strings.Split(route, ",")
if len(path) == 0 || len(path) > 9 {
return nil, ErrPathLength
}
frame.Destination = ParseAddress(path[0])
for i, l := 1, len(path); i < l; i++ {
addr := ParseAddress(path[i])
frame.Path = append(frame.Path, addr)
}
var err error
for _, d := range decoders {
if d.CanDecode(frame) {
if frame.Data, err = d.Decode(frame); err == nil {
break
}
}
}
return frame, nil
}

136
protocol/aprs/frame_test.go Normal file
View File

@@ -0,0 +1,136 @@
package aprs
import (
"bufio"
"io"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
)
func TestParse(t *testing.T) {
tests := []struct {
Line string
Want *Frame
}{
{
`PD0MZ-10>APLRG1,WIDE1-1,qAC:=L4:I#P),la !GLoRa APRS iGate / 433.775MHz / 125kHz / SF12 / CR5 Batt=4.25V`,
&Frame{
Source: Address{Call: "PD0MZ", SSID: "10"},
Destination: Address{Call: "APLRG1"},
Path: Path{{Call: "WIDE1", SSID: "1"}, {Call: "qAC"}},
Data: &Position{
Latitude: 51.860004,
Longitude: 6.309997,
HasMessaging: true,
Symbol: "La",
Comment: "LoRa APRS iGate / 433.775MHz / 125kHz / SF12 / CR5 Batt=4.25V",
},
},
},
}
for _, test := range tests {
t.Run(test.Line, func(t *testing.T) {
f, err := Parse(test.Line)
if err != nil {
t.Fatal(err)
}
//if !reflect.DeepEqual(f, test.Want) {
// t.Errorf("expected %+#v, got %#+v", test.Want, f)
//}
testCompareFrame(t, test.Want, f)
})
}
}
func TestParseSamples(t *testing.T) {
f, err := os.Open(filepath.Join("testdata", "packets.txt"))
if err != nil {
t.Skip(err)
return
}
defer func() { _ = f.Close() }()
r := bufio.NewReader(f)
for {
l, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
return
}
t.Fatal(err)
}
l = testUnescapeHex(strings.TrimSpace(l[25:]))
if _, err = Parse(l); err != nil {
t.Errorf("%s: %v", l, err)
}
}
}
// testUnescapeHex replaces occurrences of <0xHH> (case-insensitive)
// with the corresponding single byte.
// Invalid sequences are left unchanged.
func testUnescapeHex(s string) string {
if !strings.Contains(s, "<0x") && !strings.Contains(s, "<0X") {
return s
}
var b strings.Builder
b.Grow(len(s)) // upper bound; result will not exceed input length
for i := 0; i < len(s); {
// Minimal length for "<0xHH>" is 6
if i+6 <= len(s) &&
s[i] == '<' &&
(s[i+1] == '0') &&
(s[i+2] == 'x' || s[i+2] == 'X') &&
testIsHex(s[i+3]) &&
testIsHex(s[i+4]) &&
s[i+5] == '>' {
// Parse the hex byte
v, err := strconv.ParseUint(s[i+3:i+5], 16, 8)
if err == nil {
b.WriteByte(byte(v))
i += 6
continue
}
}
// Default: copy one byte and continue
b.WriteByte(s[i])
i++
}
return b.String()
}
func testIsHex(c byte) bool {
return ('0' <= c && c <= '9') ||
('a' <= c && c <= 'f') ||
('A' <= c && c <= 'F')
}
func testAlmostEqual(a, b float64) bool {
return math.Abs(a-b) < 1e-5
}
func testCompareFrame(t *testing.T, want, test *Frame) {
t.Helper()
if !test.Source.EqualTo(want.Source) {
t.Errorf("expected source %s, got %s", want.Source, test.Source)
}
if !test.Destination.EqualTo(want.Destination) {
t.Errorf("expected destination %s, got %s", want.Destination, test.Destination)
}
if !test.Path.EqualTo(want.Path) {
t.Errorf("expected path %q, got %q", want.Path, test.Path)
}
testCompareData(t, want.Data, test.Data)
}

99
protocol/aprs/message.go Normal file
View File

@@ -0,0 +1,99 @@
package aprs
import (
"fmt"
"strconv"
"strings"
)
type Message struct {
ID int `json:"id"`
IsAcknowledge bool `json:"is_ack"`
IsRejection bool `json:"is_rejection"`
IsBulletin bool `json:"is_bulletin"`
Recipient string `json:"recipient"`
AnnoucementID byte `json:"announcement_id,omitempty"`
Group string `json:"group,omitempty"`
Severity string `json:"severity,omitempty"`
Text string `json:"text"`
}
func (msg *Message) String() string {
return fmt.Sprintf("%s: %q", msg.Recipient, msg.Text)
}
type messageDecoder struct{}
func (messageDecoder) CanDecode(frame *Frame) bool {
var (
maybeMessage = len(frame.Raw) >= 11 && frame.Raw[10] == ':'
maybeBulletin = len(frame.Raw) >= 10 && frame.Raw[9] == ':'
)
return frame.Raw.Type() == ':' && (maybeMessage || maybeBulletin)
}
func (messageDecoder) Decode(frame *Frame) (data Data, err error) {
var (
msg = new(Message)
text string
size = len(text)
)
if len(frame.Raw) >= 10 && frame.Raw[9] == ':' {
msg.Recipient, text = strings.TrimSpace(string(frame.Raw[1:9])), string(frame.Raw[9:])
} else {
msg.Recipient, text = strings.TrimSpace(string(frame.Raw[1:10])), string(frame.Raw[10:])
}
switch {
case strings.HasPrefix(msg.Recipient, "BLN"):
// Bulletin
kind := msg.Recipient[3:]
if id, err := strconv.Atoi(kind); err == nil {
// General bulletin
msg.IsBulletin = true
msg.ID = id
} else if len(kind) >= 2 && isDigit(kind[0]) {
// Group Bulletin Format
msg.IsBulletin = true
msg.ID = int(kind[0] - '0')
msg.Group = kind[1:]
} else if len(kind) == 1 {
// Announcement
msg.IsBulletin = true
msg.AnnoucementID = kind[0]
}
case strings.HasPrefix(msg.Recipient, "NWS-"):
// National Weather Service Bulletins
msg.IsBulletin = true
msg.Severity = msg.Recipient[4:]
default:
if i := strings.LastIndexByte(text, '{'); i != -1 && i > (size-6) {
// Plain message ID: {XXXXX (where there are 1-5 X)
msg.ID, _ = strconv.Atoi(text[i+1:])
text = text[:i]
}
if i := strings.LastIndex(text, ":ack"); i != -1 && i > (size-9) {
// Message acknowledgement: :ackXXXXX (where there are 1-5 X)
msg.IsAcknowledge = true
msg.ID, _ = strconv.Atoi(text[i+4:])
text = text[:i]
}
if i := strings.LastIndex(text, ":rej"); i != -1 && i > (size-9) {
// Message acknowledgement: :rejXXXXX (where there are 1-5 X)
msg.IsRejection = true
msg.ID, _ = strconv.Atoi(text[i+4:])
text = text[:i]
}
}
if len(text) > 0 && text[0] == ':' {
text = text[1:]
}
msg.Text = text
return msg, nil
}

View File

@@ -0,0 +1,153 @@
package aprs
import (
"testing"
)
func TestParseMessage(t *testing.T) {
tests := []struct {
Name string
Raw Raw
Want *Message
}{
{
"message, no ack expected",
":WU2Z :Testing",
&Message{
Recipient: "WU2Z",
Text: "Testing",
},
},
{
"message with sequence number, ack expected",
":WU2Z :Testing{003",
&Message{
ID: 3,
Recipient: "WU2Z",
Text: "Testing",
},
},
{
"an e-mail message",
":EMAIL :msproul@ap.org Test email",
&Message{
Recipient: "EMAIL",
Text: "msproul@ap.org Test email",
},
},
{
"message acknowledgement",
":KB2ICI-14:ack003",
&Message{
ID: 3,
IsAcknowledge: true,
Recipient: "KB2ICI-14",
},
},
{
"message rejection",
":KB2ICI-14:rej003",
&Message{
ID: 3,
IsRejection: true,
Recipient: "KB2ICI-14",
},
},
{
"bulletin",
":BLN3 :Snow expected in Tampa RSN",
&Message{
ID: 3,
IsBulletin: true,
Recipient: "BLN3",
Text: "Snow expected in Tampa RSN",
},
},
{
"annoucement",
":BLNQ :Mt St Helen digi will be QRT this weekend",
&Message{
IsBulletin: true,
Recipient: "BLNQ",
AnnoucementID: 'Q',
Text: "Mt St Helen digi will be QRT this weekend",
},
},
{
"group bulletin 4 to the WX group",
":BLN4WX :Stand by your snowplows",
&Message{
ID: 4,
IsBulletin: true,
Recipient: "BLN4WX",
Group: "WX",
Text: "Stand by your snowplows",
},
},
{
"national weather service alert",
":NWS-WARN :092010z,THUNDER_STORM,AR_ASHLEY,{S9JbA",
&Message{
IsBulletin: true,
Recipient: "NWS-WARN",
Severity: "WARN",
Text: "092010z,THUNDER_STORM,AR_ASHLEY,{S9JbA",
},
},
}
var decoder messageDecoder
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
frame := &Frame{Raw: test.Raw}
if !decoder.CanDecode(frame) {
t.Fatalf("%T can't decode %q", decoder, test.Raw)
}
v, err := decoder.Decode(frame)
if err != nil {
t.Fatal(err)
}
testCompareMessage(t, test.Want, v)
})
}
}
func testCompareMessage(t *testing.T, want *Message, value any) {
t.Helper()
test, ok := value.(*Message)
if !ok {
t.Fatalf("expected data to be a %T, got %T", want, value)
return
}
if test.ID != want.ID {
t.Errorf("expected id %d, got %d", want.ID, test.ID)
}
if test.IsAcknowledge != want.IsAcknowledge {
t.Errorf("expected is acknowledge %t, got %t", want.IsAcknowledge, test.IsAcknowledge)
}
if test.IsRejection != want.IsRejection {
t.Errorf("expected is rejection %t, got %t", want.IsRejection, test.IsRejection)
}
if test.IsBulletin != want.IsBulletin {
t.Errorf("expected is bulletin %t, got %t", want.IsBulletin, test.IsBulletin)
}
if test.Recipient != want.Recipient {
t.Errorf("expected recipient %q, got %q", want.Recipient, test.Recipient)
}
if test.AnnoucementID != want.AnnoucementID {
t.Errorf("expected annoucement id %q, got %q", want.AnnoucementID, test.AnnoucementID)
}
if test.Group != want.Group {
t.Errorf("expected group %q, got %q", want.Group, test.Group)
}
if test.Severity != want.Severity {
t.Errorf("expected severity %q, got %q", want.Severity, test.Severity)
}
if test.Text != want.Text {
t.Errorf("expected text %q, got %q", want.Text, test.Text)
}
}

434
protocol/aprs/mice.go Normal file
View File

@@ -0,0 +1,434 @@
package aprs
import (
"bytes"
"errors"
"fmt"
)
type MessageType int
const (
MsgEmergency MessageType = iota
MsgPriority
MsgSpecial
MsgCommitted
MsgReturning
MsgInService
MsgEnRoute
MsgOffDuty
MsgCustom0
MsgCustom1
MsgCustom2
MsgCustom3
MsgCustom4
MsgCustom5
MsgCustom6
)
type MicE struct {
// Core
Latitude float64
Longitude float64
Ambiguity int
Velocity *Velocity
Symbol string
Type MessageType
HasMessaging bool
// Extensions
Altitude float64 // in meters
DAO *DAO
Telemetry *Telemetry
Comment string
// Raw
RawDest string
RawInfo []byte
}
func (report *MicE) String() string {
return report.Comment
}
type DAO struct {
LatOffset float64
LonOffset float64
}
type micEDecoder struct{}
func (d micEDecoder) CanDecode(frame *Frame) bool {
if len(frame.Destination.Call) == 6 && len(frame.Raw) >= 8 {
for i := range 6 {
if _, _, _, _, ok := decodeDestChar(frame.Destination.Call[i]); !ok {
return false
}
}
return true
}
return false
}
func (d micEDecoder) Decode(frame *Frame) (data Data, err error) {
if len(frame.Destination.Call) < 6 {
return nil, errors.New("destination too short for Mic-E")
}
if len(frame.Raw) < 9 {
return nil, errors.New("info field too short for Mic-E")
}
var (
report = new(MicE)
north, west bool
dest = frame.Destination.Call
info = []byte(frame.Raw[1:])
)
if report.Latitude, report.Type, report.Ambiguity, north, err = report.decodeLatitude(dest); err != nil {
return
}
if report.Longitude, west, report.Velocity, report.Symbol, err = report.decodeMicELongitudeAndMotion(dest, info); err != nil {
return
}
if !north {
report.Latitude = -report.Latitude
}
if west {
report.Longitude = -report.Longitude
}
report.parseExtensions(info[8:])
frame.Latitude = report.Latitude
frame.Longitude = report.Longitude
frame.Altitude = report.Altitude
frame.Symbol = report.Symbol
frame.Comment = report.Comment
return report, nil
}
func (report *MicE) decodeLatitude(dest string) (
lat float64,
msg MessageType,
ambiguity int,
north bool,
err error,
) {
if len(dest) != 6 {
return 0, 0, 0, false, errors.New("aprs: MicE destination must be 6 chars")
}
var digits [6]int
var msgBits int
for i := range 6 {
c := dest[i]
d, amb, nBit, mBit, ok := decodeDestChar(c)
if !ok {
return 0, 0, 0, false, fmt.Errorf("invalid dest char %q", c)
}
digits[i] = d
if amb {
ambiguity++
}
// Only first 3 chars contain message bits
if i < 3 && mBit {
msgBits |= 1 << (2 - i)
}
// North bit defined by char 3
if i == 3 {
north = nBit
}
}
// Apply ambiguity masking per spec
maskAmbiguity(digits[:], ambiguity)
deg := digits[0]*10 + digits[1]
min := digits[2]*10 + digits[3]
hun := digits[4]*10 + digits[5]
if deg > 89 || min > 59 || hun > 99 {
return 0, 0, 0, false, errors.New("invalid latitude range")
}
lat = float64(deg) +
float64(min)/60.0 +
float64(hun)/6000.0
if !north {
lat = -lat
}
return lat, report.interpretMessage(msgBits), ambiguity, north, nil
}
func decodeDestChar(c byte) (
digit int,
ambiguity bool,
northBit bool,
msgBit bool,
ok bool,
) {
switch {
case c >= '0' && c <= '9':
return int(c - '0'), false, true, false, true
case c >= 'A' && c <= 'J':
return int(c - 'A'), false, true, true, true
case c >= 'P' && c <= 'Y':
return int(c - 'P'), false, false, true, true
case c == 'K' || c == 'L' || c == 'Z':
return 0, true, true, false, true
default:
return 0, false, false, false, false
}
}
func maskAmbiguity(digits []int, ambiguity int) {
for i := 0; i < ambiguity && i < len(digits); i++ {
idx := len(digits) - 1 - i
digits[idx] = 0
}
}
func (report *MicE) decodeMicELongitudeAndMotion(dest string, info []byte) (lon float64, west bool, velocity *Velocity, symbol string, err error) {
if len(info) < 3 {
err = errors.New("info too short for longitude")
return
}
d := int(info[0]) - 28
m := int(info[1]) - 28
h := int(info[2]) - 28
if d < 0 || m < 0 || h < 0 {
err = errors.New("invalid longitude encoding")
return
}
// 100° offset bit from dest[4]
if dest[4] >= 'P' {
d += 100
}
// Wrap correction
if d >= 180 {
d -= 80
}
if m >= 60 {
m -= 60
d++
}
lon = float64(d) +
float64(m)/60.0 +
float64(h)/6000.0
// East/West from dest[5]
west = dest[5] >= 'P'
if west {
lon = -lon
}
// Speed/course
if len(info) >= 6 {
s1 := int(info[3]) - 28
s2 := int(info[4]) - 28
s3 := int(info[5]) - 28
if !(s1 < 0 || s2 < 0 || s3 < 0) {
speed := s1*10 + s2/10
course := (s2%10)*100 + s3
if speed >= 800 {
speed -= 800
}
if course >= 400 {
course -= 400
}
if course >= 360 {
course %= 360
}
velocity = &Velocity{
Speed: knotsToMetersPerSecond(float64(speed)),
Course: course,
}
}
}
// Symbol
if len(info) >= 8 {
symbol = string([]byte{info[7], info[6]})
}
return
}
func (report *MicE) interpretMessage(bits int) MessageType {
if bits == 0 {
return MsgEmergency
}
if bits <= 7 {
return MessageType(bits)
}
return MsgCustom0
}
func (report *MicE) parseExtensions(info []byte) {
info = report.parseOldTelemetry(info)
info = report.parseNewTelemetry(info)
info = report.parseAltitude(info)
info = report.parseDAO(info)
info = report.parseRadioPrefix(info)
// Remainder is comment
if len(info) > 0 {
report.Comment = string(info)
}
}
func (report *MicE) parseRadioPrefix(info []byte) []byte {
if len(info) == 0 {
return info
}
switch info[0] {
case '>', ']': // Kenwood
return info[1:]
case '`': // Yaesu / Byonics / etc.
report.HasMessaging = true
return info[1:]
case '\'': // Yaesu / Byonics / etc.
return info[1:]
default:
return info
}
}
func (report *MicE) parseOldTelemetry(info []byte) []byte {
if len(info) < 1 {
return info
}
b := info[0]
if b != '`' && b != '\'' {
return info
}
// Need 6 bytes total
if 6 > len(info) {
return info // not fatal, ignore malformed
}
data := info[1:6]
values := make([]int, 5)
for i := 0; i < 5; i++ {
values[i] = int(data[i] - 33)
}
report.Telemetry = &Telemetry{
Analog: values,
}
return info[6:]
}
func (report *MicE) parseNewTelemetry(info []byte) []byte {
i := bytes.IndexByte(info, '|')
if i == -1 {
return info
}
prefix, data := info[:i], info[i+1:]
if i = bytes.IndexByte(data, '|'); i == -1 {
return info
}
suffix, data := data[i+1:], data[:i]
if len(data) < 2 || len(data)%2 != 0 {
return info
}
report.Telemetry = new(Telemetry)
var err error
if report.Telemetry.ID, err = base91Decode(string(data[:2])); err != nil {
return info
}
data = data[2:]
for range 5 {
if len(data) == 0 {
break
}
var value int
if value, err = base91Decode(string(data[:2])); err != nil {
return info
}
report.Telemetry.Analog = append(report.Telemetry.Analog, value)
data = data[2:]
}
if len(data) > 0 {
var digital int
if digital, err = base91Decode(string(data[:2])); err != nil {
return info
}
for range 8 {
report.Telemetry.Digital = append(report.Telemetry.Digital, (digital&1) == 1)
digital >>= 1
}
}
return append(prefix, suffix...)
}
func (report *MicE) parseAltitude(info []byte) []byte {
if 4 > len(info) {
return info
}
if info[3] != '}' {
return info
}
alt, err := base91Decode(string(info[:3]))
if err != nil {
return info
}
value := alt - 10000
report.Altitude = feetToMeters(float64(value))
return info[4:]
}
func (report *MicE) parseDAO(info []byte) []byte {
if 6 > len(info) {
return info
}
if info[0] != '!' || info[1] != 'W' {
return info
}
latOff := float64(info[2]-33) / 10000.0
lonOff := float64(info[3]-33) / 10000.0
report.DAO = &DAO{
LatOffset: latOff,
LonOffset: lonOff,
}
return info[6:]
}

130
protocol/aprs/mice_test.go Normal file
View File

@@ -0,0 +1,130 @@
package aprs
import (
"reflect"
"testing"
)
func TestPositionMicE(t *testing.T) {
tests := []struct {
Name string
Frame string
Want *MicE
}{
{
"position",
"N0CALL>S32U6T:`(_f \"Oj/",
&MicE{
Latitude: 33.427333,
Longitude: 13.129000,
Symbol: "/j",
Velocity: &Velocity{Course: 251, Speed: 20.57777776},
},
},
{
"position with altitude",
"N0CALL>S32U6T:`(_f \"Oj/\"4T}",
&MicE{
Latitude: 33.427333,
Longitude: 13.129000,
Altitude: feetToMeters(61),
Symbol: "/j",
Velocity: &Velocity{Course: 251, Speed: 20.57777776},
},
},
{
"position with telemetry",
"N0CALL>S32U6T:`(_f \"Oj/>|\\'s%0\\'c|",
&MicE{
Latitude: 33.427333,
Longitude: 13.129000,
Symbol: "/j",
Velocity: &Velocity{Course: 251, Speed: 20.57777776},
Telemetry: &Telemetry{ID: 5375, Analog: []int{7466, 1424, 612}},
},
},
/*
{
"gridsquare position with comment",
"NOCALL>S32U6T:IO91SX/G Hello world",
&Position{
Latitude: 33.427333,
Longitude: -12.129,
Symbol: "/G",
Comment: "Hello world",
},
},
*/
}
var decoder micEDecoder
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
frame, err := Parse(test.Frame)
if err != nil {
t.Fatalf("can't parse %q: %v", test.Frame, err)
}
if !decoder.CanDecode(frame) {
t.Fatalf("%T can't decode %q", decoder, test.Frame)
}
v, err := decoder.Decode(frame)
if err != nil {
t.Fatalf("can't decode %q: %v", test.Frame, err)
}
testCompareMicE(t, test.Want, v)
})
}
}
func testCompareMicE(t *testing.T, want *MicE, value any) {
t.Helper()
p, ok := value.(*MicE)
if !ok {
t.Fatalf("expected data to be a %T, got %T", want, value)
return
}
if p.HasMessaging != want.HasMessaging {
t.Errorf("expected to have messaging: %t, got %t", want.HasMessaging, p.HasMessaging)
}
if !testAlmostEqual(p.Latitude, want.Latitude) {
t.Errorf("expected latitude %f, got %f", want.Latitude, p.Latitude)
}
if !testAlmostEqual(p.Longitude, want.Longitude) {
t.Errorf("expected longitude %f, got %f", want.Longitude, p.Longitude)
}
if !testAlmostEqual(p.Altitude, want.Altitude) {
t.Errorf("expected altitude %f, got %f", want.Altitude, p.Altitude)
}
if p.Symbol != want.Symbol {
t.Errorf("expected symbol %q, got %q", want.Symbol, p.Symbol)
}
if p.Comment != want.Comment {
t.Errorf("expected comment %q, got %q", want.Comment, p.Comment)
}
if want.Velocity != nil {
if p.Velocity == nil {
t.Errorf("expected velocity, got none")
} else if !reflect.DeepEqual(p.Velocity, want.Velocity) {
t.Errorf("expected velocity %#+v, got %#+v", want.Velocity, p.Velocity)
}
} else if p.Velocity != nil {
t.Errorf("expected no velocity, got %#+v", p.Velocity)
}
if want.Telemetry != nil {
if p.Telemetry == nil {
t.Errorf("expected telemetry, got none")
} else if !reflect.DeepEqual(p.Telemetry, want.Telemetry) {
t.Errorf("expected telemetry %#+v, got %#+v", want.Telemetry, p.Telemetry)
}
} else if p.Telemetry != nil {
t.Errorf("expected no telemetry, got %#+v", p.Telemetry)
}
}

View File

@@ -1,481 +0,0 @@
package aprs
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
"time"
)
var (
// ErrInvalidPacket signals a corrupted/unknown APRS packet.
ErrInvalidPacket = errors.New("aprs: invalid packet")
// ErrInvalidPosition signals a corrupted APRS position report.
ErrInvalidPosition = errors.New("aprs: invalid position")
)
// Payload is the raw payload contained within an APRS packet.
type Payload string
// Type of payload.
func (p Payload) Type() DataType {
var t DataType
if len(p) > 0 {
t = DataType(p[0])
}
return t
}
func isDigit(b byte) bool {
return b >= '0' && b <= '9'
}
func (p Payload) Len() int { return len(p) }
// Velocity details.
type Velocity struct {
Course float64 // Degrees
Speed float64 // Knots
}
// Wind details.
type Wind struct {
Direction float64 // Degrees
Speed float64 // Knots
}
// PowerHeightGain details.
type PowerHeightGain struct {
PowerCode byte
HeightCode byte
GainCode byte
DirectivityCode byte
}
// Power level (in Watts).
func (p PowerHeightGain) Power() int {
w := int(p.PowerCode - '0')
if w <= 0 {
return 0
}
return w * w
}
// Height above ground (in meters).
func (p PowerHeightGain) Height() float64 {
h := float64(p.HeightCode - '0')
if h <= 0 {
return 10
}
return math.Pow(2, h) * 10
}
// Gain level (in dBs).
func (p PowerHeightGain) Gain() int {
d := int(p.GainCode - '0')
if d <= 0 {
return 0
}
return d
}
// Directivity angle.
func (p PowerHeightGain) Directivity() float64 {
d := int(p.DirectivityCode - '0')
if d <= 0 {
return 0
}
return float64(d%8) * 45.0
}
// OmniDFStrength contains the omni-directional direction finding signal strength (for fox hunting).
type OmniDFStrength struct {
StrengthCode byte
HeightCode byte
GainCode byte
DirectivityCode byte
}
// Strength of the signal.
func (o OmniDFStrength) Strength() int {
w := int(o.StrengthCode - '0')
if w <= 0 {
return 0
}
return w * w
}
// Height above ground (in meters).
func (o OmniDFStrength) Height() float64 {
h := float64(o.HeightCode - '0')
if h <= 0 {
return 10
}
return math.Pow(2, h) * 10
}
// Gain level (in dBs).
func (o OmniDFStrength) Gain() int {
d := int(o.GainCode - '0')
if d <= 0 {
return 0
}
return d
}
// Directivity angle.
func (o OmniDFStrength) Directivity() float64 {
d := int(o.DirectivityCode - '0')
if d <= 0 {
return 0
}
return float64(d%8) * 45.0
}
// Packet contains an APRS packet.
type Packet struct {
// Raw packet (as captured from the air or APRS-IS).
Raw string `json:"-"`
// Src is the source address.
Src Address `json:"src"`
// Dst is the destination address.
Dst Address `json:"dst"`
// Path contains the digipeater path.
Path Path `json:"path,omitempty"`
// Payload is the raw payload.
Payload Payload `json:"payload"`
// Position encoded in the payload.
Position *Position `json:"position,omitempty"`
// Time encoded in the payload.
Time *time.Time `json:"time,omitempty"`
// Altitude encoded in the payload (in feet).
Altitude float64 `json:"altitude,omitempty"`
// Velocity encoded in the payload.
Velocity *Velocity `json:"velocity,omitempty"`
// Wind details encoded in the payload.
Wind *Wind `json:"wind,omitempty"`
// PHG are the power, height and gain details encoded in the payload.
PHG *PowerHeightGain `json:"phg,omitempty"`
// DFS are the direction finder strength details encoded in the payload.
DFS *OmniDFStrength `json:"dfs,omitempty"`
// Range encoded in the payload (in miles).
Range float64 `json:"range,omitempty"`
// Symbol encoded in the payload.
Symbol Symbol `json:"symbol"`
// Comment encoded in the payload.
Comment string `json:"comment,omitempty"`
// Unparsed data.
data string
}
// ParsePacket parses an APRS packet as captured from AX.25 or APRS-IS.
func ParsePacket(raw string) (Packet, error) {
p := Packet{Raw: raw}
var i int
if i = strings.Index(raw, ":"); i < 0 {
return p, ErrInvalidPacket
}
p.Payload = Payload(raw[i+1:])
// Parse src, dst and path
var err error
var a = raw[:i]
if i = strings.Index(a, ">"); i < 0 {
return p, ErrInvalidPacket
}
if p.Src, err = ParseAddress(a[:i]); err != nil {
return p, err
}
var r = strings.Split(a[i+1:], ",")
if p.Dst, err = ParseAddress(r[0]); err != nil {
return p, err
}
if p.Path, err = ParsePath(strings.Join(r[1:], ",")); err != nil {
return p, err
}
// Post processing of payload
err = p.parse()
return p, err
}
func (p *Packet) parse() error {
s := string(p.Payload)
//log.Printf("parse %q [%c]\n", s, p.Payload.Type())
switch p.Payload.Type() {
case '!': // Lat/Long Position Report Format — without Timestamp
var o = strings.IndexByte(s, '!')
pos, txt, err := ParsePosition(s[o+1:], !isDigit(s[o+1]))
if err != nil {
return err
}
p.Position = &pos
p.data = txt
if len(s) >= 20 {
p.Symbol[0] = s[9]
p.Symbol[1] = s[19]
}
case '=':
compressed := IsValidCompressedSymTable(s[1])
pos, txt, err := ParsePosition(s[1:], compressed)
if err != nil {
return err
}
p.Position = &pos
p.data = txt
if compressed {
p.Symbol[0] = s[1]
p.Symbol[1] = s[10]
} else {
p.Symbol[0] = s[9]
p.Symbol[1] = s[19]
}
case '/', '@': // Lat/Long Position Report Format — with Timestamp
if len(s) < 8 {
return ErrInvalidPosition
}
var compressed bool
if s[7] == 'h' || s[7] == 'z' || s[7] == '/' {
if ts, err := ParseTime(s[1:]); err == nil {
p.Time = &ts
}
compressed = IsValidCompressedSymTable(s[8])
pos, txt, err := ParsePosition(s[8:], compressed)
if err != nil {
return err
}
p.Position = &pos
p.data = txt
} else if s[7] >= '0' && s[7] <= '9' {
ts, err := ParseTime(s[1:])
if err != nil {
return err
}
p.Time = &ts
compressed = IsValidCompressedSymTable(s[10])
pos, txt, err := ParsePosition(s[10:], compressed)
if err != nil {
return err
}
p.Position = &pos
p.data = txt
}
if compressed {
p.Symbol[0] = s[8]
p.Symbol[1] = s[17]
} else {
p.Symbol[0] = s[16]
p.Symbol[1] = s[26]
}
case ';':
pos, txt, err := ParsePosition(s[18:], !isDigit(s[18]))
if err != nil {
return err
}
p.Position = &pos
p.data = txt
case '[':
pos, txt, err := ParsePositionGrid(s[1:])
if err != nil {
return err
}
p.Position = &pos
p.data = txt
case '`', '\'':
pos, err := ParseMicE(s, p.Dst.Call)
if err != nil {
return err
}
p.Position = &pos
_ = p.parseMicEData()
return nil // there is no additional data to parse
default:
pos, txt, err := ParsePositionBoth(s)
if err != nil {
if err != ErrInvalidPosition {
return err
}
p.Comment = s[1:]
} else {
p.Position = &pos
p.Comment = txt
}
}
if p.Position != nil {
if p.Position.Compressed {
return p.parseCompressedData()
}
return p.parseData()
}
return nil
}
func (p *Packet) parseMicEData() error {
// APRS PROTOCOL REFERENCE 1.0.1 Chapter 10, page 42 in PDF
s := string(p.Payload)
// Mic-E Message Type
var mt []string
var t string
for i := 0; i < 3; i++ {
mc := miceCodes[rune(p.Dst.Call[i])][1]
if strings.HasSuffix(mc, "(Custom)") {
t = messageTypeCustom
} else if strings.HasSuffix(mc, "(Std)") {
t = messageTypeStd
}
mt = append(mt, string(mc[0]))
}
switch t {
case messageTypeStd:
mt = append(mt, " (Std)")
case messageTypeCustom:
mt = append(mt, " (Custom)")
}
p.Comment = miceMsgTypes[strings.Join(mt, "")]
// Speed and Course.
speed := float64(int(s[4])-28) * 10
dc := float64(int(s[5])-28) / 10
unit := float64(int(dc))
speed += unit
course := dc - unit
course += float64(int(s[6]) - 28)
if speed >= 800 {
speed -= 800
}
speed = 1.852 * speed // convert speed from knots to km/h
if course >= 400 {
course -= 400
}
// Symbol
p.Symbol[0] = s[7]
p.Symbol[1] = s[8]
p.Comment += fmt.Sprintf(" (%.fkm/h, %.f°)", speed, course)
// Check whether there's additional Telemetry or Status Text data.
if len(s) == 9 {
return nil
}
if s[9] == ',' || s[9] == '\x1d' {
// TODO: Parse telemetry data.
return nil
}
// Parse MicE Status Text data.
// TODO: Parse additional data in the Status Text data:
// - Actual (custom) text message
// - Maidenhead locator
// - Altitude
return nil
}
func (p *Packet) parseCompressedData() error {
// Parse csT bytes
if len(p.data) >= 3 {
// Compression Type (T) Byte Format
// Bit: 7 | 6 | 5 | 4 3 | 2 1 0 |
// -------+--------+---------+-------------+------------------+
// Unused | Unused | GPS Fix | NMEA Source | Origin |
// -------+--------+---------+-------------+------------------+
// Val: 0 | 0 | 0 = old | 00 = other | 000 = Compressed |
// | | 1 = cur | 01 = GLL | 001 = TNC BTex |
// | | | 10 = CGA | 010 = Software |
// | | | 11 = RMC | 011 = [tbd] |
// | | | | 100 = KPC3 |
// | | | | 101 = Pico |
// | | | | 110 = Other |
// | | | | 111 = Digipeater |
cb := p.data[0] - 33
sb := p.data[1] - 33
Tb := p.data[2] - 33
if p.data[0] != ' ' && ((Tb>>3)&3) == 2 {
// CGA sentence, NMEA Source = 0b10
d, err := base91Decode(p.data[0:2])
if err != nil {
return err
}
p.Altitude = math.Pow(1.002, float64(d))
p.Comment = p.data[3:]
} else if cb <= 89 { // !..z
// Course/Speed
p.Velocity.Course = float64(cb) * 4.0
p.Velocity.Speed = math.Pow(1.08, float64(sb)) - 1.0
} else if cb == 90 { // {
// Pre-Calculated Radio Range
p.Range = 2 * math.Pow(1.08, float64(sb))
}
}
return nil
}
func (p *Packet) parseData() error {
switch {
case len(p.data) >= 1 && p.data[0] == ' ':
p.Comment = p.data[1:]
case len(p.data) >= 7 && strings.HasPrefix(p.data, "PHG"):
p.PHG.PowerCode = p.data[3]
p.PHG.HeightCode = p.data[4]
p.PHG.GainCode = p.data[5]
p.PHG.DirectivityCode = p.data[6]
p.Range = math.Sqrt(2 * p.PHG.Height() * math.Sqrt((float64(p.PHG.Power())/10)*(float64(p.PHG.Gain())/2)))
p.Comment = p.data[7:]
case len(p.data) >= 7 && strings.HasPrefix(p.data, "RNG"):
var err error
p.Range, err = strconv.ParseFloat(p.data[3:7], 64)
if err != nil {
return err
}
p.Comment = p.data[7:]
case len(p.data) >= 7 && strings.HasPrefix(p.data, "DFS"):
p.DFS.StrengthCode = p.data[3]
p.DFS.HeightCode = p.data[4]
p.DFS.GainCode = p.data[5]
p.DFS.DirectivityCode = p.data[6]
p.Comment = p.data[7:]
}
return nil
}
func (p Payload) Time() (time.Time, error) {
switch p.Type() {
case '/', '@':
return ParseTime(string(p)[1:])
default:
return time.Time{}, nil
}
}

View File

@@ -1,329 +0,0 @@
package aprs
import (
"math"
"testing"
"time"
)
const earthRadius = float64(6378100)
func testTime(day, hour, min, sec int) *time.Time {
t := time.Date(0, 0, day, hour, min, sec, 0, time.UTC)
return &t
}
// haversin(θ) function
func testHaversin(theta float64) float64 {
return math.Pow(math.Sin(theta/2), 2)
}
func testDistance(a *Position, b *Position) float64 {
var (
lat1 = a.Latitude * math.Pi / 180
lng1 = a.Longitude * math.Pi / 180
lat2 = b.Latitude * math.Pi / 180
lng2 = b.Longitude * math.Pi / 180
h = testHaversin(lat2-lat1) + math.Cos(lat1)*math.Cos(lat2)*testHaversin(lng2-lng1)
)
return 2 * earthRadius * math.Asin(math.Sqrt(h))
}
func TestPacket(t *testing.T) {
var tests = []struct {
Raw string
Src Address
Dst Address
PathLen int
Type DataType
Position *Position
Velocity *Velocity
PHG *PowerHeightGain
DFS *OmniDFStrength
Altitude float64
Range float64
Time *time.Time
}{
{
Raw: "N0CALL>APRS,qAC:!4903.50N/07201.75W-Test 001234",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('!'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
},
{
Raw: "N0CALL>APRS,qAC:!4903.50N/07201.75W-Test /A=001234",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('!'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
},
{
Raw: "N0CALL>APRS,qAC:!49 . N/072 . W-",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('!'),
Position: &Position{Latitude: 49.0, Longitude: -72.000000},
},
/*
{
Raw: "N0CALL>APRS,qAC:TheNet X-1J4 (BFLD)!4903.50N/07201.75Wn",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('!'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
},
*/
{
Raw: "N0CALL>APRS,qAC:@092345/4903.50N/07201.75W>Test1234",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('@'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
Time: testTime(9, 23, 45, 0),
},
{
Raw: "N0CALL>APRS,qAC:=4903.50N/07201.75W#PHG5132",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('='),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
PHG: &PowerHeightGain{'5', '1', '3', '2'},
},
{
Raw: "N0CALL>APRS,qAC:=4903.50N/07201.75W 225/000g000t050r000p001...h00b10138dU2k",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('='),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
},
{
Raw: "N0CALL>APRS,qAC:@092345/4903.50N/07201.75W>088/036",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('@'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
Time: testTime(9, 23, 45, 0),
},
{
Raw: "N0CALL>APRS,qAC:@234517h4903.50N/07201.75W>PHG5132",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('@'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
Time: testTime(0, 23, 45, 17),
PHG: &PowerHeightGain{'5', '1', '3', '2'},
},
{
Raw: "N0CALL>APRS,qAC:@092345z4903.50N/07201.75W>RNG0050",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('@'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
Time: testTime(9, 23, 45, 0),
Range: 50,
},
{
Raw: "N0CALL>APRS,qAC:/234517h4903.50N/07201.75W>DFS2360",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('/'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
Time: testTime(0, 23, 45, 17),
DFS: &OmniDFStrength{'2', '3', '6', '0'},
},
{
Raw: "N0CALL>APRS,qAC:@092345z4903.50N/07201.75W 090/000g000t066r000p000...dUII",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('@'),
Position: &Position{Latitude: 49.058333, Longitude: -72.029167},
Time: testTime(9, 23, 45, 00),
},
{
Raw: "N0CALL>APRS,qAC:[IO91SX] 35 miles NNW of London",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('['),
Position: &Position{Latitude: 51.958333, Longitude: -0.500000},
},
{
Raw: "N0CALL>APRS,qAC:[IO91]",
Src: MustParseAddress("N0CALL"),
Dst: MustParseAddress("APRS"),
PathLen: 1,
Type: DataType('['),
Position: &Position{Latitude: 51.0, Longitude: -2.0},
},
{
Raw: "WX4GSO-9>APN382,qAR,WD4LSS:!3545.18NL07957.08W#PHG5680/R,W,85NC,NCn Mount Shepherd Piedmont Triad NC",
Src: MustParseAddress("WX4GSO-9"),
Dst: MustParseAddress("APN382"),
PathLen: 2,
Type: DataType('!'),
Position: &Position{Latitude: 35.753000, Longitude: -79.951333},
},
{
Raw: "PA4TW>APRS,qAS,PA4TW-2:=5216.28N/00510.05Er Remco, DMR:2041014, Soms QRV op PI2NOS",
Src: MustParseAddress("PA4TW"),
Dst: MustParseAddress("APRS"),
PathLen: 2,
Type: DataType('='),
Position: &Position{Latitude: 52.271333, Longitude: 5.167500},
},
{
Raw: "PA4TW-10>APRS,TCPIP*,qAC,FOURTH:=5220.18N/00453.25EIhttp://aprs.pa4tw.nl:14501/",
Src: MustParseAddress("PA4TW-10"),
Dst: MustParseAddress("APRS"),
PathLen: 3,
Type: DataType('='),
Position: &Position{Latitude: 52.336333, Longitude: 4.887500},
},
{
Raw: "N0CALL-1>T3PY1Y,KQ1L-8*,WIDE1,WIDE2-1,qAR:=/5L!!<*e7> sTComment",
Src: MustParseAddress("N0CALL-1"),
Dst: MustParseAddress("T3PY1Y"),
PathLen: 4,
Type: DataType('='),
Position: &Position{Latitude: 49.5, Longitude: -72.75},
},
{
Raw: "N0CALL-1>T3PY1Y,KQ1L-8*,WIDE1,WIDE2-1,qAR:=/5L!!<*e7>7P[",
Src: MustParseAddress("N0CALL-1"),
Dst: MustParseAddress("T3PY1Y"),
PathLen: 4,
Type: DataType('='),
Position: &Position{Latitude: 49.5, Longitude: -72.75},
Velocity: &Velocity{88.0, 36.2},
},
{
Raw: "N0CALL-1>T3PY1Y,KQ1L-8*,WIDE1,WIDE2-1,qAR:=/5L!!<*e7>{?!",
Src: MustParseAddress("N0CALL-1"),
Dst: MustParseAddress("T3PY1Y"),
PathLen: 4,
Type: DataType('='),
Position: &Position{Latitude: 49.5, Longitude: -72.75},
Range: 20.13,
},
{
Raw: "N0CALL-1>T3PY1Y,KQ1L-8*,WIDE1,WIDE2-1,qAR:=/5L!!<*e7OS]S",
Src: MustParseAddress("N0CALL-1"),
Dst: MustParseAddress("T3PY1Y"),
PathLen: 4,
Type: DataType('='),
Position: &Position{Latitude: 49.5, Longitude: -72.75},
Altitude: 10004,
},
{
Raw: "N0CALL-1>T3PY1Y,KQ1L-8*,WIDE1,WIDE2-1,qAR:@092345z/5L!!<*e7>{?!",
Src: MustParseAddress("N0CALL-1"),
Dst: MustParseAddress("T3PY1Y"),
PathLen: 4,
Type: '@',
Time: testTime(9, 23, 45, 0),
Position: &Position{Latitude: 49.5, Longitude: -72.75},
Range: 20.13,
},
{
Raw: "PE1ROG-8>APLC13,qAS,PE1ROG-2:!/49[<P'PC>8pQLoRa___MOBIL___TEST",
Src: MustParseAddress("PE1ROG-8"),
Dst: MustParseAddress("APLC13"),
PathLen: 2,
Type: '!',
},
{
Raw: "PE1ROG-1>APPM13,TCPIP*,qAC,T2PRT:>Running on Raspberry Pi with RTL dongle",
Src: MustParseAddress("PE1ROG-1"),
Dst: MustParseAddress("APPM13"),
PathLen: 3,
Type: '>',
},
{
Raw: "PD0MZ-4>APE32L,WIDE1-1,qAR,PD0MZ-10:>LoRa APRS Tracker",
Src: MustParseAddress("PD0MZ-4"),
Dst: MustParseAddress("APE32L"),
PathLen: 3,
Type: '>',
},
}
for _, test := range tests {
t.Run(test.Raw, func(t *testing.T) {
p, err := ParsePacket(test.Raw)
if err != nil {
t.Fatal(err)
}
if !p.Dst.EqualTo(test.Dst) {
t.Fatalf("expected dst %s, got %s", test.Dst, p.Dst)
}
if !p.Src.EqualTo(test.Src) {
t.Fatalf("expected src %s, got %s", test.Src, p.Src)
}
if len(p.Path) != test.PathLen {
t.Fatalf("expected path length %d, got %d: %v", test.PathLen, len(p.Path), p.Path)
}
if p.Payload.Type() != test.Type {
t.Fatalf("expected packet type %s [%c], got %s [%c]",
test.Type, test.Type,
p.Payload.Type(), p.Payload.Type())
}
if test.Altitude != 0 {
if p.Altitude == 0 {
t.Fatalf("expected altitude %f, got none", test.Altitude)
}
if math.Abs(test.Altitude-p.Altitude) > 1.0 {
t.Fatalf("expected altitude %f, got %f", test.Altitude, p.Altitude)
}
}
if test.Velocity != nil {
if p.Velocity.Course == 0 {
t.Fatalf("expected velocity %v, got none", test.Velocity)
}
if math.Abs(test.Velocity.Course-p.Velocity.Course) > 1.0 {
t.Fatalf("expected course %f, got %f", test.Velocity.Course, p.Velocity.Course)
}
if math.Abs(test.Velocity.Speed-p.Velocity.Speed) > 1.0 {
t.Fatalf("expected speed %f, got %f", test.Velocity.Speed, p.Velocity.Speed)
}
}
if test.Range != 0 {
if p.Range == 0 {
t.Fatalf("expected range %f, got none", test.Range)
}
if math.Abs(test.Range-p.Range) > 0.1 {
t.Fatalf("expected range %f, got %f", test.Range, p.Range)
}
}
if test.Position != nil {
if p.Position == nil {
t.Fatalf("expected position %s, got none", test.Position)
}
if d := testDistance(test.Position, p.Position); d > 1.0 {
t.Fatalf("expected position %s, got %s with distance %f meter", test.Position, p.Position, d)
}
}
if test.Time != nil {
if p.Time == nil {
t.Fatalf("expected time %s", test.Time)
}
if test.Time.Sub(*p.Time) > time.Minute {
t.Fatalf("expected time %s, got %s", test.Time, p.Time)
}
}
//t.Logf("%#+v", p)
})
}
}

View File

@@ -2,312 +2,504 @@ package aprs
import (
"fmt"
"io"
"math"
"regexp"
"strconv"
"strings"
"git.maze.io/go/ham/util/maidenhead"
"time"
)
var (
// Position ambiguity replacement
disambiguation = []int{2, 3, 5, 6, 12, 13, 15, 16}
miceCodes = map[rune]map[int]string{
'0': {0: "0", 1: "0", 2: "S", 3: "0", 4: "E"},
'1': {0: "1", 1: "0", 2: "S", 3: "0", 4: "E"},
'2': {0: "2", 1: "0", 2: "S", 3: "0", 4: "E"},
'3': {0: "3", 1: "0", 2: "S", 3: "0", 4: "E"},
'4': {0: "4", 1: "0", 2: "S", 3: "0", 4: "E"},
'5': {0: "5", 1: "0", 2: "S", 3: "0", 4: "E"},
'6': {0: "6", 1: "0", 2: "S", 3: "0", 4: "E"},
'7': {0: "7", 1: "0", 2: "S", 3: "0", 4: "E"},
'8': {0: "8", 1: "0", 2: "S", 3: "0", 4: "E"},
'9': {0: "9", 1: "0", 2: "S", 3: "0", 4: "E"},
'A': {0: "0", 1: "1 (Custom)"},
'B': {0: "1", 1: "1 (Custom)"},
'C': {0: "2", 1: "1 (Custom)"},
'D': {0: "3", 1: "1 (Custom)"},
'E': {0: "4", 1: "1 (Custom)"},
'F': {0: "5", 1: "1 (Custom)"},
'G': {0: "6", 1: "1 (Custom)"},
'H': {0: "7", 1: "1 (Custom)"},
'I': {0: "8", 1: "1 (Custom)"},
'J': {0: "9", 1: "1 (Custom)"},
'K': {0: " ", 1: "1 (Custom)"},
'L': {0: " ", 1: "0", 2: "S", 3: "0", 4: "E"},
'P': {0: "0", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'Q': {0: "1", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'R': {0: "2", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'S': {0: "3", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'T': {0: "4", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'U': {0: "5", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'V': {0: "6", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'W': {0: "7", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'X': {0: "8", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'Y': {0: "9", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
'Z': {0: " ", 1: "1 (Std)", 2: "N", 3: "100", 4: "W"},
}
miceMsgTypes = map[string]string{
"000": "Emergency",
"001 (Std)": "Priority",
"001 (Custom)": "Custom-6",
"010 (Std)": "Special",
"010 (Custom)": "Custom-5",
"011 (Std)": "Committed",
"011 (Custom)": "Custom-4",
"100 (Std)": "Returning",
"100 (Custom)": "Custom-3",
"101 (Std)": "In Service",
"101 (Custom)": "Custom-2",
"110 (Std)": "En Route",
"110 (Custom)": "Custom-1",
"111 (Std)": "Off Duty",
"111 (Custom)": "Custom-0",
}
)
const (
gridChars = "ABCDEFGHIJKLMNOPQRSTUVWX0123456789"
messageTypeStd = "Std"
messageTypeCustom = "Custom"
)
// Position contains GPS coordinates.
type Position struct {
Latitude float64 `json:"latitude"` // Degrees
Longitude float64 `json:"longitude"` // Degrees
Ambiguity int `json:"ambiguity,omitempty"`
Symbol Symbol `json:"symbol"`
Compressed bool `json:"compressed,omitempty"`
HasMessaging bool `json:"has_messaging"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Altitude float64 `json:"altitude,omitempty"` // Altitude (in meters)
Range float64 `json:"range,omitempty"` // Radio range (in meters)
IsCompressed bool `json:"is_compressed"`
CompressedGPSFix uint8 `json:"-"`
CompressedNMEASource uint8 `json:"-"`
CompressedOrigin uint8 `json:"-"`
Time time.Time `json:"time"`
Comment string `json:"comment"`
Symbol string `json:"symbol"`
Velocity *Velocity `json:"velocity,omitempty"` // Velocity encoded in the payload.
Wind *Wind `json:"wind,omitempty"` // Wind direction and speed.
PHG *PowerHeightGain `json:"phg,omitempty"`
DFS *OmniDFStrength `json:"dfs,omitempty"`
Weather *Weather `json:"weather,omitempty"`
Telemetry *Telemetry `json:"telemetry,omitempty"` // Telemetry data
}
func (pos Position) String() string {
if pos.Ambiguity == 0 {
return fmt.Sprintf("{%f, %f}", pos.Latitude, pos.Longitude)
}
return fmt.Sprintf("{%f, %f}, ambiguity=%d", pos.Latitude, pos.Longitude, pos.Ambiguity)
func (pos *Position) String() string {
return "position"
}
func ParseUncompressedPosition(s string) (Position, string, error) {
// APRS PROTOCOL REFERENCE 1.0.1 Chapter 8, page 32 (42 in PDF)
// Velocity details.
type Velocity struct {
Course int // degrees
Speed float64 // meters per second
}
pos := Position{}
// Wind details.
type Wind struct {
Direction float64 // degrees
Speed float64 // meters per second
}
if len(s) < 18 {
return pos, "", ErrInvalidPosition
// PowerHeightGain details.
type PowerHeightGain struct {
PowerCode byte
HeightCode byte
GainCode byte
DirectivityCode byte
}
// OmniDFStrength contains the omni-directional direction finding signal strength (for fox hunting).
type OmniDFStrength struct {
StrengthCode byte
HeightCode byte
GainCode byte
DirectivityCode byte
}
type positionDecoder struct{}
func (d positionDecoder) CanDecode(frame *Frame) bool {
switch frame.Raw.Type() {
case '!', '=', '/', '@': // compressed/uncompressed with/without messaging
return true
default:
return false
}
}
b := []byte(s)
for _, p := range disambiguation {
if b[p] == ' ' {
pos.Ambiguity++
b[p] = '0'
}
}
s = string(b)
func (d positionDecoder) Decode(frame *Frame) (data Data, err error) {
var (
err error
latDeg, latMin, latMinFrag uint64
lngDeg, lngMin, lngMinFrag uint64
latHemi, lngHemi byte
isSouth, isWest bool
raw = frame.Raw
kind = raw.Type()
hasTimestamp = kind == '@' || kind == '/'
hasMessaging = kind == '@' || kind == '='
content = string(raw[1:])
pos = &Position{
HasMessaging: hasMessaging,
}
)
if latDeg, err = strconv.ParseUint(s[0:2], 10, 8); err != nil {
return pos, "", err
if hasTimestamp {
if len(content) < 7 {
return nil, io.ErrUnexpectedEOF
}
if latMin, err = strconv.ParseUint(s[2:4], 10, 8); err != nil {
return pos, "", err
if pos.Time, _, err = parseTimestamp(content[:7]); err != nil {
return
}
if latMinFrag, err = strconv.ParseUint(s[5:7], 10, 8); err != nil {
return pos, "", err
}
latHemi = s[7]
pos.Symbol[0] = s[8]
if lngDeg, err = strconv.ParseUint(s[9:12], 10, 8); err != nil {
return pos, "", err
}
if lngMin, err = strconv.ParseUint(s[12:14], 10, 8); err != nil {
return pos, "", err
}
if lngMinFrag, err = strconv.ParseUint(s[15:17], 10, 8); err != nil {
return pos, "", err
}
lngHemi = s[17]
pos.Symbol[1] = s[18]
if latHemi == 'S' || latHemi == 's' {
isSouth = true
} else if latHemi != 'N' && latHemi != 'n' {
return pos, "", ErrInvalidPosition
content = content[7:]
}
if lngHemi == 'W' || lngHemi == 'w' {
isWest = true
} else if lngHemi != 'E' && lngHemi != 'e' {
return pos, "", ErrInvalidPosition
if err = pos.parsePositionAndComment(content); err != nil {
return
}
if latDeg > 89 || lngDeg > 179 {
return pos, "", ErrInvalidPosition
}
pos.Latitude = float64(latDeg) + float64(latMin)/60.0 + float64(latMinFrag)/6000.0
pos.Longitude = float64(lngDeg) + float64(lngMin)/60.0 + float64(lngMinFrag)/6000.0
if isSouth {
pos.Latitude = 0.0 - pos.Latitude
}
if isWest {
pos.Longitude = 0.0 - pos.Longitude
}
if pos.Symbol[1] >= 'a' || pos.Symbol[1] <= 'k' {
pos.Symbol[1] -= 32
}
if len(s) > 19 {
return pos, s[19:], nil
}
return pos, "", nil
}
func ParseCompressedPosition(s string) (Position, string, error) {
// APRS PROTOCOL REFERENCE 1.0.1 Chapter 9, page 36 (46 in PDF)
pos := Position{}
if len(s) < 10 {
return pos, "", ErrInvalidPosition
}
// Base-91 check
for _, c := range s[1:9] {
if c < 0x21 || c > 0x7b {
return pos, "", ErrInvalidPosition
}
}
var err error
var lat, lng int
if lat, err = base91Decode(s[1:5]); err != nil {
return pos, "", err
}
if lng, err = base91Decode(s[5:9]); err != nil {
return pos, "", err
}
pos.Latitude = 90.0 - float64(lat)/380926.0
pos.Longitude = -180.0 + float64(lng)/190463.0
pos.Compressed = true
return pos, s[10:], nil
}
func ParseMicE(s, dest string) (Position, error) {
// APRS PROTOCOL REFERENCE 1.0.1 Chapter 10, page 42 in PDF
pos := Position{}
if len(s) < 9 || len(dest) != 6 {
return pos, ErrInvalidPosition
}
ns := miceCodes[rune(dest[3])][2]
we := miceCodes[rune(dest[5])][4]
latF := fmt.Sprintf("%s%s", miceCodes[rune(dest[0])][0], miceCodes[rune(dest[1])][0])
latF = strings.Trim(latF, ". ")
latD, err := strconv.ParseFloat(latF, 64)
if err != nil {
return pos, ErrInvalidPosition
}
lonF := fmt.Sprintf("%s%s.%s%s", miceCodes[rune(dest[2])][0], miceCodes[rune(dest[3])][0], miceCodes[rune(dest[4])][0], miceCodes[rune(dest[5])][0])
lonF = strings.Trim(lonF, ". ")
latM, err := strconv.ParseFloat(lonF, 64)
if err != nil {
return pos, ErrInvalidPosition
}
if latM != 0 {
latD += latM / 60
}
if strings.ToUpper(ns) == "S" {
latD = -latD
}
lonOff := miceCodes[rune(dest[4])][3]
lonD := float64(s[1]) - 28
if lonOff == "100" {
lonD += 100
}
if lonD >= 180 && lonD < 190 {
lonD -= 80
}
if lonD >= 190 && lonD < 200 {
lonD -= 190
}
lonM := float64(s[2]) - 28
if lonM >= 60 {
lonM -= 60
}
// adding hundreth of minute then add minute as deg fraction
lonH := float64(s[3]) - 28
if lonH != 0 {
lonM += lonH / 100
}
if lonM != 0 {
lonD += lonM / 60
}
if strings.ToUpper(we) == "W" {
lonD = -lonD
}
pos.Latitude = latD
pos.Longitude = lonD
frame.Latitude = pos.Latitude
frame.Longitude = pos.Longitude
frame.Altitude = pos.Altitude
frame.Symbol = pos.Symbol
frame.Comment = pos.Comment
return pos, nil
}
func ParsePositionGrid(s string) (Position, string, error) {
var o int
for o = 0; o < len(s); o++ {
if strings.IndexByte(gridChars, s[o]) < 0 {
func (pos *Position) parsePositionAndComment(s string) (err error) {
var comment string
//log.Printf("parse position and comment %q: %t", s, isDigit(s[0]))
if isDigit(s[0]) {
// probably not compressed
if comment, err = pos.parseNotCompressedPosition(s); err != nil {
return
}
pos.IsCompressed = false
} else {
// probably compressed
if comment, err = pos.parseCompressedPosition(s); err != nil {
return
}
pos.IsCompressed = true
}
if len(comment) > 0 {
if err = pos.parseAltitudeWeatherAndExtension(comment); err != nil {
return
}
}
return
}
func (pos *Position) parseCompressedPosition(raw string) (comment string, err error) {
if len(raw) < 10 {
return "", fmt.Errorf("aprs: invalid compressed position string of length %d", len(raw))
}
pos.Symbol = string([]byte{raw[0], raw[9]})
var lat, lng int
if lat, err = base91Decode(raw[1:5]); err != nil {
return
}
if lng, err = base91Decode(raw[5:9]); err != nil {
return
}
pos.Latitude = 90.0 - float64(lat)/380926.0
pos.Longitude = -180.0 + float64(lng)/190463.0
pos.IsCompressed = true
comment = raw[13:]
if len(comment) >= 3 {
// Compression Type (T) Byte Format
//
// Bit: 7 | 6 | 5 | 4 3 | 2 1 0 |
// -------+--------+---------+-------------+------------------+
// Unused | Unused | GPS Fix | NMEA Source | Origin |
// -------+--------+---------+-------------+------------------+
// Val: 0 | 0 | 0 = old | 00 = other | 000 = Compressed |
// | | 1 = cur | 01 = GLL | 001 = TNC BTex |
// | | | 10 = CGA | 010 = Software |
// | | | 11 = RMC | 011 = [tbd] |
// | | | | 100 = KPC3 |
// | | | | 101 = Pico |
// | | | | 110 = Other |
// | | | | 111 = Digipeater |
var (
c = comment[0] - 33
s = comment[1] - 33
T = comment[2] - 33
)
if raw[10] == ' ' {
// Don't do any further processing
} else if ((T >> 3) & 3) == 2 {
// CGA sentence, NMEA Source = 0b10
var altitudeFeet int
if altitudeFeet, err = base91Decode(comment[:2]); err != nil {
return
}
pos.Altitude = feetToMeters(float64(altitudeFeet))
pos.CompressedGPSFix = (T >> 5) & 0x01
pos.CompressedNMEASource = (T >> 3) & 0x03
pos.CompressedOrigin = T & 0x07
comment = comment[3:]
} else if comment[0] >= '!' && comment[0] <= 'z' { // !..z
// Course/speed
pos.Velocity = &Velocity{
Course: int(c) * 4,
Speed: knotsToMetersPerSecond(math.Pow(1.08, float64(s))),
}
pos.CompressedGPSFix = (T >> 5) & 0x01
pos.CompressedNMEASource = (T >> 3) & 0x03
pos.CompressedOrigin = T & 0x07
comment = comment[3:]
} else if comment[0] == '{' { // {
// Precalculated range
pos.Range = milesToMeters(2 * math.Pow(1.08, float64(s)))
comment = comment[3:]
}
}
return
}
func (pos *Position) parseNotCompressedPosition(s string) (comment string, err error) {
//log.Printf("parse not compressed: %q", s)
if len(s) < 19 {
return "", fmt.Errorf("aprs: invalid not compressed position string of length %d", len(s))
}
pos.Symbol = string([]byte{s[8], s[18]})
if pos.Latitude, err = parseDMLatitude(s[:8]); err != nil {
return
}
if pos.Longitude, err = parseDMLongitude(s[9:18]); err != nil {
return
}
return s[19:], nil
}
var (
matchVelocity = regexp.MustCompile(`^[. 0-3][. 0-9]{2,2}/[. 0-9]{3}`)
matchPHG = regexp.MustCompile(`^PHG[0-9]{3,3}[0-8]`)
matchDFS = regexp.MustCompile(`^DFS[0-9]{3,3}[0-8]`)
matchRNG = regexp.MustCompile(`^RNG[0-9]{4}`)
matchAreaObject = regexp.MustCompile(``)
)
func (pos *Position) parseAltitudeWeatherAndExtension(s string) (err error) {
//log.Printf("parse altitude, weather and extensions %q", s)
var comment string
// Parse extensions
switch {
case matchVelocity.MatchString(s):
var course, speed int
if course, err = strconv.Atoi(fillZeros(s[:3])); err != nil {
return fmt.Errorf("invalid course: %v", err)
}
if speed, err = strconv.Atoi(fillZeros(s[4:7])); err != nil {
return fmt.Errorf("invalid speed: %v", err)
}
pos.Velocity = &Velocity{
Course: int(course),
Speed: knotsToMetersPerSecond(float64(speed)),
}
comment = s[7:]
if len(comment) > 2 && comment[0] == '/' && isDigit(comment[1]) && isDigit(comment[2]) {
var dir int
if dir, err = strconv.Atoi(fillZeros(comment[1:4])); err != nil {
return fmt.Errorf("invalid wind direction: %v", err)
}
if speed, err = strconv.Atoi(fillZeros(comment[5:8])); err != nil {
return fmt.Errorf("invalid wind speed: %v", err)
}
pos.Wind = &Wind{
Direction: float64(dir),
Speed: knotsToMetersPerSecond(float64(speed)),
}
comment = comment[8:]
}
case matchPHG.MatchString(s):
comment = s[7:]
case matchDFS.MatchString(s):
comment = s[7:]
case matchRNG.MatchString(s):
comment = s[7:]
default:
comment = s
}
//log.Printf("after extensions: %q", comment)
if pos.Altitude, comment, err = parseAltitude(comment); err != nil {
return
}
//log.Printf("after altitude, before weather: %q", comment)
if pos.Weather, comment, err = parseWeather(comment); err != nil {
return
}
//log.Printf("after weather, before telemetry: %q", comment)
if pos.Telemetry, comment, err = parseBase91Telemetry(comment); err != nil {
return
}
pos.Comment = comment
return
}
func parseAltitude(s string) (altitude float64, comment string, err error) {
const altitudeMarker = "/A="
if i := strings.Index(s, altitudeMarker); i >= 0 {
//log.Printf("parse altitude marker: %q", s[i+3:])
var feet int
if feet, err = strconv.Atoi(strings.TrimSpace(s[i+3 : i+3+6])); err != nil {
return 0, "", fmt.Errorf("aprs: invalid altitude: %v", err)
}
return feetToMeters(float64(feet)), s[:i] + s[i+3+6:], nil
}
return 0, s, nil
}
var weatherSymbolSize = map[byte]int{
'g': 3, // peak wind speed in the past 5 minutes, in mph
't': 3, // temperature in degrees Fahrenheit
'r': 3, // rainfall in hundredths of an inch
'p': 3, // rainfall in hundredths of an inch
'P': 3, // rainfall in hundredths of an inch
'h': 2, // relative humidity in %
'b': 5, // barometric pressure in tenths of millibars
'l': 3, // luminosity (in Watts per square meter) 1000 and above
'L': 3, // luminosity (in Watts per square meter) 999 and below
's': 3, // snowfall in the last 24 hours in inches
'#': 3, // raw rain counter
}
// Weather report.
type Weather struct {
WindGust float64 `json:"windGust"` // wind gust in m/s
Temperature float64 `json:"temperature"` // temperature (in degrees C)
Rain1h float64 `json:"rain1h"` // rain in the last hour (in mm)
Rain24h float64 `json:"rain24h"` // rain in the last day (in mm)
RainSinceMidnight float64 `json:"rainSinceMidnight"` // rain since midnight (in mm)
RainRaw int `json:"rainRaw"` // rain raw counter
Humidity float64 `json:"humidity"` // relative humidity (in %)
Pressure float64 `json:"pressure"` // pressure (in mBar)
Luminosity float64 `json:"luminosity"` // luminosity (in W/m^2)
Snowfall float64 `json:"snowfall"` // snowfall (in cm/day)
}
func parseWeather(s string) (weather *Weather, comment string, err error) {
comment = s
for len(comment) > 0 {
if size, ok := weatherSymbolSize[comment[0]]; ok {
if len(comment[1:]) < size {
return nil, "", fmt.Errorf("aprs: not enough characters to encode weather symbol %c (%d < %d)", comment[0], len(comment[1:]), size)
}
var value float64
if value, err = strconv.ParseFloat(comment[1:size+1], 64); err != nil {
// Something else that started with a weather symbol, perhaps a comment, stop parsing
err = nil
break
}
if weather == nil {
weather = new(Weather)
}
switch comment[0] {
case 'g':
weather.WindGust = value * 0.4470
case 't':
weather.Temperature = fahrenheitToCelcius(value)
case 'r':
weather.Rain1h = value * 0.254
case 'p':
weather.Rain24h = value * 0.254
case 'P':
weather.RainSinceMidnight = value * 0.254
case 'h':
weather.Humidity = value
case 'b':
weather.Pressure = value / 10
case 'l':
weather.Luminosity = value + 1000
case 'L':
weather.Luminosity = value
case 's':
weather.Snowfall = value * 2.54
case '#':
weather.RainRaw = int(value)
}
comment = comment[1+size:]
} else {
break
}
}
pos := Position{}
if o == 2 || o == 4 || o == 6 || o == 8 {
p, err := maidenhead.ParseLocator(s[:o])
if err != nil {
return pos, "", err
}
pos.Latitude = p.Latitude
pos.Longitude = p.Longitude
}
var txt string
if o < len(s) {
txt = s[o+1:]
}
return pos, txt, nil
return
}
func ParsePosition(s string, compressed bool) (Position, string, error) {
if compressed {
return ParseCompressedPosition(s)
}
return ParseUncompressedPosition(s)
type Telemetry struct {
ID int
Analog []int
Digital []bool
}
func ParsePositionBoth(s string) (Position, string, error) {
pos, txt, err := ParseUncompressedPosition(s)
if err != nil {
return ParseCompressedPosition(s)
var matchTelemetry = regexp.MustCompile(`\|([!-{]{4,14})\|`)
func parseBase91Telemetry(s string) (telemetry *Telemetry, comment string, err error) {
var i int
if i = strings.IndexByte(s, '|'); i == -1 {
return nil, s, nil
}
return pos, txt, err
var sequence string
comment, sequence = s[:i], s[i+1:]
if i = strings.IndexByte(sequence, '|'); i < 1 {
// no closing | found, return as comment
return nil, s, nil
}
if sequence, comment = sequence[:i], comment+sequence[i+1:]; len(sequence)%2 != 0 {
// uneven number of sequence elements,
return nil, s, nil
}
telemetry = new(Telemetry)
if telemetry.ID, err = base91Decode(sequence[:2]); err != nil {
// it wasn't base-91 encoded telemetry, return data as comment
return nil, s, nil
}
var values []int
for i = 2; i < len(sequence); i += 2 {
var value int
if value, err = base91Decode(sequence[i : i+2]); err != nil {
// it wasn't base-91 encoded telemetry, return data as comment
return nil, s, nil
}
values = append(values, value)
}
if len(values) > 5 {
for i = 0; i < 8; i++ {
telemetry.Digital = append(telemetry.Digital, (values[5]&1) == 1)
values[5] >>= 1
}
values = values[:5]
}
telemetry.Analog = values
return
}
func parseDMLatitude(s string) (v float64, err error) {
if len(s) != 8 || s[4] != '.' || !(s[7] == 'N' || s[7] == 'S') {
return 0, fmt.Errorf("aprs: invalid latitude %q", s)
}
s = strings.Replace(s, " ", "0", -1) // position ambiguity
var (
degs, mins, minFrags int
south = s[7] == 'S'
)
if degs, err = strconv.Atoi(s[:2]); err != nil {
return
}
if mins, err = strconv.Atoi(s[2:4]); err != nil {
return
}
if minFrags, err = strconv.Atoi(s[5:7]); err != nil {
return
}
v = float64(degs) + float64(mins)/60 + float64(minFrags)/6000
if south {
return -v, nil
}
return v, nil
}
func parseDMLongitude(s string) (v float64, err error) {
if len(s) != 9 || s[5] != '.' || !(s[8] == 'W' || s[8] == 'E') {
return 0, fmt.Errorf("aprs: invalid longitude %q", s)
}
s = strings.Replace(s, " ", "0", -1) // position ambiguity
var (
degs, mins, minFrags int
east = s[8] == 'E'
)
if degs, err = strconv.Atoi(s[:3]); err != nil {
return
}
if mins, err = strconv.Atoi(s[3:5]); err != nil {
return
}
if minFrags, err = strconv.Atoi(s[6:8]); err != nil {
return
}
v = float64(degs) + float64(mins)/60 + float64(minFrags)/6000
if east {
return v, nil
}
return -v, nil
}

View File

@@ -0,0 +1,433 @@
package aprs
import (
"reflect"
"testing"
"time"
)
func TestParseBase91Telemetry(t *testing.T) {
tests := []struct {
Test string
Telemetry *Telemetry
Comment string
}{
{
"|!!!!|",
&Telemetry{Analog: []int{0}},
"",
},
{
"|ss11|",
&Telemetry{ID: 7544, Analog: []int{1472}},
"",
},
{
"|ss112233|",
&Telemetry{ID: 7544, Analog: []int{1472, 1564, 1656}},
"",
},
{
"|ss1122334455!\"|",
&Telemetry{ID: 7544, Analog: []int{1472, 1564, 1656, 1748, 1840}, Digital: []bool{true, false, false, false, false, false, false, false}},
"",
},
{
"|ss11|73's de N0CALL",
&Telemetry{ID: 7544, Analog: []int{1472}},
"73's de N0CALL",
},
{
"`pZ3l-B]/'\"6{}|!9'X$u|!wr8!|3",
&Telemetry{ID: 24, Analog: []int{601, 357}},
"`pZ3l-B]/'\"6{}!wr8!|3",
},
{
"!/0%3RTh<6>dS_http://aprs.fi/|\"p%T'.ag|",
&Telemetry{ID: 170, Analog: []int{415, 559, 5894}},
"!/0%3RTh<6>dS_http://aprs.fi/",
},
{
"!6304.03NN02739.63E#PHG26303/Siilinjarvi|\"p%T'.agff|",
&Telemetry{ID: 170, Analog: []int{415, 559, 5894, 6348}},
"!6304.03NN02739.63E#PHG26303/Siilinjarvi",
},
}
for _, test := range tests {
t.Run(test.Test, func(t *testing.T) {
v, comment, err := parseBase91Telemetry(test.Test)
if err != nil {
t.Fatal(err)
}
if (v == nil) != (test.Telemetry == nil) {
t.Fatalf("expected telemetry %#+v, got %#+v", test.Telemetry, v)
}
if test.Telemetry != nil {
if v.ID != test.Telemetry.ID {
t.Errorf("expected id %d, got %d", test.Telemetry.ID, v.ID)
}
if !reflect.DeepEqual(v.Analog, test.Telemetry.Analog) {
t.Errorf("expected analog values %d, got %d", test.Telemetry.Analog, v.Analog)
}
if !reflect.DeepEqual(v.Digital, test.Telemetry.Digital) {
t.Errorf("expected digital values %t, got %t", test.Telemetry.Digital, v.Digital)
}
}
if comment != test.Comment {
t.Errorf("expected comment %q, got %q", test.Comment, comment)
}
})
}
}
func TestParsePosition(t *testing.T) {
localTime := time.Now()
tests := []struct {
Name string
Raw Raw
Want *Position
}{
{
"no timestamp, no APRS messaging, with comment",
"!4903.50N/07201.75W-Test 001234",
&Position{
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/-",
Comment: "Test 001234",
},
},
{
"no timestamp, no APRS messaging, altitude = 1234 ft",
"!4903.50N/07201.75W-Test /A=001234",
&Position{
Latitude: 49.058333,
Longitude: -72.029167,
Altitude: 376.1232,
Symbol: "/-",
Comment: "Test ",
},
},
{
"no timestamp, no APRS messaging, location to nearest degree",
"!49 . N/072 . W-",
&Position{
Latitude: 49,
Longitude: -72,
Symbol: "/-",
},
},
{
"with timestamp, no APRS messaging, zulu time, with comment",
"/092345z4903.50N/07201.75W>Test1234",
&Position{
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/>",
Comment: "Test1234",
Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC),
},
},
{
"with timestamp, with APRS messaging, local time, with comment",
"@092345/4903.50N/07201.75W>Test1234",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/>",
Comment: "Test1234",
Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, localTime.Location()),
},
},
{
"no timestamp, with APRS messaging, with PHG",
"=4903.50N/07201.75W#PHG5132",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/#",
},
},
{
"weather report",
"=4903.50N/07201.75W 225/000g000t050r000p001h00b10138dU2k",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/ ",
Comment: "dU2k",
Velocity: &Velocity{Course: 225},
Weather: &Weather{Temperature: 10, Rain24h: 0.254, Pressure: 1013.8},
},
},
{
"with timestamp, with APRS messaging, local time, course/speed",
"@092345/4903.50N/07201.75W>088/036",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/>",
Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, localTime.Location()),
Velocity: &Velocity{Course: 88, Speed: 18.519999984000002},
},
},
{
"with timestamp, with APRS messaging, hours/mins/secs time, PHG",
"@234517h4903.50N/07201.75W>PHG5132",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/>",
Time: time.Date(0, 0, 0, 23, 45, 17, 0, time.UTC),
},
},
{
"with timestamp, with APRS messaging, zulu time, radio range",
"@092345z4903.50N/07201.75W>RNG0050",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/>",
Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC),
},
},
{
"with timestamp, hours/mins/secs time, DF, no APRS messaging",
"/234517h4903.50N/07201.75W>DFS2360",
&Position{
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/>",
Time: time.Date(0, 0, 0, 23, 45, 17, 0, localTime.Location()),
},
},
{
"with timestamp, APRS messaging, zulu time, weather report",
"@092345z4903.50N/07201.75W 090/000g000t066r000p000dUII",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/ ",
Comment: "dUII",
Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC),
Velocity: &Velocity{Course: 90},
Weather: &Weather{Temperature: 18.88888888888889},
},
},
{
"no timestamp, course/speed/bearing/NRQ, with APRS messaging, DF station moving",
"=4903.50N/07201.75W\\088/036/270/729",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/\\",
Velocity: &Velocity{Course: 88, Speed: knotsToMetersPerSecond(36)},
Wind: &Wind{Direction: 270, Speed: knotsToMetersPerSecond(729)},
},
},
{
"no timestamp, course/speed/bearing/NRQ, with APRS messaging, DF station fixed",
"=4903.50N/07201.75W\\000/036/270/729",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/\\",
Velocity: &Velocity{Course: 0, Speed: knotsToMetersPerSecond(36)},
Wind: &Wind{Direction: 270, Speed: knotsToMetersPerSecond(729)},
},
},
{
"with timestamp, course/speed/bearing/NRQ, with APRS messaging",
"@092345z4903.50N/07201.75W\\088/036/270/729",
&Position{
HasMessaging: true,
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/\\",
Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC),
Velocity: &Velocity{Course: 88, Speed: knotsToMetersPerSecond(36)},
Wind: &Wind{Direction: 270, Speed: knotsToMetersPerSecond(729)},
},
},
{
"with timestamp, bearing/NRQ, no course/speed, no APRS messaging",
"/092345z4903.50N/07201.75W\\000/000/270/729",
&Position{
Latitude: 49.058333,
Longitude: -72.029167,
Symbol: "/\\",
Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC),
Velocity: &Velocity{},
Wind: &Wind{Direction: 270, Speed: knotsToMetersPerSecond(729)},
},
},
// compressed positions:
{
"compressed, with APRS messaging",
"=/5L!!<*e7> sTComment",
&Position{
HasMessaging: true,
Latitude: 49.5,
Longitude: -72.750004,
Symbol: "/>",
Comment: "Comment",
},
},
{
"compressed, with APRS messaging, RMC sentence, with course/speed",
"=/5L!!<*e7>7P[",
&Position{
HasMessaging: true,
Latitude: 49.5,
Longitude: -72.750004,
Symbol: "/>",
},
},
{
"compressed, with APRS messaging, with radio range",
"=/5L!!<*e7>{?!",
&Position{
HasMessaging: true,
Latitude: 49.5,
Longitude: -72.750004,
Symbol: "/>",
},
},
{
"compressed, with APRS messaging, GGA sentence, altitude",
"=/5L!!<*e7OS]S",
&Position{
HasMessaging: true,
Latitude: 49.5,
Longitude: -72.750004,
Symbol: "/O",
},
},
{
"compressed, with APRS messaging, timestamp, radio range",
"@092345z/5L!!<*e7>{?!",
&Position{
HasMessaging: true,
Latitude: 49.5,
Longitude: -72.750004,
Symbol: "/>",
Time: time.Date(localTime.Year(), localTime.Month(), 9, 23, 45, 0, 0, time.UTC),
},
},
}
var decoder positionDecoder
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
frame := &Frame{Raw: test.Raw}
if !decoder.CanDecode(frame) {
t.Fatalf("%T can't decode %q", decoder, test.Raw)
}
v, err := decoder.Decode(frame)
if err != nil {
t.Fatal(err)
}
testComparePosition(t, test.Want, v)
})
}
}
func testComparePosition(t *testing.T, want *Position, value any) {
t.Helper()
p, ok := value.(*Position)
if !ok {
t.Fatalf("expected data to be a %T, got %T", want, value)
return
}
if p.HasMessaging != want.HasMessaging {
t.Errorf("expected to have messaging: %t, got %t", want.HasMessaging, p.HasMessaging)
}
if !testAlmostEqual(p.Latitude, want.Latitude) {
t.Errorf("expected latitude %f, got %f", want.Latitude, p.Latitude)
}
if !testAlmostEqual(p.Longitude, want.Longitude) {
t.Errorf("expected longitude %f, got %f", want.Longitude, p.Longitude)
}
if !testAlmostEqual(p.Altitude, want.Altitude) {
t.Errorf("expected altitude %f, got %f", want.Altitude, p.Altitude)
}
if p.Symbol != want.Symbol {
t.Errorf("expected symbol %q, got %q", want.Symbol, p.Symbol)
}
if p.Comment != want.Comment {
t.Errorf("expected comment %q, got %q", want.Comment, p.Comment)
}
if want.Time.Equal(time.Time{}) {
if !p.Time.Equal(time.Time{}) {
t.Errorf("expected no time stamp, got %s", p.Time)
}
} else if want.Time.Year() == -1 {
if p.Time.Hour() != want.Time.Hour() ||
p.Time.Minute() != want.Time.Minute() ||
p.Time.Second() != want.Time.Second() {
t.Errorf("expected time %s, got %s", want.Time.Format("15:04:05"), p.Time.Format("15:04:05"))
}
} else if !want.Time.Equal(p.Time) {
t.Errorf("expected time %s, got %s", want.Time, p.Time)
}
if want.Velocity != nil {
if p.Velocity == nil {
t.Errorf("expected velocity, got none")
} else if !reflect.DeepEqual(p.Velocity, want.Velocity) {
t.Errorf("expected velocity %#+v, got %#+v", want.Velocity, p.Velocity)
}
} else if p.Velocity != nil {
t.Errorf("expected no velocity, got %#+v", p.Velocity)
}
if want.Wind != nil {
if p.Wind == nil {
t.Errorf("expected wind, got none")
} else if !reflect.DeepEqual(p.Wind, want.Wind) {
t.Errorf("expected wind %#+v, got %#+v", want.Wind, p.Wind)
}
} else if p.Wind != nil {
t.Errorf("expected no wind, got %#+v", p.Wind)
}
if want.Telemetry != nil {
if p.Telemetry == nil {
t.Errorf("expected telemetry, got none")
} else if !reflect.DeepEqual(p.Telemetry, want.Telemetry) {
t.Errorf("expected telemetry %#+v, got %#+v", want.Telemetry, p.Telemetry)
}
} else if p.Telemetry != nil {
t.Errorf("expected no telemetry, got %#+v", p.Telemetry)
}
if want.Weather != nil {
if p.Weather == nil {
t.Errorf("expected weather, got none")
} else if !reflect.DeepEqual(p.Weather, want.Weather) {
t.Errorf("expected weather %#+v, got %#+v", want.Weather, p.Weather)
}
} else if p.Weather != nil {
t.Errorf("expected no weather, got %#+v", p.Weather)
}
}

50
protocol/aprs/query.go Normal file
View File

@@ -0,0 +1,50 @@
package aprs
import (
"strconv"
"strings"
)
type Query struct {
Type string `json:"type"`
Latitude float64
Longitude float64
Radius float64 // radius in meters
}
func (q Query) String() string {
return q.Type
}
type queryDecoder struct{}
func (queryDecoder) CanDecode(frame *Frame) bool {
return len(frame.Raw) >= 3 && frame.Raw.Type() == '?'
}
func (queryDecoder) Decode(frame *Frame) (data Data, err error) {
var (
kind = string(frame.Raw[1:])
args string
i int
)
if i = strings.IndexByte(kind, '?'); i == -1 {
return &Query{
Type: kind,
}, nil
} else {
kind, args = kind[:i], kind[i+1:]
}
query := &Query{Type: kind}
if part := strings.SplitN(strings.TrimSpace(args), ",", 3); len(part) == 3 {
var radius int
query.Latitude, _ = strconv.ParseFloat(part[0], 64)
query.Longitude, _ = strconv.ParseFloat(part[1], 64)
radius, _ = strconv.Atoi(part[2])
query.Radius = milesToMeters(float64(radius))
}
return query, nil
}

View File

@@ -0,0 +1,69 @@
package aprs
import "testing"
func TestParseQuery(t *testing.T) {
tests := []struct {
Name string
Raw Raw
Want *Query
}{
{
"general query",
"?APRS?",
&Query{
Type: "APRS",
},
},
{
"general query for stations within a target footprint of radius 200 miles",
"?APRS? 34.02,-117.15,0200",
&Query{
Type: "APRS",
Latitude: 34.02,
Longitude: -117.15,
Radius: milesToMeters(200),
},
},
}
var decoder queryDecoder
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
frame := &Frame{Raw: test.Raw}
if !decoder.CanDecode(frame) {
t.Fatalf("%T can't decode %q", decoder, test.Raw)
}
v, err := decoder.Decode(frame)
if err != nil {
t.Fatal(err)
}
testCompareQuery(t, test.Want, v)
})
}
}
func testCompareQuery(t *testing.T, want *Query, value any) {
t.Helper()
test, ok := value.(*Query)
if !ok {
t.Fatalf("expected data to be a %T, got %T", want, value)
return
}
if test.Type != want.Type {
t.Errorf("expected type %q, got %q", want.Type, test.Type)
}
if !testAlmostEqual(test.Latitude, want.Latitude) {
t.Errorf("expected latitude %f, got %f", want.Latitude, test.Latitude)
}
if !testAlmostEqual(test.Longitude, want.Longitude) {
t.Errorf("expected longitude %f, got %f", want.Longitude, test.Longitude)
}
if !testAlmostEqual(test.Radius, want.Radius) {
t.Errorf("expected radius %f, got %f", want.Radius, test.Radius)
}
}

92
protocol/aprs/status.go Normal file
View File

@@ -0,0 +1,92 @@
package aprs
import (
"strings"
"time"
"git.maze.io/go/ham/util/maidenhead"
)
type Status struct {
Time time.Time
Latitude float64
Longitude float64
BeamHeading int
ERP int
Symbol string
Text string
}
func (s Status) String() string {
return s.Text
}
type statusReportDecoder struct{}
func (statusReportDecoder) CanDecode(frame Frame) bool {
return frame.Raw.Type() == '>'
}
func (statusReportDecoder) Decode(frame Frame) (data Data, err error) {
var (
text = string(frame.Raw[1:])
status = new(Status)
)
if hasTimestamp(text) {
if status.Time, text, err = parseTimestamp(text); err != nil {
return
}
}
if len(text) > 3 && text[len(text)-3] == '^' {
var (
h = text[len(text)-2]
p = text[len(text)-1]
)
if h >= '0' && h <= '9' {
status.BeamHeading = int(h-'0') * 10
} else if h >= 'A' && h <= 'Z' {
status.BeamHeading = 100 + int(h-'A')*10
}
if p >= '0' && p <= 'Z' {
status.ERP = int(p-'0') * int(p-'0') * 10
}
text = text[:len(text)-3]
}
here := text
if i := strings.IndexByte(here, ' '); i != -1 {
here = here[:i]
}
switch len(here) {
case 6:
var point maidenhead.Point
if point, err = maidenhead.ParseLocator(here[:4]); err != nil {
return
}
status.Latitude = point.Latitude
status.Longitude = point.Longitude
status.Symbol = here[4:]
if len(text) > 6 {
text = text[7:]
} else {
text = text[6:]
}
case 8:
var point maidenhead.Point
if point, err = maidenhead.ParseLocator(here[:6]); err != nil {
return
}
status.Latitude = point.Latitude
status.Longitude = point.Longitude
status.Symbol = here[6:]
if len(text) > 8 {
text = text[9:]
} else {
text = text[8:]
}
}
status.Text = text
return status, nil
}

View File

@@ -0,0 +1,120 @@
package aprs
import "testing"
func TestParseStatusReport(t *testing.T) {
tests := []struct {
Name string
Raw Raw
Want *Status
}{
{
"without timestamp",
">Net Control Center",
&Status{
Text: "Net Control Center",
},
},
{
"with timestamp",
">092345zNet Control Center",
&Status{
Text: "Net Control Center",
},
},
{
"with beam heading and erp",
">Test^B7",
&Status{
Text: "Test",
BeamHeading: 110,
ERP: 490,
},
},
{
"with maidenhead locator",
">IO91SX/G",
&Status{
Latitude: 51.958333,
Longitude: -0.5,
Symbol: "/G",
},
},
{
"with short maidenhead locator",
">IO91/G",
&Status{
Latitude: 51,
Longitude: -2,
Symbol: "/G",
},
},
{
"with maidenhead locator and comment",
">IO91SX/- My house",
&Status{
Latitude: 51.958333,
Longitude: -0.5,
Symbol: "/-",
Text: "My house",
},
},
{
"with maidenhead locator and beam heading",
">IO91SX/- ^B7",
&Status{
Latitude: 51.958333,
Longitude: -0.5,
Symbol: "/-",
BeamHeading: 110,
ERP: 490,
},
},
}
var decoder statusReportDecoder
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
frame := Frame{Raw: test.Raw}
if !decoder.CanDecode(frame) {
t.Fatalf("%T can't decode %q", decoder, test.Raw)
}
v, err := decoder.Decode(frame)
if err != nil {
t.Fatal(err)
}
testCompareStatusReport(t, test.Want, v)
})
}
}
func testCompareStatusReport(t *testing.T, want *Status, value any) {
t.Helper()
test, ok := value.(*Status)
if !ok {
t.Fatalf("expected data to be a %T, got %T", want, value)
return
}
if !testAlmostEqual(test.Latitude, want.Latitude) {
t.Errorf("expected latitude %f, got %f", want.Latitude, test.Latitude)
}
if !testAlmostEqual(test.Longitude, want.Longitude) {
t.Errorf("expected longitude %f, got %f", want.Longitude, test.Longitude)
}
if test.BeamHeading != want.BeamHeading {
t.Errorf("expected beam heading %d, got %d", want.BeamHeading, test.BeamHeading)
}
if test.ERP != want.ERP {
t.Errorf("expected ERP %dW, got %dW", want.ERP, test.ERP)
}
if test.Symbol != want.Symbol {
t.Errorf("expected symbol %q, got %q", want.Symbol, test.Symbol)
}
if test.Text != want.Text {
t.Errorf("expected text %q, got %q", want.Text, test.Text)
}
}

View File

@@ -1,266 +0,0 @@
package aprs
import (
"encoding/json"
"fmt"
)
var emptySymbol Symbol
type Symbol [2]byte
func (s Symbol) IsPrimaryTable() bool { return s[0] != '\\' }
func (s Symbol) MarshalJSON() ([]byte, error) {
if s == emptySymbol {
return json.Marshal("")
}
return json.Marshal(string(s[:]))
}
func (s Symbol) get(idx int) (string, error) {
var m map[byte]map[int]string
if s.IsPrimaryTable() {
m = primarySymbol
} else {
m = alternateSymbol
}
n, ok := m[s[1]]
if !ok {
return "", fmt.Errorf("unknown symbol %x", s[1])
}
if i, ok := n[idx]; ok {
return i, nil
}
return "", fmt.Errorf("symbol doesn't have requested index: %v", n)
}
func (s Symbol) String() string {
hr, err := s.get(1)
if err != nil {
return err.Error()
}
return hr
}
func (s Symbol) SSID() (string, error) {
return s.get(2)
}
func (s Symbol) Emoji() (string, error) {
return s.get(3)
}
func IsValidCompressedSymTable(c byte) bool {
return c == '/' ||
c == '\\' ||
(c >= 0x41 && c <= 0x5a) ||
(c >= 0x61 && c <= 0x6a)
}
func IsValidUncompressedSymTable(c byte) bool {
return c == '/' ||
c == '\\' ||
(c >= 0x41 && c <= 0x5a) ||
(c >= 0x30 && c <= 0x39)
}
var (
// Source: http://www.aprs.org/symbols/symbolsX.txt
// 0: XYZ code
// 1: Human readable
// 2: SSID
// 3: Emoji
primarySymbol = map[byte]map[int]string{
'!': {0: "BB", 1: "Police, Sheriff", 3: ":cop:"},
'"': {0: "BC", 1: "reserved"},
'#': {0: "BD", 1: "Digi"},
'$': {0: "BE", 1: "Phone", 3: ":phone:"},
'%': {0: "BF", 1: "DX Cluster"},
'&': {0: "BG", 1: "HF Gateway"},
'\'': {0: "BH", 1: "Small Aircraft", 2: "11", 3: ":airplane:"},
'(': {0: "BI", 1: "Mobile Satellite Station", 3: ":satellite:"},
')': {0: "BJ", 1: "Wheelchair", 3: ":wheelchair:"},
'*': {0: "BK", 1: "Snowmobile"},
'+': {0: "BL", 1: "Red Cross"},
',': {0: "BM", 1: "Boy Scout"},
'-': {0: "BN", 1: "House QTH (VHF)"},
'.': {0: "BO", 1: "X"},
'/': {0: "BP", 1: "Red Dot"},
'0': {0: "P0", 1: "Circle (0)"},
'1': {0: "P1", 1: "Circle (1)"},
'2': {0: "P2", 1: "Circle (2)"},
'3': {0: "P3", 1: "Circle (3)"},
'4': {0: "P4", 1: "Circle (4)"},
'5': {0: "P5", 1: "Circle (5)"},
'6': {0: "P6", 1: "Circle (6)"},
'7': {0: "P7", 1: "Circle (7)"},
'8': {0: "P8", 1: "Circle (8)"},
'9': {0: "P9", 1: "Circle (9)"},
':': {0: "MR", 1: "Fire", 3: ":fire:"},
';': {0: "MS", 1: "Campground", 3: ":tent:"},
'<': {0: "MT", 1: "Motorcycle", 2: "10", 3: ":bike:"},
'=': {0: "MU", 1: "Railroad Engine", 3: ":train:"},
'>': {0: "MV", 1: "Car", 2: "9", 3: ":car:"},
'?': {0: "MW", 1: "File Server"},
'@': {0: "MX", 1: "HC Future"},
'A': {0: "PA", 1: "Aid Station", 3: ":hospital:"},
'B': {0: "PB", 1: "BBS or PBBS"},
'C': {0: "PC", 1: "Canoe"},
'D': {0: "PD"},
'E': {0: "PE", 1: "Eyeball"},
'F': {0: "PF", 1: "Tractor", 3: ":tractor:"},
'G': {0: "PG", 1: "Grid Square"},
'H': {0: "PH", 1: "Hotel", 3: ":hotel:"},
'I': {0: "PI", 1: "TCP/IP"},
'J': {0: "PJ"},
'K': {0: "PK", 1: "School", 3: ":school:"},
'L': {0: "PL", 1: "PC User", 3: ":computer:"},
'M': {0: "PM", 1: "MacAPRS", 3: ":computer:"},
'N': {0: "PN", 1: "NTS Station"},
'O': {0: "PO", 1: "Balloon", 2: "11", 3: ":airplane:"},
'P': {0: "PP", 1: "Police", 3: ":police_car:"},
'Q': {0: "PQ"},
'R': {0: "PR", 1: "Recreational Vehicle", 2: "13", 3: ":car:"},
'S': {0: "PS", 1: "Shuttle"},
'T': {0: "PT", 1: "SSTV"},
'U': {0: "PU", 1: "Bus", 2: "2", 3: ":bus:"},
'V': {0: "PV", 1: "ATV"},
'W': {0: "PW", 1: "National WX Service Site"},
'X': {0: "PX", 1: "Helo", 2: "6"},
'Y': {0: "PY", 1: "Yacht", 2: "5", 3: ":sailboat:"},
'Z': {0: "PZ", 1: "WinAPRS", 3: ":computer:"},
'[': {0: "HS", 1: "Human/Person", 2: "7", 3: ":running:"},
'\\': {0: "HT", 1: "DF Station"},
']': {0: "HU", 1: "Post Office", 3: ":post_office:"},
'^': {0: "HV", 1: "Large Aircraft", 3: ":airplane:"},
'_': {0: "HW", 1: "Weather Station", 3: ":cloud:"},
'`': {0: "HX", 1: "Dish Antenna", 3: ":satellite:"},
'a': {0: "LA", 1: "Ambulance", 2: "1", 3: ":ambulance:"},
'b': {0: "LB", 1: "Bike", 2: "4", 3: ":bike:"},
'c': {0: "LC", 1: "Incident Command Post"},
'd': {0: "LD", 1: "Fire Dept", 3: ":fire_engine:"},
'e': {0: "LE", 1: "Horse", 3: ":racehorse:"},
'f': {0: "LF", 1: "Fire Truck", 2: "3", 3: ":fire_engine:"},
'g': {0: "LG", 1: "Glider", 3: ":airplane:"},
'h': {0: "LH", 1: "Hospital", 3: ":hospital:"},
'i': {0: "LI", 1: "IOTA"},
'j': {0: "LJ", 1: "Jeep", 2: "12", 3: ":car:"},
'k': {0: "LK", 1: "Truck", 2: "14", 3: ":truck:"},
'l': {0: "LL", 1: "Laptop", 3: ":computer:"},
'm': {0: "LM", 1: "Mic-E Repeater"},
'n': {0: "LN", 1: "Node"},
'o': {0: "LO", 1: "EOC"},
'p': {0: "LP", 1: "Dog", 3: ":dog2:"},
'q': {0: "LQ", 1: "Grid SQ"},
'r': {0: "LR", 1: "Repeater"},
's': {0: "LS", 1: "Ship", 2: "8", 3: ":ship:"},
't': {0: "LT", 1: "Truck Stop"},
'u': {0: "LU", 1: "Truck (18 Wheeler)", 3: ":truck:"},
'v': {0: "LV", 1: "Van", 2: "15", 3: ":minibus:"},
'w': {0: "LW", 1: "Water Station"},
'x': {0: "LX", 1: "xAPRS", 3: ":computer:"},
'y': {0: "LY", 1: "Yagi @ QTH"},
'z': {0: "LZ"},
'{': {0: "J1"},
'|': {0: "J2", 1: "TNC Stream Switch"},
'}': {0: "J3"},
'~': {0: "J4", 1: "TNC Stream Switch"},
}
alternateSymbol = map[byte]map[int]string{
'!': {0: "OBO", 1: "Emergency"},
'"': {0: "OC", 1: "Reserved"},
'#': {0: "OD#", 1: "Overlay Digi"},
'$': {0: "OEO", 1: "Bank/ATM", 3: ":atm:"},
'%': {0: "OFO", 1: "Power Plant", 3: ":factory:"},
'&': {0: "OG#", 1: "I=Igte R=RX T=1hopTX 2=2hopTX"},
'\'': {0: "OHO", 1: "Crash Site"},
'(': {0: "OIO", 1: "Cloudy", 3: ":cloud:"},
')': {0: "OJO", 1: "Firenet MEO"},
'*': {0: "OK"},
'+': {0: "OL", 1: "Church", 3: ":church:"},
',': {0: "OM", 1: "Girl Scouts", 3: ":tent:"},
'-': {0: "ONO", 1: "House", 3: ":house:"},
'.': {0: "OO", 1: "Ambiguous"},
'/': {0: "OP", 1: "Waypoint Destination"},
'0': {0: "A0#", 1: "Circle", 3: ":red_circle:"},
'1': {0: "A1"},
'2': {0: "A2"},
'3': {0: "A3"},
'4': {0: "A4"},
'5': {0: "A5"},
'6': {0: "A6"},
'7': {0: "A7"},
'8': {0: "A8O", 1: "WiFi Network"},
'9': {0: "A9", 1: "Gas Station", 3: ":fuelpump:"},
':': {0: "NR"},
';': {0: "NSO", 1: "Park/Picnic"},
'<': {0: "NTO", 1: "Advisory"},
'=': {0: "NUO"},
'>': {0: "NV#", 1: "Cars & Vehicles", 3: ":car:"},
'?': {0: "NW", 1: "Info Kiosk"},
'@': {0: "NX", 1: "Hurricane", 3: ":cyclone:"},
'A': {0: "AA#", 1: "Box DTMF & RFID"},
'B': {0: "AB"},
'C': {0: "AC", 1: "Coast Guard"},
'D': {0: "ADO", 1: "Depots"},
'E': {0: "AE", 1: "Smoke"},
'F': {0: "AF"},
'G': {0: "AG"},
'H': {0: "AHO", 1: "Haze"},
'I': {0: "AI", 1: "Rain Shower", 3: ":umbrella:"},
'J': {0: "AJ"},
'K': {0: "AK", 1: "Kenwood HT"},
'L': {0: "AL", 1: "Lighthouse"},
'M': {0: "AMO", 1: "MARS"},
'N': {0: "AN", 1: "Navigation Buoy"},
'O': {0: "AO", 1: "Rocket", 3: ":rocket:"},
'P': {0: "AP", 1: "Parking", 3: ":parking:"},
'Q': {0: "AQ", 1: "Quake"},
'R': {0: "ARO", 1: "Restaurant"},
'S': {0: "AS", 1: "Satellite/Pacsat", 3: ":rocket:"},
'T': {0: "AT", 1: "Thunderstorm"},
'U': {0: "AU", 1: "Sunny"},
'V': {0: "AV", 1: "VORTAC Nav Aid"},
'W': {0: "AW#", 1: "NWS Site"},
'X': {0: "AX", 1: "Pharmacy"},
'Y': {0: "AYO", 1: "Radios and devices"},
'Z': {0: "AZ"},
'[': {0: "DSO", 1: "W. Cloud"},
'\\': {0: "DTO", 1: "GPS"},
']': {0: "DU"},
'^': {0: "DV#", 1: "Other Aircraft", 3: ":airplane:"},
'_': {0: "DW#", 1: "WX Site"},
'`': {0: "DX", 1: "Rain", 3: ":umbrella:"},
'a': {0: "SA#O"},
'b': {0: "SB"},
'c': {0: "SC#O", 1: "CD Triangle"},
'd': {0: "SD", 1: "DX Spot"},
'e': {0: "SE", 1: "Sleet"},
'f': {0: "SF", 1: "Funnel Cloud"},
'g': {0: "SG", 1: "Gale Flags"},
'h': {0: "SHO", 1: "Store or Hamfest"},
'i': {0: "SI#", 1: "Box / POI"},
'j': {0: "SJ", 1: "Work Zone"},
'k': {0: "SKO", 1: "Special Vehicle"},
'l': {0: "SL", 1: "Areas"},
'm': {0: "SM", 1: "Value Sign"},
'n': {0: "SN#", 1: "Triangle"},
'o': {0: "SO", 1: "Small Circle"},
'p': {0: "SP"},
'q': {0: "SQ"},
'r': {0: "SR", 1: "Restrooms"},
's': {0: "SS#", 1: "Ship/Boats", 3: ":speedboat:"},
't': {0: "ST", 1: "Tornado", 3: ":cyclone:"},
'u': {0: "SU#", 1: "Truck", 3: ":truck:"},
'v': {0: "SV#", 1: "Van", 3: ":minibus:"},
'w': {0: "SWO", 1: "Flooding"},
'x': {0: "SX", 1: "Wreck/Obstruction"},
'y': {0: "SY", 1: "Skywarn"},
'z': {0: "SZ#", 1: "Shelter"},
'{': {0: "Q1"},
'|': {0: "Q2", 1: "TNC Stream Switch"},
'}': {0: "Q3"},
'~': {0: "Q4", 1: "TNC Stream Switch"},
}
)

996
protocol/aprs/testdata/packets.txt vendored Normal file
View File

@@ -0,0 +1,996 @@
2026-03-02 15:49:27 CET: GW1523>APN000,TCPXX*,qAX,CWOP-4:@021449z5052.20N/00331.42E_126/009g023t061r000p000P000b10185h58L207eMB63
2026-03-02 15:49:27 CET: EW6603>APRS,TCPXX*,qAX,CWOP-4:@021449z3815.25N/07827.60W_161/000g003t037r000p000P000h57b10357.DsVP
2026-03-02 15:49:27 CET: IU6SSZ-15>APHBL3,TCPIP*,qAS,AD4NCO-10:@144927h4221.32N/01423.98E[000/000/D-APRS ADN Systems / DMR ID: 2226001
2026-03-02 15:49:28 CET: G7UKK-10>APDW18,qAO,G7UKK-10:!5335.18N/00142.48W_202/007g011t054r000p000P000h79b10128144.800MHz Rx IGate and Weather Station near Huddersfield.
2026-03-02 15:49:28 CET: EA4KM-B>APDG02,TCPIP*,qAC,EA4KM-BS:!4013.22ND00351.04W&RNG0001/A=000010 70cm Voice (D-Star) 438.62500MHz -7.6000MHz
2026-03-02 15:49:28 CET: FW0217>APN000,TCPXX*,qAX,CWOP-5:@021449z4241.64N/07150.94W_.../...g...t017r000p000P000b10357h30eMB54
2026-03-02 15:49:28 CET: W7DJK-5>APT314,WIDE1-1,WIDE2-1,qAR,N7XAO:/144930h4434.85N/12317.97W>071/000/A=000482/TinyTrak3|!.&D'R|!wa>!
2026-03-02 15:49:28 CET: HS6LU-11>AESPT4,TCPIP*,qAC,T2HAKATA:!1647.81NP10114.32E>176/000/A=000488 Vin:12.40V. SAT:22 Topspeed:7kmh DX:8km Uptime:206:36
2026-03-02 15:49:28 CET: BD7KHW-10>APLG01,TCPIP*,qAC,T2FRANCE:!2440.26N/11335.23ErLORA APRS 433.775 & BD7KHW <0xe9><0x9f><0xb6><0xe5><0x85><0xb3><0xe6><0xac><0xa2><0xe8><0xbf><0x8e><0xe4><0xbd><0xa0> BAT: 3.3
2026-03-02 15:49:28 CET: CQ0XSM-11>APOTW1,CQ0XBF-3*,WIDE3-2,qAR,CT2IRK-11:> 13.2V Meteo Semideiro - Amsat-Po - 144.900 Mhz
2026-03-02 15:49:27 CET: CR7BQX-8>APLRG1,TCPIP*,qAC,T2DENMARK:!L:0"[LAQ^a GLoRa APRS test|!.%_|
2026-03-02 15:49:27 CET: AD4FM-3>SUPV2U,qAR,AK4ZX-10:`q0Ynpx>/`"80}mon 144.700 winlink_4
2026-03-02 15:49:27 CET: YG1BYD>AESPG4,TCPIP*,qAC,T2TOKYO:@021449z0636.83S/10648.69E_000/000g000t082r...p...P...h81b09800L000 Wx Station YG1BYD v1.0 T=27<0xc2><0xb0> H=81% P=980
2026-03-02 15:49:27 CET: KE0CGR>APU25N,TCPIP*,qAC,T2MCI:=3853.73N\09442.56WL {UIV32N}
2026-03-02 15:49:27 CET: AD7MR-15>APDR17,WA7DRE-11,WIDE1*,WIDE2-1,qAR,WA7DRE:=4801.77N/11632.45WE APRSDroid, UV-PRO
2026-03-02 15:49:27 CET: HB9TMR-9>HB9TMR-9,WIDE1-1,WIDE2-1,qAR,OE9XVI-6:!4714.23N/00927.68E[285/000/A=001605HB9TMR-9
2026-03-02 15:49:27 CET: BH6-1>APE32A,WIDE1-1,qAR,BH6MKM-11:>https://github.com/nakhonthai/ESP32APRS_Audio
2026-03-02 15:49:27 CET: WINLINK>APWL2K,TCPIP*,qAS,WLNK-1:;KD0IVV-10*020249z4103. NW09547. Wa145.090MHz Winlink Packet Gateway
2026-03-02 15:49:27 CET: 9W2KHE-3>APLRG1,TCPIP*,qAC,T2FINLAND:!LMa'hhar=_ !G.../...g...t085h..b10127Bandar Dato' Onn LoRa iGate 433.400MHz
2026-03-02 15:49:27 CET: N0KFB-11>T5PT3Q,WIDE1-1,qAR,W0ANA-15:`y*hm]R>/"6{}Out runnin' 'round / Mon 444.750,146.58,14].52
2026-03-02 15:49:27 CET: KC7O-9>APT311,N6EX-4*,qAR,KELLER:!3409.44N/11809.07W>057/000/A=000931
2026-03-02 15:49:27 CET: WH6CDU-N>APDG03,TCPIP*,qAC,WH6CDU-NS:!4015.00ND08604.20W&/A=0000002m MMDVM Voice (C4FM) 145.65000MHz +0.0000MHz, WH6CDU_Pi-Star_ND
2026-03-02 15:49:27 CET: VE7KGV-7>TYQTUS,FROMME,WIDE1*,WIDE2-1,qAR,AF7DX-1:`4[koq/[/`"4h}_0
2026-03-02 15:49:27 CET: SA6BXE-2>APMI06,TCPIP*,qAC,T2SWEDEN:T#171,184,000,000,000,000,00000000
2026-03-02 15:49:27 CET: VE1YAR>APNU19,WIDE3-3,qAR,VE1GU-2:!4356.67NS06601.32W#PHG5460/Yarmouth,NS MARCAN UIDIGI YARC
2026-03-02 15:49:28 CET: SR5SAN>APRX29,DL2-2,qAR,SR5ZOC-1:;438.837OC*111111z5211.14N/02246.90Er438.837MHz<0xc2><0xa0>-760<0xc2><0xa0>R60k<0xc2><0xa0>SR8DMR<0xc2><0xa0>Chotycze
2026-03-02 15:49:28 CET: BG6STN-7>APRS,TCPIP*,qAC,T2VAN:@021449z3026.72N/11131.94EOPHG5920 <0xe4><0xb8><0xbb><0xe9><0xa1><0xb5>:https://www.bg6stn.top , <0xe8><0xbf><0x90><0xe8><0xa1><0x8c><0xe6><0x97><0xb6><0xe9><0x97><0xb4>181<0xe5><0xa4><0xa9>23<0xe6><0x97><0xb6>0<0xe5><0x88><0x86> <0xe9><0x80><0x9f><0xe5><0xba><0xa6>133.0km/h <0xe6><0x80><0xbb><0xe9><0x87><0x8c><0xe7><0xa8><0x8b>355187.6km
2026-03-02 15:49:28 CET: OE3XVI-S>APDG01,TCPIP*,qAC,OE3XVI-GS:;OE3XVI B *021449z4756.28ND01549.12EaRNG0031 440 Voice 438.27500MHz -7.6000MHz
2026-03-02 15:49:28 CET: DG5GSA-B>APLRG1,TCPIP*,qAC,T2UKRAINE:=4817.28NL00749.18E&Lora I-Gate an X30 Antenne + VV + Bandpass, Solar Powerd
2026-03-02 15:49:28 CET: CW5988>APRS,TCPXX*,qAX,CWOP-5:@021449z3745.58N/12225.90W_276/004g009t056r000p007P007h87b10176L009.DsVP
2026-03-02 15:49:28 CET: EL-F5LCT>RXTLM-1,TCPIP,qAR,F5LCT::EL-F5LCT :UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:28 CET: BD4KM-10>APET51,TCPIP*,qAC,T2FUKUOKA:!3730.66N/12203.05Erweihai igate 144.640MHz BR4IW 439.900 -5 88.5 8.5V
2026-03-02 15:49:28 CET: EL-F5LCT>RXTLM-1,TCPIP,qAR,F5LCT:T#232,0.00,0.00,0,1,0.0,00000000,SimplexLogic
2026-03-02 15:49:28 CET: EL-F5LCT>RXTLM-1,TCPIP,qAR,F5LCT:T#233,0.00,0.00,0,1,0.0,00000000,SimplexLogic2
2026-03-02 15:49:28 CET: BI1NGG>APIN20,TCPIP*,qAC,T2YANTAI:!4024.48N/11728.79Er[QSO:439.600MHz -8.0MHz TSQ:100.0]
2026-03-02 15:49:28 CET: AMITY>APRS,TCPXX*,qAX,CWOP-5:@021449z3024.00N/09106.00W_030/001g004t064r000p000P000h87b10215.DsVP
2026-03-02 15:49:28 CET: EA1MNB-9>APLRT1,WIDE1-1,WIDE2-1,qAR,EC1AME-10:>https://github.com/richonguzman/LoRa_APRS_Tracker 2024.10.11
2026-03-02 15:49:28 CET: JP1YJX-I>APIRP2,TCPIP*,qAC,JP1YJX-IS:!3524.64ND13924.17E&EBINA -> APRS
2026-03-02 15:49:28 CET: CE4TQY-7>APLRT1,WIDE1-1,qAR,CE4TQY-10:=/_g:J<YU$[IOQ
2026-03-02 15:49:28 CET: VA7RHH-3>BEACON,qAR,VE7SST-2:>146.540s*111111x5602.75N/12207.88WrT100 VA7RHH
2026-03-02 15:49:26 CET: N0VYE-B>APDG02,TCPIP*,qAC,N0VYE-BS:!3910.85ND09154.61W&RNG0001/A=000010 70cm Voice (D-Star) 433.00000MHz +0.0000MHz
2026-03-02 15:49:26 CET: IW3BTS-2>APBM1D,IW3BTS,DMR*,qAR,IW3BTS:=4629.72N/01119.11Ef000/002/A=001012Paolo BTS VF BZ
2026-03-02 15:49:26 CET: W4VA-13>APMI06,KN4IJF-1,WC4J,WIDE2*,qAR,KG4GIY:T#003,170,095,030,057,25693,00000000
2026-03-02 15:49:27 CET: K3TI-S>APJIO4,TCPIP*,qAC,K3TI-GS:;K3TI B *071248z4028.17ND07600.84WaRNG0040 440 Voice 445.550 -5.00 MHz
2026-03-02 15:49:27 CET: K3TI-S>APJIO4,TCPIP*,qAC,K3TI-GS:;K3TI C *071248z4028.17ND07600.84WaRNG0040 2m Voice 145.340 -0.600 MHz
2026-03-02 15:49:27 CET: E27ASY-1>APRX29,TCPIP*,qAC,T2OSAKA:!1445.26NR10043.99EaPHG3270 Rx-only iGate
2026-03-02 15:49:27 CET: YN1ZHG-10>APHPIB,TCPIP*,qAC,T2DENMARK:>QRG: 438.710MHz CTCSS: 123Hz @ 60W
2026-03-02 15:49:27 CET: YN1ZHG-10>APHPIB,TCPIP*,qAC,T2DENMARK:!1206.99N/08617.05WnUHF EchoLink Node YN1JLX-L | MGA
2026-03-02 15:49:27 CET: EB1GEM-3>APMI06,ESAS2-2,qAR,ED1ZAE-3:@021449z4330.01N/00536.07W-WX3in1Plus2.0 U=13.3V,T=??.?C/??.?F
2026-03-02 15:49:27 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.41N/01603.42EQ252/050/A=000643 MOBILE DMR TG:21966
2026-03-02 15:49:27 CET: YB1HBO-5>APDR16,TCPIP*,qAC,T2SWEDEN:=0719.23S/11031.70Ew319/052/A=002060 https://aprsdroid.org/
2026-03-02 15:49:27 CET: BD5DOY-12>APGPRS,TCPIP*,qAC,T2CS:@144909h1519.54S/02810.43Ef082/000/A=003768 YiWu CHINA TOYOTA 11.35V 31.0C
2026-03-02 15:49:27 CET: KE8MLF-S>APDG01,TCPIP*,qAC,KE8MLF-GS:;KE8MLF B *021449z4530.00ND08200.00WaRNG0001 440 Voice 438.80000MHz +0.0000MHz
2026-03-02 15:49:27 CET: KE9FZ-1>TQSP6X,qAR,KC9ZMY-2:`s5wl4%-/`"6@}_4
2026-03-02 15:49:27 CET: VE3ZKA-B>APDG02,TCPIP*,qAC,VE3ZKA-BS:!4622.66ND08102.80W&RNG0001/A=000010 70cm Voice (D-Star) 445.60000MHz +0.0000MHz
2026-03-02 15:49:27 CET: SR2PP>APRS,TCPIP*,qAC,SIXTH:;ER-SR2PP *111111z5249.41NE01802.63E0145.700MHz T074 R22k **Doswietlenie sieci KPSR**_Kujawsko-Pomorska Siec Radiowa_w sieci SR2J,SR2JJ,SR2GG,SR2VV,SR2PP
2026-03-02 15:49:27 CET: K9RCZ-3>APLOX1,TCPIP*,qAC,T2TEXAS:!L9/3c80/1# XLora Digi/iGate <-> www.paaros.com/lora P=4.53V
2026-03-02 15:49:27 CET: JH4GQC>APU25N,TCPIP*,qAC,T2FUKUOKA:;EL-917416*311623z3415.46NE13231.55E0JH4GQC-L (Linked to this Wires-X)
2026-03-02 15:49:27 CET: YM9KE>AESPG4,TCPIP*,qAC,APRSTH:!3946.85N/04113.58E#145.700 88.5 TRAC ERZURUM
2026-03-02 15:49:27 CET: GW5031>APN000,TCPXX*,qAX,CWOP-7:@021449z3302.70N/09640.65W_052/000g003t065r000p000P000b10206h81L055eMB63
2026-03-02 15:49:27 CET: K5MPH>APBK15,WIDE1-1,WIDE2-1,qAR,KC5MAH-3:!2555.09N/09730.00WIIgate/Digi
2026-03-02 15:49:27 CET: DW1973>APRS,TCPXX*,qAX,CWOP-6:@021449z4145.98N/08803.55W_017/007g013t031r000p000P000h51b10296.DsVP
2026-03-02 15:49:28 CET: SADDLE>APMI06,TCPIP*,qAS,K7RPT-10:@021449z4532.71NI12322.93W#PHG52604/Digipeater & iGate | K7RPT
2026-03-02 15:49:28 CET: VY1XY>APRS,TCPXX*,qAX,CWOP-7:@021449z6029.63N/13451.68W_054/000g001t-31r000p000P000h78b09982eCumulusDsVP
2026-03-02 15:49:28 CET: TA5ADB>APDR16,TCPIP*,qAC,T2KA:=3706.39N/03722.90Ee359/000/144.850MHz/A=003057 73!...
2026-03-02 15:49:28 CET: K0WXC-1>APRS,TCPIP*,qAC,T2SPAIN:@021445z4008.96N/09237.79W_c089s003g004t031r000P000h94b10123
2026-03-02 15:49:28 CET: BR3QQ>APLM3D,TCPIP*,qAC,T2NANJING:!3954.10N/11934.59E&QHDRA 144.640MHz IGATE DIGI Voice BR3QQ 439.800-8 T88.5 14.2V
2026-03-02 15:49:28 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.41N/01603.40EQ252/051/A=000646 MOBILE DMR TG:21966
2026-03-02 15:49:28 CET: BG4GNW>APN000,TCPIP*,qAC,T2HAKATA:=3043.54NW12119.71E_
2026-03-02 15:49:28 CET: DL2AKT-20>APLRG1,TCPIP*,qAC,T2ERFURT:!L4gWgQ<g"_ G.../...g...t062h38b10259Jens' LoRa APRS iGate Rx only
2026-03-02 15:49:26 CET: 9A1BTU-1>APNU3B,WIDE3-3,qAR,9A3XK:!4520.12N/01740.60E#PHG2230/APRS Pozega * JN85UI * UIDIGI1.93B (1)
2026-03-02 15:49:26 CET: KE7EX-12>KE7EX-10,WIDE2-2,qAR,KE7EX-10:@021443z3845.73N\07732.42WSHF A1A/F1D QRP Laser Keyed CW/WSPR (30m) U=13.5V
2026-03-02 15:49:27 CET: DW4212>APRS,TCPXX*,qAX,CWOP-5:@021449z4052.98N/00335.20W_173/000g006t055r000p000P000h63b10138L492.DsVP
2026-03-02 15:49:27 CET: YM3BUR>APMI06,LOCAL-1,qAR,YM1KTC-11:>TRAC WIDEn-N v1.13 U=13.3V,T=20.4C
2026-03-02 15:49:27 CET: W4IOS-10>APWW11,TCPIP*,qAC,T2DENMARK:>021449zAPRSISCE/32 FTM-300DR @ 33'
2026-03-02 15:49:27 CET: N9PNO-10>APNU19,qAR,KE0NHQ-3:!4231.08NS09037.10W#PHG6300/(N9PNO-10 W3,IAn DUBUQUE UIDIGI 1.9B3)1
2026-03-02 15:49:27 CET: CE7DE-12>APLRT1,WIDE1-1,qAR,CE7DE-10:=/ctJE;q"`O;nQ
2026-03-02 15:49:27 CET: CW6404>APN000,TCPXX*,qAX,CWOP-5:@021446z3910.06N/07536.04W_111/002g007t032r000p000P000b10359h55L232eMB54
2026-03-02 15:49:27 CET: IZ8FAV>APRS,TCPIP*,qAC,IQ2ARI-AS:;EL-IZ8FAV*111111z4048.83NE01421.46E0433.275MHz T071 R03m Svxlink Toni: 71.9-TG83 , 77.0-TG41100 , 88.5-TG222 , 123.0-TG32 , 141.0-TG41 , 67.0-TG61 , 131.8-TG21, 136.5-TG81, 107.2-TG82, 206.5-TG22290,167.9-TG97100
2026-03-02 15:49:27 CET: KN4PLO>APDW17,WIDE1-1,qAR,WX4BK-13:!3334.17ND08504.49W#PHG5230146.640MHz T131 -060 /A=001280Tanner-Carrollton IG/Digi/WX
2026-03-02 15:49:27 CET: TB1DVM-7>APAT51,YM3BUR,WIDE1*,WIDE2-1,qAR,YM1KTC-11:!4057.94N/02839.61Ey000/000/A=000196OP.MURATHAN 0532 348 3764
2026-03-02 15:49:27 CET: EL-F4ELA>RXTLM-1,TCPIP,qAR,F4ELA::EL-F4ELA :UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:27 CET: DD7VM-7>APOSB4,TCPIP*,qAS,DD7VM:@021449z4919.56N/00720.23E[/A=000833SharkRF openSPOT4
2026-03-02 15:49:27 CET: EL-F4ELA>RXTLM-1,TCPIP,qAR,F4ELA:T#124,0.00,0.02,0,3,0.0,00000000,SimplexLogic
2026-03-02 15:49:27 CET: WLNK-1>APWLK,TCPIP*,qAC,T2CAWEST::AD4FM-3 :You have 1 Winlink mail messages pending{7953
2026-03-02 15:49:28 CET: EW9467>APRS,TCPXX*,qAX,CWOP-7:@021449z3322.83N/08427.83E_155/000g001t066r000p000P000b10237h56eMB36
2026-03-02 15:49:28 CET: FW0403>APRS,TCPXX*,qAX,CWOP-4:@021449z2912.16N/09456.06W_098/008g012t068r000p002P002h89b10191L053WeatherCatV241B14H31
2026-03-02 15:49:28 CET: NE3Z>APWW10,WIDE1-1,WIDE2-1,qAR,W4KEL-12:@144926h3816.80NR07732.62W&PHG3230Hello!
2026-03-02 15:49:28 CET: DO0RDG>APMI06,WIDE2-2,qAO,DG6HD-1:T#151,181,183,00000,00000,000,00000000
2026-03-02 15:49:28 CET: KD3BSA-7>T0RU9W,KB3FCZ-2,WIDE1*,WIDE2-1,qAR,W5RTM-2:`kI*<0x1c>DmR/"7R}RADTEL950PRO
2026-03-02 15:49:28 CET: GW7041>APREST,TCPXX*,qAX,CWOP-3:@021449z3713.13N/09350.64W_069/003g004t038r000P019h97b10244L037eREST
2026-03-02 15:49:28 CET: HK4RAU-60>HK4DAP,TCPIP*,qAC,T2CAEAST:>Estacion Meterologica APRS con SCRIP Python, Raspberry Pi,ESP32 y Sensor BMP280 por HK4DAP
2026-03-02 15:49:28 CET: HK4RAU-60>HK4DAP,TCPIP*,qAC,T2CAEAST:@021449z0425.51N/07508.44W_000/004g000t069r000p000h69b10140ESTACION DEL CLIMA APRS ZONA 6 RADIOAFICIONADOS UNIDOS 146.520 MHz en Simplex Fonia - Reporte del clima en Ibagu<0xc3><0xa9>: nubes dispersas
2026-03-02 15:49:28 CET: DL5BTE-9>APRS,WIDE2-1,qAR,DB0PCH:!5335.88N/01126.02E>Speed=13km/h; VDD=8.57V; QRV:
2026-03-02 15:49:28 CET: EW2817>APN000,TCPXX*,qAX,CWOP-7:@021449z3559.64N/07853.94W_000/000g000t042r000p000P000b10087h71eMB63
2026-03-02 15:49:28 CET: DO1BJ-14>APLC13,qAR,DB0BHV-L4:!/3J/TP^G'u1TQLoRa-System-Hannes QRL im Lkw
2026-03-02 15:49:28 CET: DM5HR-9>APLRT1,WIDE1-1,qAR,DL7JP-10:!/5w#mQppj>GUQ
2026-03-02 15:49:28 CET: DW5411>APRS,TCPXX*,qAX,CWOP-7:!3327.90N/08620.70W_135/002g007t063r000P000p000h71b10248.VWS-DavisVP2+
2026-03-02 15:49:28 CET: 9M4GKJ-10>AESPG4,TCPIP*,qAC,DMRNET01:>V.4.4c build May 14 2023 20:28:54 Rx:107 Digi:60 Tx:56 UpTime:04.38 DX:02-03-2026 18:28Loc 9M4RJR-3 40km 6<0xc2><0xb0>
2026-03-02 15:49:28 CET: F1ZIB>APRS,TCPIP*,qAC,T2UK:;ER-F1ZIB *111111z4930.63NE00009.73E0432.800MHz T250 R20k 145.2375Mhz T:250.3Hz LH/F1ZIB N:385525
2026-03-02 15:49:28 CET: BH4EAW-10>APN000,WIDE1-1,WIDE2-1,qAS,BG4GTL-10:=3116.97NZ12130.29E&000/000/A=000160BH4EAW-Digi 13.86V
2026-03-02 15:49:28 CET: WB5NFC-3>APBPQ1,TCPIP*,qAC,T2MCI:<IGATE,MSG_CNT=6,LOC_CNT=0
2026-03-02 15:49:28 CET: AA2QU-B>APDG02,TCPIP*,qAC,AA2QU-BS:!4436.06NW07332.29WiRNG0001/A=000010 70cm Voice (D-Star) 438.80000MHz +0.0000MHz, APRS for ircDDBGateway
2026-03-02 15:49:28 CET: AA2QU-B>APDG02,qAS,AA2QU:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:28 CET: BD4SDX-10>APET51,WIDE1-1,WIDE2-1,qAS,BY4SA-10:!3054.31N/12039.41Er WuJiang BD4SDX APRS iGate 144.640Mhz 13.7V
2026-03-02 15:49:28 CET: ER-SK7RGI>APSVX1,WIDE1-1,WIDE2-2,qAS,SK7RGI:T#602,000,000,000,000,000,00000000
2026-03-02 15:49:28 CET: F1ZGQ-3>APFD29,F1ZBH-3*,qAR,F6ZAU-3:!4821.38N/00404.89W# DIGI F1ZGQ-3 ***ADRASEC-29***
2026-03-02 15:49:28 CET: DH1SP-20>APRSGW,TCPIP*,qAR,DH1SP-20:T#075,999.1,27.8,37.1,1020.2,0.0,10000000
2026-03-02 15:49:28 CET: DH1SP-20>APRSGW,TCPIP*,qAR,DH1SP-20:!4907.10N/00912.61E#/B=100/A=000656
2026-03-02 15:49:28 CET: E27IHO-13>AESPG4,TCPIP*,qAC,CWOP-3:T#014,18,25,26,61,0.33,00000000,@ 14 49 27 2 3 2026
2026-03-02 15:49:28 CET: ZR1RTG-8>APCLEY,TCPIP*,qAC,APRS-ZA:/021449z3356.20S/01853.41Ek000/000/A=000658 42C 1Mv 4.19V In 13.12V 9kmh
2026-03-02 15:49:28 CET: GW1972>APN000,TCPXX*,qAX,CWOP-3:@021449z4130.22N/08724.99W_296/005g011t030r000p000P000b10062h62eMB63
2026-03-02 15:49:28 CET: PU4BNL-1>APE32I,TCPIP*,qAC,T2BRAZIL:!1944.80SI04758.44W&PHG5060/A=002656 IGate MODE - UBERABA-MG
2026-03-02 15:49:28 CET: M7OMH-15>APWW11,TCPIP*,qAC,T2SYDNEY:>IO93cr/#DX: G7UKK-10 11.0mi 167<0xb0> 14:49 5335.18N 00142.48W
2026-03-02 15:49:28 CET: EA1HMK-7>APLRT1,WIDE1-1,qAO,EA1HDC-10:=/9+f?L=*->;.Q
2026-03-02 15:49:28 CET: BD7IIT>APBM1D,DMR*,qAS,BM4602-10:=2301.73N/11307.26E[360/000/A=-00098Kayu Pan
2026-03-02 15:49:28 CET: HB9FM-4>APMI06,TCPIP*,qAC,T2SWEDEN:T#133,186,071,025,079,074,00000000
2026-03-02 15:49:28 CET: OH7UDH>APAT51,WIDE2-1,qAR,OH5RBG:!6017.90N/02456.10E>192/000/A=000216Paranoidi Pessimisti
2026-03-02 15:49:28 CET: OH9RDA>APZMDR,TRACE1-1,qAR,SK2HG-10:!6541.26N/02442.00E#
2026-03-02 15:49:28 CET: VA2BBW-2>APDW19,qAR,VA2BBW-2:;VA2BBW-13*010000z4529.72N/07535.36W__87/001g002t007r000P000b10490h54
2026-03-02 15:49:28 CET: LB1HJ>APBM1D,LD9HR,DMR*,qAR,LD9HR:@144926h6848.69N/01630.36E-010/000Trond
2026-03-02 15:49:28 CET: CX1AAO-Y>APDG03,TCPIP*,qAC,CX1AAO-YS:!3450.15SW05518.09Wi/A=00000070cm MMDVM Voice (C4FM) 434.60000MHz +5.0000MHz, APRS for YSFGateway
2026-03-02 15:49:28 CET: CX1AAO-Y>APDG03,qAS,CX1AAO:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:28 CET: BG3RQT-9>APTNC0,qAS,BG3RQT:=4000.77N/11621.83E[232/000/A=000304 ESP32 Tracker TEST <0xe5><0x8d><0xab><0xe6><0x98><0x9f><0xe6><0x95><0xb0><0xe9><0x87><0x8f>:10 | <0xe7><0xb4><0xaf><0xe8><0xae><0xa1><0xe8><0xb7><0x9d><0xe7><0xa6><0xbb>:0.72km
2026-03-02 15:49:28 CET: F5STO-S>APDG01,TCPIP*,qAC,F5STO-GS:;F5STO B *021449z4831.75ND00644.57EaRNG0025/A=000820 70cm Voice (D-Star) 433.48750MHz +0.0000MHz
2026-03-02 15:49:28 CET: NP4GT>APDW17,TCPIP*,qAC,T2PR:!1817.66N/06707.10W-/A=000395 NP4GT > http://www.np4gt.net > FK68kh | Anasco, PR 00610
2026-03-02 15:49:28 CET: NP4GT>APDW17,TCPIP*,qAC,T2PR:;NP4GT-R *111111z1817.66N/06707.10WrPHG9390 449.975MHz Multimode DMR-RPT FK68kh | Anasco, PR 00610 > NP4GT > http://www.np4gt.net
2026-03-02 15:49:29 CET: IW6ATQ-8>BEACON,WIDE3-3,IQ6KX-10,qAR,IZ6DDQ-1:=4335.62N\01331.11ES - info www.iw6atq.net
2026-03-02 15:49:29 CET: PE2KMV-S>APDG01,TCPIP*,qAC,PE2KMV-GS:;PE2KMV B *021449z5052.06ND00550.43EaRNG0001/A=000010 70cm Voice (D-Star) 439.85000MHz -9.4000MHz
2026-03-02 15:49:29 CET: SP8MB-L>APLRFD,TCPIP*,qAC,T2PRT:!L4Wi3T6)F# GLora iGate/DIGI 434.855 @1k2 Batt=4.92V|'s%^!!!!!!!!|
2026-03-02 15:49:28 CET: JP1YJX-I>APIRP2,TCPIP*,qAC,JP1YJX-IS:<IGATE,MSG_CNT=0,LOC_CNT=1
2026-03-02 15:49:28 CET: EL-W8PAR>APSVX1,WIDE1-1,qAS,W8PAR:T#103,012,000,001,000,000,00000000
2026-03-02 15:49:28 CET: BA4IIK>APN000,TCPIP*,qAC,T2OSAKA:=3654.89N/12132.06E)4.0V
2026-03-02 15:49:28 CET: PK10XH-12>APN000,TCPIP*,qAC,T2JKTP:=1019.67N/12355.83Exradio fault
2026-03-02 15:49:28 CET: W5IC>APN000,TCPXX*,qAX,CWOP-3:@021444z3024.24N/09105.08W_338/000g003t063r000p000P000b10212h92L378eMB62
2026-03-02 15:49:28 CET: AL7PB>APRS,TCPXX*,qAX,CWOP-6:@021449z3225.88N/09937.95W_000/000g000t050r000p000P000h78b10177eCumulusDsVP
2026-03-02 15:49:28 CET: HS7BHK-15>APAT81,WIDE1-1,WIDE2-1,qAR,HS7BHK-1:!1356.49N/10044.11E[077/000/A=000000HS7BHK D878UV
2026-03-02 15:49:28 CET: PC1RZ-7>APN000,WIDE2-1,qAR,PD5A-5:=5212.16N/00559.34E>055/000/A=000082
2026-03-02 15:49:28 CET: N2ASU-N>APDG03,TCPIP*,qAC,N2ASU-NS:!3950.07ND07500.75W&/A=00000070cm MMDVM Voice (C4FM) 433.45000MHz +0.0000MHz, N2ASU_Pi-Star_ND
2026-03-02 15:49:28 CET: HS5SQI-4>AESPG4,TCPIP*,qAC,APRSTH::HS5SQI-4 :PARM.RxTraffic,TxTraffic,RxDrop,RSSI,Voltage
2026-03-02 15:49:28 CET: 9A0DVR>APBM1S,TCPIP*,qAS,BM2222:@021449z4522.88N/01500.87ErPHG0000Solarni DMR/FM repetitor 438.9875Mhz -7.6Mhz (173.8) i C4FM hotspot 433.650Mhz 438.9875/431.3875 CC1
2026-03-02 15:49:28 CET: FW3211>APN000,TCPXX*,qAX,CWOP-6:@021449z4421.49N/00917.10E_264/001g003t054r000p006P006b10247h70eMB63
2026-03-02 15:49:28 CET: ON6ZQ>APN100,TCPIP*,qAC,SIXTH:;I/VE-163 *021449z4635.72NS01236.82E;SOTA activ. planned on 20260302 1630z
2026-03-02 15:49:28 CET: DW8362>APRS,TCPXX*,qAX,CWOP-6:@021449z3914.33N/07730.43W_321/000g004t032r000p000P000b10353h62eMB35
2026-03-02 15:49:28 CET: 9A0DVR>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=219015
2026-03-02 15:49:29 CET: OK5TVR-8>APLRT1,OK1CTR-10*,qAS,OK1RPL-15:!/5?r.QoMC>G;Q DR:-119 DS:-0.50
2026-03-02 15:49:29 CET: WINLINK>APWL2K,TCPIP*,qAS,WLNK-1:;KD0SFY *020249z3853. NW10447. Wa441.050MHz Winlink VARA FM Wide Gateway
2026-03-02 15:49:29 CET: BX2ADJ-2>APVRT7,TCPIP*,qAC,T2FUKUOKA:!2500.63N/12128.06Er 8.9V +31.6C 1012hPa
2026-03-02 15:49:29 CET: KF5JED-13>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3500.51N/10151.92W_047/004g005t042r000p000P000h62b10196L060AmbientCWOP.com
2026-03-02 15:49:29 CET: F1OLZ-7>APDR16,TCPIP*,qAC,T2SWEDEN:=4819.3 N/00340.8 W$161/002/145.625MHz/A=000466 https://aprsdro
2026-03-02 15:49:29 CET: VE2GWD-D>APDG03,TCPIP*,qAC,VE2GWD-DS:!4524.00ND07324.00W&/A=00000070cm MMDVM Voice (DMR) 446.50000MHz +0.0000MHz, APRS for DMRGateway
2026-03-02 15:49:29 CET: N7FAN-14>APDR17,TCPIP*,qAC,T2PANAMA:=3933.88N/10607.65Wu043/059/A=009176 n7fan@n7fan.com
2026-03-02 15:49:29 CET: FW9374>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3505.12N/08051.24W_055/000g000t048r000p000P000h70b10308L000AmbientCWOP.com
2026-03-02 15:49:29 CET: GW1603>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3656.69N/09044.93W_074/004g008t039r000p021P021h99b10229L083AmbientCWOP.com
2026-03-02 15:49:29 CET: DB0KLE-10>APLOX1,TCPIP*,qAC,T2ERFURT:!5138.59NL00618.96E&iGate Relais Uedemerbruch Batt=5.33V,
2026-03-02 15:49:29 CET: EL-E75BM>RXTLM-1,TCPIP,qAR,E75BM::EL-E75BM :UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:29 CET: DL2NEQ-1>APRSGW,TCPIP*,qAR,DL2NEQ-1:!4932.21N/01046.39E#/B=100/A=001427
2026-03-02 15:49:29 CET: AA6CD-15>APWW11,TCPIP*,qAC,T2DENMARK:@144926h3358.54N\11804.10W?Whittier Cam & Seismograph Station: http://whittiercam.ddns.net/
2026-03-02 15:49:29 CET: KE4EST>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3506.61N/08406.13W_101/001g001t055r000p000P000h71b10256L088AmbientCWOP.com
2026-03-02 15:49:29 CET: XQ6AK-15>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z4042.34S/07301.11W_006/000g002t058r000p001P001h91b10095L122AmbientCWOP.com
2026-03-02 15:49:28 CET: KC5EZZ-2>APTW14,WIDE1-1,WIDE2-2,qAR,KC5EZZ:_01051417c359s000g000t065r000p000P000h..b.....tU2k
2026-03-02 15:49:28 CET: KJ6EUC-B>APDG02,TCPIP*,qAC,KJ6EUC-BS:!3438.60ND11815.32W&RNG0001/A=000010 70cm Voice (D-Star) 440.64000MHz +5.0000MHz
2026-03-02 15:49:28 CET: ER-OE9XVD>RXTLM-1,TCPIP,qAR,OE9XVD::ER-OE9XVD:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:28 CET: ER-OE9XVD>RXTLM-1,TCPIP,qAR,OE9XVD:T#076,0.00,0.00,0,0,0.0,00000000,TetraLogic
2026-03-02 15:49:28 CET: N2KOJ-4>APBM1D,N2KOJ,DMR*,qAR,N2KOJ:@144926h4105.88N/07507.43W4144/000King of Jersey-2
2026-03-02 15:49:28 CET: AJ4FJ-5>BEACON,qAO,K2ATL-1:;147.015PL*111111z3419.31N/08438.20Wr147.015MHz T100 + R45m Pine Log
2026-03-02 15:49:28 CET: SP5RC-9>URQW49,WIDE1-1,qAR,SR5NWA:`0W}oqr>/]"4l}145.500MHz , VA 136,5 Hz, op. PAWEL =
2026-03-02 15:49:28 CET: KM7CSF>SQSTRQ,WIDE1-1,qAR,W7BAN:`&/Pl@.>/`"CP}_5
2026-03-02 15:49:28 CET: G6JEF-B>APDG02,TCPIP*,qAC,G6JEF-BS:!5405.24ND00012.07W&RNG0001/A=000010 70cm Voice (D-Star) 438.80000MHz +0.0000MHz
2026-03-02 15:49:28 CET: DB0WBD-10>APMI04,WIDE2-2,qAR,HB9FLD-11:T#014,179,044,002,057,000,00000000
2026-03-02 15:49:29 CET: K6XLT-12>T3SVRR,SHAFER,WIDE1*,WIDE2-1,qAR,K7RYH-2:`,'5l <0x1c>v/'"='}|$g%j'M|!w^?!|3
2026-03-02 15:49:29 CET: SR5SAN-2>APRX29,DL2-2,qAR,SQ5LS-2:;438.837OC*111111z5211.14N/02246.90Er438.837MHz<0xc2><0xa0>-760<0xc2><0xa0>R60k<0xc2><0xa0>SR8DMR<0xc2><0xa0>Chotycze
2026-03-02 15:49:29 CET: W9WRP>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3345.58N/11230.65W_292/002g002t060r000p000P000h34b10161L026AmbientCWOP.com
2026-03-02 15:49:29 CET: NP4KB>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z1819.13N/06539.64W_117/000g001t...r000p000P000h...b10212L702AmbientCWOP.com
2026-03-02 15:49:29 CET: KN6RBP-Y>APDG03,TCPIP*,qAC,KN6RBP-YS:!3344.52N/11656.75WM/A=00000070cm MMDVM Voice (C4FM) 445.00000MHz -5.0000MHz, APRS for YSFGateway
2026-03-02 15:49:29 CET: KN6RBP-Y>APDG03,qAS,KN6RBP:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:29 CET: HS6TXN>AESPT4,TCPIP*,qAC,T2HK:!1554.85N/10101.56E-000/000/A=000334 Satellite 09 https://sites.google.com/view/pptxn
2026-03-02 15:49:29 CET: F5ZZ-10>APMI06,TCPIP*,qAC,T2FRANCE:@021449z4354.80N/00202.86E-WX3in1Plus2.0 ADRASEC 09
2026-03-02 15:49:29 CET: IR3UFF-3>APDG03,TCPIP*,qAC,IR3UFF-3S:!4628.60ND01331.21E&/A=00196870cm MMDVM Voice (DMR) 431.41250MHz +1.6000MHz, Multimode Repeater
2026-03-02 15:49:29 CET: GW3738>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4309.21N/08948.32W_138/001g002t030r000p000P000h66b10281L206AmbientCWOP.com
2026-03-02 15:49:29 CET: IW2FCH-12>APLRT1,WIDE1-1,qAR,IW2FCH-10:=/7LZwQ(lPj@LG|(W%O|
2026-03-02 15:49:29 CET: GW3716>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3148.02N/09931.53W_141/006g008t061r000p000P000h85b10167L240AmbientCWOP.com
2026-03-02 15:49:29 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.40N/01603.38EQ252/051/A=000643 MOBILE DMR TG:21966
2026-03-02 15:49:29 CET: HB9GYR-10>APMI06,TCPIP*,qAC,T2PRT::HB9GYR-10:PARM.V.in,Rx.1h,Dg.1h,Eff.1h,Temp,O1,O2,O3,O4,I1,I2,I3,I4
2026-03-02 15:49:29 CET: KD4ROB-N>APDG03,TCPIP*,qAC,T2SPAIN:!3444.29ND08459.48W&/A=000000440 MMDVM Voice 446.30000MHz +0.0000MHz, KD4ROB_Pi-Star
2026-03-02 15:49:29 CET: HB9GYR-10>APMI06,TCPIP*,qAC,T2PRT::HB9GYR-10:UNIT.Volt,Pkt,Pkt,Pcnt,C,On,On,On,On,Hi,Hi,Hi,Hi
2026-03-02 15:49:29 CET: KC1NEZ-R>APDG03,TCPIP*,qAC,KC1NEZ-RS:!4148.01ND07227.32W&/A=00055870cm MMDVM Voice (C4FM) 442.55000MHz +5.0000MHz, KC1NEZ_Pi-Star_RPT
2026-03-02 15:49:29 CET: HB9GYR-10>APMI06,TCPIP*,qAC,T2PRT::HB9GYR-10:EQNS.0,0.075,0,0,10,0,0,10,0,0,1,0,0,0.5,-64
2026-03-02 15:49:29 CET: KE0LUL-9>T3QP1W,WIDE1-1,qAR,N0JBE:`y_@l^ k/`"65}2012 HONDA RIDGELINE_%
2026-03-02 15:49:29 CET: KI7JYE>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z4518.74N/12256.49W_263/000g000t043r000p000P000h98b10177L000AmbientCWOP.com
2026-03-02 15:49:28 CET: DL9TD-13>APLRT1,WIDE1-1,QA8M9,qAO,DB0LNR-10:=/4Kh7P/?F[90Q
2026-03-02 15:49:28 CET: MM7RRD-D>APDG03,TCPIP*,qAC,MM7RRD-DS:!5451.20N/00402.60Wr/A=00000070cm MMDVM Voice (DMR) 438.66250MHz +0.0000MHz, APRS for DMRGateway
2026-03-02 15:49:28 CET: MM7RRD-D>APDG03,qAS,MM7RRD:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:28 CET: DL2UL-4>APLC13,TCPIP*,qAC,T2CSNGRAD:!5118.51NL00630.86E&LoRa-System
2026-03-02 15:49:28 CET: TA1FL>APMI06,WIDE2-2,qAR,YM2VTC:T#108,170,044,000,078,000,00000000
2026-03-02 15:49:28 CET: DK4WO-D>APDG03,TCPIP*,qAC,DK4WO-DS:!5159.54ND00738.62E&/A=00000070cm MMDVM Voice (DMR) 433.62500MHz +0.0000MHz, APRS for DMRGateway
2026-03-02 15:49:28 CET: E24XVG-1>APMI06,TCPIP*,qAS,E24XVG:@021449z1252.46N/10135.80E-WX3in1Plus2.0 U=12.2V,T=??.?C/??.?F
2026-03-02 15:49:28 CET: 9W2FRR-1>APLRG1,TCPIP*,qAC,T2TAIWAN:=LLo'th1f,a !GSelayang Utama LoRa APRS I-Gate Batt=3.95V
2026-03-02 15:49:28 CET: DH1LC-9>APOTC1,WIDE1-1,WIDE2-1,qAR,DB0KOE-1:/144927h/4d@JP7%G> st/A=000145
2026-03-02 15:49:29 CET: DB0TT>APRS,TCPIP*,qAC,T2PRT:;ER-DB0TT *111111z5127.71NE00732.60E0438.650MHz T067 R57k DB0TT
2026-03-02 15:49:29 CET: DL4HAO-10>APMI06,TCPIP*,qAS,DL4HAO:@021449z5343.23N/01000.57E-WX3in1Plus2.0 U=13.1V,T=24.8C/testing digipeating + igate
2026-03-02 15:49:29 CET: JH7ZER-S>APDG01,TCPIP*,qAC,JH7ZER-GS:;JH7ZER B *021449z4047.97ND14048.63EaRNG0001/A=000010 70cm Voice (D-Star) 438.71000MHz +0.0000MHz
2026-03-02 15:49:29 CET: EL-F5KDB>RXTLM-1,TCPIP,qAR,F5KDB::EL-F5KDB :UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:29 CET: SV1BLD-Y>APDG03,TCPIP*,qAC,SV1BLD-YS:!3757.56N/02311.95Er/A=00000070cm MMDVM Voice (C4FM) 433.80000MHz +0.0000MHz, APRS for YSFGateway
2026-03-02 15:49:29 CET: SV1BLD-Y>APDG03,qAS,SV1BLD:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:29 CET: DG7FDL>APSVX1,TCPIP*,qAC,T2TAS:;EL-DG7FDL*111111z5052.25NE00941.71E0PHG2250/145.525MHz toff R13k Bad Hersfeld 256m-NN - Node: 817631
2026-03-02 15:49:29 CET: EL-F5KDB>RXTLM-1,TCPIP,qAR,F5KDB:T#027,0.00,0.02,0,3,0.0,00000000,SimplexLogic
2026-03-02 15:49:29 CET: GW3591>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3919.68N/07637.43W_072/002g003t032r000p000P000h47b10393L224AmbientCWOP.com
2026-03-02 15:49:29 CET: KA8ZGE-14>APDR16,TCPIP*,qAC,T2BIO:=4050.97N/08435.58Wu086/001/A=000705 No Ham Equipment
2026-03-02 15:49:29 CET: YM3KB-1>AESPG4,TCPIP*,qAC,T2ROMANIA:T#355,0,3,0,35,11.92,00000000,@ 13 37 35 2 3 2026
2026-03-02 15:49:29 CET: N5LSN>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3430.27N/09229.89W_066/002g004t055r000p000P000h75b10213L154AmbientCWOP.com
2026-03-02 15:49:29 CET: KF7RZH-9>SRTUST,KE7JVX-3*,WIDE1,WIDE2-1,qAR,AK7Z-1:`'D6lrSk/`"8y}447.725MHz T100 -500_4
2026-03-02 15:49:29 CET: VU3KEB-S>APDG01,TCPIP*,qAC,VU3KEB-GS:;VU3KEB B *021449z1253.43ND07732.31EaRNG0001/A=000010 70cm Voice (D-Star) 434.50000MHz +0.0000MHz
2026-03-02 15:49:29 CET: N1ORT>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z4254.74N/07106.94W_094/003g005t017r000p000P000h31b10371L509AmbientCWOP.com
2026-03-02 15:49:29 CET: GW4196>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4035.23N/07648.99W_069/006g009t024r000p000P000h64b10376L231AmbientCWOP.com
2026-03-02 15:49:29 CET: GW6374>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4322.37N/07804.75W_040/006g007t019r000p000P000h64b10390L268AmbientCWOP.com
2026-03-02 15:49:29 CET: CW4226>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4132.24N/07557.31W_057/002g004t020r000p000P000h55b10337L218AmbientCWOP.com
2026-03-02 15:49:29 CET: KM4NDN>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z4122.45N/08247.47W_067/003g008t024r000p000P000h57b10322L323AmbientCWOP.com
2026-03-02 15:49:29 CET: EL-EA3RKD>APSVX1,WIDE1-1,qAS,EA3RKD:T#076,000,017,000,001,000,00000000
2026-03-02 15:49:29 CET: FW9130>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4104.49N/07523.34W_119/000g000t017r000p000P000h63b10360L166AmbientCWOP.com
2026-03-02 15:49:29 CET: HS6BUE-3>AESPG4,TCPIP*,qAC,T2TOKYO:=1656.25N/10009.33E_000/000g000t088r...p...P...h45b10030L000 T=31<0xc2><0xb0> H=45% P=1003 PM 1:[13] , 2.5:[22] , 10:[28] (<0xce><0xbc>g/m3)
2026-03-02 15:49:29 CET: GW3666>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4651.84N/12233.15W_028/000g000t029r000p000P000h99b10183L000AmbientCWOP.com
2026-03-02 15:49:29 CET: LA9TLA>APU25N,LD5AV*,WIDE3-2,qAR,LA8NRA:=5900.30N/00543.30E- {UIV32N}
2026-03-02 15:49:29 CET: 9A0URV>APBM1S,TCPIP*,qAS,BM2222:@021449z4504.08N/01338.40ErPHG0000DMR / C4FM / D-STAR / 438.7750/431.1750 CC1
2026-03-02 15:49:29 CET: 9A0URV>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=219017
2026-03-02 15:49:29 CET: KD5KJZ-9>APAT51,WIDE1-1,WIDE2-1,qAR,K5VZC-1:!3230.86N/09554.31Wk314/000/A=000478KD5KJZ VZCARES.ORG
2026-03-02 15:49:29 CET: KQ4JKJ>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z2824.23N/08040.67W_038/002g003t076r000p000P000h67b10175L426AmbientCWOP.com
2026-03-02 15:49:29 CET: GW3677>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z2913.72N/08233.34W_082/003g005t066r000p000P000h84b10231L428AmbientCWOP.com
2026-03-02 15:49:29 CET: GW3701>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3534.67N/09723.94W_248/002g002t039r000p000P000h90b10206L080AmbientCWOP.com
2026-03-02 15:49:29 CET: GW3710>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4422.64N/08925.94W_220/003g010t026r000p000P000h79b10279L124AmbientCWOP.com
2026-03-02 15:49:29 CET: FW3614>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3015.84N/08127.25W_092/005g006t065r000p000P000h89b10243L370AmbientCWOP.com
2026-03-02 15:49:29 CET: N7JJY-4>APN391,qAR,N0SZ-2:;449.300WY*111111z4103.32N/10447.03WrT131.8 -5.00 ANALOG
2026-03-02 15:49:29 CET: N2SKY-5>APRS,TCPIP*,qAC,T2SYDNEY:=4029.62N/10510.90WrPHG0330/448.400 PL 94.8 SkyHubLink.com NoCo
2026-03-02 15:49:29 CET: GB7BWR>HEARD,WIDE1-1,qAR,GB7RDG:GB7RDG GB7OXF GB7LOX GB7WEM GB7BPQ PD4R GB7BMY PI1LAP [Unsupported packet format]
2026-03-02 15:49:29 CET: TB8ATA-13>AESPG4,TCPIP*,qAC,T2BELGIUM::TB8ATA-13:EQNS.0,1,0,0,1,0,0,1,0,0,-1,0,0,1,0
2026-03-02 15:49:29 CET: KO6GGW-Y>APDG03,TCPIP*,qAC,KO6GGW-YS:!3344.52NW11656.75Wi/A=00000070cm MMDVM Voice (C4FM) 445.35000MHz +0.0000MHz, APRS for YSFGateway
2026-03-02 15:49:29 CET: KO6GGW-Y>APDG03,qAS,KO6GGW:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:29 CET: W5ITZ>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z2857.94N/09629.49W_114/002g002t064r000p000P000h98b10198L188AmbientCWOP.com
2026-03-02 15:49:29 CET: FW9191>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z5113.44N/11442.41W_059/005g008t019r000p000P000h10b10079L016AmbientCWOP.com
2026-03-02 15:49:29 CET: WA8WDQ-D>APDG03,TCPIP*,qAC,WA8WDQ-DS:!4330.62ND07207.27W&/A=00000070cm MMDVM Voice (DMR) 444.36250MHz +5.0000MHz, APRS for DMRGateway
2026-03-02 15:49:29 CET: DL1MKJ-10>APLRG1,TCPIP*,qAC,T2CAEAST:!L4[*VP@%va GLoRa APRS iGate
2026-03-02 15:49:29 CET: GW5985>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z1818.07N/06709.86W_157/001g003t087r000p000P000h64b10186L652AmbientCWOP.com
2026-03-02 15:49:29 CET: GW0191>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z2959.52N/09531.26W_295/000g001t069r000p000P000h87b10232L099AmbientCWOP.com
2026-03-02 15:49:29 CET: GW0490>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4359.07N/07102.44W_005/000g002t014r000p000P000h40b10336L348AmbientCWOP.com
2026-03-02 15:49:29 CET: HK4AM-12>APLRG1,TCPIP*,qAC,T2BRAZIL:!LKCr^;D9^a GLoRa_iGate_HKNET_HK6RM Batt=5.16V
2026-03-02 15:49:29 CET: YM2KDB-3>APRS,WIDE2-2,qAR,YM2KY-1:>www.anarad.org.tr
2026-03-02 15:49:29 CET: DH0GSU-10>APLG01,TCPIP*,qAC,T2SWEDEN:=4859.10NL00823.52E&Uwe A03 - LoRa iGATE
2026-03-02 15:49:29 CET: G3RJI-9>UQ3TS4,WIDE1-1,WIDE2-1,qAR,MB7UUE:`v+Enqv>/`"49}145.525MHz alan mobile_%
2026-03-02 15:49:29 CET: K4FZN-10>APLRG1,TCPIP*,qAC,T2BC:=L<kvH:(LJa !GLoRa APRS
2026-03-02 15:49:29 CET: DG2HAM-10>APLRG1,TCPIP*,qAC,T2PERTH:!L3GH3Pyo*& GLoRa APRS iGate LILYGO T3 ONLY RX|#6&8|
2026-03-02 15:49:29 CET: 9A0DRJ>APBM1S,TCPIP*,qAS,BM2222:@021449z4512.91N/01433.59ErPHG0000DMR / C4FM / D-STAR / 438.9875/431.3875 CC1
2026-03-02 15:49:29 CET: EL-E75BM>RXTLM-1,TCPIP,qAR,E75BM:T#762,0.00,0.01,0,6,0.0,00000000,SimplexLogic
2026-03-02 15:49:29 CET: 9A0DRJ>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=219024
2026-03-02 15:49:29 CET: GW6398>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3506.58N/10615.58W_220/008g011t052r000p000P000h35b10218L146AmbientCWOP.com
2026-03-02 15:49:29 CET: KD0RIH>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3947.75N/09332.65W_331/001g001t034r000p003P003h89b10258L095AmbientCWOP.com
2026-03-02 15:49:29 CET: N3WMC>APWW11,TCPIP*,qAC,T2PERTH:T#068,100,048,002,128,000,10000000
2026-03-02 15:49:29 CET: KD4UFD>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3600.72N/08404.27W_039/003g003t049r000p004P004h90b10260L067AmbientCWOP.com
2026-03-02 15:49:29 CET: GW6839>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4302.48N/08245.74W_150/002g006t023r000p000P000h63b10347L285AmbientCWOP.com
2026-03-02 15:49:29 CET: FW8827>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4025.78N/07951.52W_273/002g002t026r000p000P000h64b10336L080AmbientCWOP.com
2026-03-02 15:49:29 CET: GW4455>APRS,TCPXX*,qAX,CWOP-6:/021449z4251.67N/10616.18W_269/003g007t045P000b10122h74cwMServer
2026-03-02 15:49:29 CET: FW9185>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3214.94N/11055.46W_151/000g000t060r000p000P000h40b10154L066AmbientCWOP.com
2026-03-02 15:49:29 CET: SP9INZ-6>UPQW49,SR9WXS*,WIDE1*,WIDE2-1,qAR,SR9KFZ:`/&n<0x1c><0x1c>v[/"7j}145.500 MHz //73!
2026-03-02 15:49:29 CET: VE3RLR-10>APDW17,TCPIP*,qAC,T2VAN:T#900,45.7,0.00,0.01,0.00
2026-03-02 15:49:29 CET: KB0Y>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3044.58N/08323.70W_188/003g005t066r000p000P000h79b10289L147AmbientCWOP.com
2026-03-02 15:49:29 CET: 9A0DRR>APBM1S,TCPIP*,qAS,BM2222:@021449z4449.65N/01459.55ErPHG0000Radio klub Rijeka 9A1ARS 438.4375/430.8375 CC8
2026-03-02 15:49:29 CET: K0AMC-5>APDW17,TCPIP*,qAC,T2MCI:!3818.68NT09026.18W&
2026-03-02 15:49:29 CET: HS5SQI-4>AESPG4,TCPIP*,qAC,APRSTH::HS5SQI-4 :UNIT.Pkt,Pkt,Pkt,dBm,V
2026-03-02 15:49:29 CET: HS5SQI-4>AESPG4,TCPIP*,qAC,APRSTH::HS5SQI-4 :EQNS.0,1,0,0,1,0,0,1,0,0,-1,0,0,1,0
2026-03-02 15:49:29 CET: HS5SQI-4>AESPG4,TCPIP*,qAC,APRSTH:T#309,3,1,0,72,21.00,00000000
2026-03-02 15:49:29 CET: BG3HPB-1>APRS4G,TCPIP*,qAC,T2NANJING:>https://aprs.tv 1.0.30
2026-03-02 15:49:29 CET: F4JWQ-S>APDG01,TCPIP*,qAC,F4JWQ-GS:;F4JWQ B *021449z4843.58ND00122.13EaRNG0001/A=000010 70cm Voice (D-Star) 438.80000MHz -4.0000MHz
2026-03-02 15:49:29 CET: DH1GAP-13>APGE01,qAS,DH1GAP-10:!4750.27N/00855.56E_112/000g...t052r000p000h75b10304
2026-03-02 15:49:29 CET: YO5CEA-10>TVQX91,YO8CLN-2,WIDE1*,WIDE2-1,qAR,YO8A-10:`3Gkl!MYY`"6Z}_)
2026-03-02 15:49:29 CET: F5RQX-9>TUQYVR,WIDE1-1,WIDE2-1,qAR,F6ZAF:`v)Xp,<0x1f>R/`"5<}_%
2026-03-02 15:49:29 CET: 9A0DRR>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=219029
2026-03-02 15:49:30 CET: YO7IUM>APRS,TCPIP*,qAC,T2FRANCE:>Transmission ended
2026-03-02 15:49:30 CET: BD5DOY-8>AP51G2,TCPIP*,qAC,T2CS:!1524.90S/02816.88Ej349/018/A=004208<0xe6><0x99><0xae><0xe6><0x8b><0x89><0xe5><0xa4><0x9a> TEL:0573752436 1.996Km 148m 13.2V S08
2026-03-02 15:49:30 CET: EL-DL0ABC>RXTLM-1,TCPIP,qAR,DL0ABC::EL-DL0ABC:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:30 CET: DJ0MDC>APREST,TCPXX*,qAX,CWOP-3:@021448z4851.51N/01034.23E_288/000g000t055r000P000h72b10210L162eREST
2026-03-02 15:49:30 CET: EL-DL0ABC>RXTLM-1,TCPIP,qAR,DL0ABC:T#076,5908208.00,0.01,0,1,0.0,00000000,TetraLogic
2026-03-02 15:49:29 CET: OE7CIT-1>APLG01,TCPIP*,qAC,T2LAUSITZ:=4715.87NL01119.79E&LoRa iGATE Info: github.com/lora-aprs/LoRa_APRS_iGate
2026-03-02 15:49:29 CET: W4MRL-N>APDG03,TCPIP*,qAC,W4MRL-NS:!3306.00ND08619.80W&/A=00000070cm MMDVM Voice (C4FM) 438.60000MHz +0.0000MHz, W4MRL_Pi-Star_ND
2026-03-02 15:49:29 CET: HB9GYR-10>APMI06,TCPIP*,qAC,T2PRT::HB9GYR-10:BITS.11111111,HB9GYR-10 Telemetry
2026-03-02 15:49:29 CET: CE3ABZ-6>APDR16,TCPIP*,qAC,T2SWEDEN:=3317.64S/07052.08We251/003/A=001626 https://aprsdroid.org/
2026-03-02 15:49:29 CET: JJ2YAA-1>APTT4,WIDE1-1,WIDE2-1,qAO,JO2OKP-10:/000000h3446.10N113710.34E#000/000is 144.66MHz 1200bps 27C
2026-03-02 15:49:29 CET: GW0175>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3238.32N/10422.97W_000/011g012t050r000p000P000h54b10170AmbientCWOP.com
2026-03-02 15:49:29 CET: CN8VX>APSVX1,TCPIP*,qAS,CN8VX-16:T#034,000,015,000,001,000,00000000
2026-03-02 15:49:29 CET: W7PLC>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z4702.49N/12257.73W_358/000g000t031r000p000P000h99b10172L000AmbientCWOP.com
2026-03-02 15:49:29 CET: KK7DBC-D>APDG03,TCPIP*,qAC,KK7DBC-DS:!4810.78NW11702.60Wi/A=00000070cm MMDVM Voice (DMR) 439.00000MHz -5.0000MHz, APRS for DMRGateway
2026-03-02 15:49:29 CET: KK7DBC-D>APDG03,qAS,KK7DBC:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:29 CET: BJ8582>APN000,TCPIP*,qAC,T2TOKYO:=2929.20N/10626.65E$vlink.cc/bj8582 4.3V
2026-03-02 15:49:29 CET: GW0043>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3013.87N/08947.38W_350/002g004t061r000p000P000h99b10190L168AmbientCWOP.com
2026-03-02 15:49:29 CET: GW0469>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z2635.65N/08136.78W_149/000g000t075r000p000P000h82b10189L500AmbientCWOP.com
2026-03-02 15:49:29 CET: GW0496>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4132.93N/07036.53W_342/000g001t023r000p000P000h47b10382L342AmbientCWOP.com
2026-03-02 15:49:29 CET: K2UWC-R>APDG03,TCPIP*,qAC,K2UWC-RS:!4029.38ND07516.55W&/A=00000070cm MMDVM Voice (C4FM) 441.10000MHz +5.0000MHz, K2UWC_Pi-Star_RPT
2026-03-02 15:49:29 CET: OE3OSB>APMI06,TCPIP*,qAC,T2AUSTRIA::OE3OSB :PARM.Vin,Rx1h,Dg1h,T@Dach,T@SDR,O1,O2,O3,O4,I1,I2,I3,I4
2026-03-02 15:49:29 CET: KE7JVX-3>CQ,qAR,N7GEE-10:@021340z3316.93N/11049.27W_275/009g015t048r000p000P000h21b10138.DsVP
2026-03-02 15:49:29 CET: GW1992>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4008.65N/07527.53W_058/001g001t028r000p000P000h43b10411L174AmbientCWOP.com
2026-03-02 15:49:29 CET: OE3OSB>APMI06,TCPIP*,qAC,T2AUSTRIA::OE3OSB :UNIT.Volt,Pkt,Pkt,C,C,On,On,On,On,Hi,Hi,Hi,Hi
2026-03-02 15:49:30 CET: JH4GQC>APU25N,TCPIP*,qAC,T2FUKUOKA:;WIR-32062*290314z3415.48NW13231.55E0#42062(20:00-)#23496(00:00-)/144.60/88.5
2026-03-02 15:49:30 CET: BM7IIG-1>APTMAG,TCPIP*,qAC,T2OSAKA:T#685,107,1,82,4,21,00000000
2026-03-02 15:49:30 CET: OE3OSB>APMI06,TCPIP*,qAC,T2AUSTRIA::OE3OSB :EQNS.0,0.075,0,0,10,0,0,10,0,0,0.5,-64,0,0.5,-64
2026-03-02 15:49:30 CET: OE3OSB>APMI06,TCPIP*,qAC,T2AUSTRIA::OE3OSB :BITS.11111111,WX3in1Plus20 Telemetry
2026-03-02 15:49:30 CET: IW0DSR>APBM1S,TCPIP*,qAS,BM2222:@021449z4203.62N/01248.09ErPHG0000Monte Gennaro (RM) - 430.6000/435.6000 CC1
2026-03-02 15:49:30 CET: IW0DSR>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222004
2026-03-02 15:49:30 CET: VE6WDM-10>APFII0,TCPIP*,qAC,APRSFI:@144923h1018.97N\10727.04Es070/014/A=000013aprs.fi for iOS!wID!
2026-03-02 15:49:30 CET: KK5PP-3>APN383,qAR,W5NGU-3:!3255.88N/09627.61W#PHGD9560/DIGI Rockwall County, TX
2026-03-02 15:49:30 CET: KMASWX>APOT30,VE7RLO,VE7CHW,qAO,VA7AV-5:>www.kmasrc.ca WX Stn, ve7fsr@rac.ca
2026-03-02 15:49:30 CET: W4PVW-15>APMI06,WIDE2-2,qAR,KD4IEZ-1:@021352z3138.38N/08344.96W# CPARC Turner County GA
2026-03-02 15:49:29 CET: GW3580>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3650.51N/11951.85W_303/002g002t051r000p000P000h85b10167L010AmbientCWOP.com
2026-03-02 15:49:29 CET: W0AKO>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3700.20N/11330.96W_155/001g002t051r000p000P000h10b10143L039AmbientCWOP.com
2026-03-02 15:49:29 CET: HK2LS-1>APOT30,HK4LRM-14,WIDE2*,qAR,HK4D-3:!0607.00N/07529.62Wr 06.4V 55C
2026-03-02 15:49:29 CET: IV3DVE-8>APLRT1,WIDE1-1,qAU,IQ3AZ-11:=/7@eMQu]8>:eQ
2026-03-02 15:49:29 CET: EA5IHI-13>AESPG4,TCPIP*,qAC,T2BIO:@021449z3823.69N/00030.85W_Estacion Meteorologica Wx T=16<0xc2><0xb0> H=72% P=1003
2026-03-02 15:49:29 CET: F5OPV-9T>APSFLT,WIDE1-1,qAR,F1ZVC-10:!/8@k=N=Q&>oM[
2026-03-02 15:49:29 CET: FW8609>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4204.04N/12414.31W_177/010g011t048r000p000P000h99b10172AmbientCWOP.com
2026-03-02 15:49:29 CET: HP1UPR-1>APET51,TCPIP*,qAC,T2USANE:!0913.80N/07923.00W#UNI<0xd3>N PANAME<0xd1>A DE RADIO AFICIONADOS UPRA, DMR TG7146 12.3V
2026-03-02 15:49:29 CET: YB3RDW-13>APREST,TCPIP*,qAC,SEVENTH:@021448z0717.98S/11239.17E_125/002g002t076r000P179h97b10120L000eREST
2026-03-02 15:49:29 CET: GW0637>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4856.61N/11049.45W_333/000g000t030r000p000P000h61b10133L026AmbientCWOP.com
2026-03-02 15:49:30 CET: NF4J-9>S0SX1R,N4TAE-15*,WIDE2-1,qAR,KE4BFX-1:`t'I gkK\]"4O}= [Location changes too fast (adaptive limit)]
2026-03-02 15:49:30 CET: OE3HCH>APOSB4,TCPIP*,qAC,T2PERTH:@021449z4806.66N/01623.30Ey/A=000600SharkRF openSPOT4
2026-03-02 15:49:30 CET: FW0443>APRS,TCPXX*,qAX,CWOP-7:/021449z3838.14N/12055.27W_128/002g005t050r000p000P000h00b11082RainwiseNet-MKIII
2026-03-02 15:49:30 CET: YG1CBG-13>AESPG4,TCPIP*,qAC,T2TAIWAN:@021449z0654.57S/10742.77E_WX Station T=32<0xc2><0xb0> H=0% P=931
2026-03-02 15:49:30 CET: FW8421>APN000,TCPXX*,qAX,CWOP-4:@021449z3410.15S/05944.06W_045/014g022t080r000p000P000b10071h56eMB55
2026-03-02 15:49:30 CET: PD5RM-9>UR2UR0,WIDE1-1,WIDE2-1,qAO,PB2N-10:`zRgms*>/`"3I}430.250MHz T088 +160 Robin QRV Mobiel_1
2026-03-02 15:49:30 CET: EW4437>APRS,TCPXX*,qAX,CWOP-7:@021447z3800.75N/08539.78W_091/001g005t033r004p020P020h89b10277eCumulusDsVP
2026-03-02 15:49:30 CET: N0EO-C>APDG02,TCPIP*,qAC,N0EO-CS:!4647.29ND09206.86W&RNG0001/A=000010 2m Voice (D-Star) 147.97500MHz -0.6000MHz
2026-03-02 15:49:30 CET: JR4WE-I>APIRP2,TCPIP*,qAC,JR4WE-IS:!3522.02ND13245.28E&D-STAR -> APRS
2026-03-02 15:49:30 CET: JR4WE-I>APIRP2,TCPIP*,qAC,JR4WE-IS:<IGATE,MSG_CNT=0,LOC_CNT=1
2026-03-02 15:49:30 CET: dl1er-9>APRS,TCPIP*,qAC,T2NUERNBG:!5118.39N/00621.83Eb327/007
2026-03-02 15:49:30 CET: KK6MIG-13>APN000,TCPIP*,qAC,T2DENMARK:@021449z3744.64N/12228.95W_225/003g005t054r000p005P005b10161h01L007eMB57
2026-03-02 15:49:30 CET: ED1ZAW-3>APMI06,WIDE3-3,qAR,ED1ZAD-3:@021407z4251.23N/00511.57W#PHG4160/Radioaficionados de Leon URLE Volt.=13.9V,Temp=7.9C 1605m.
2026-03-02 15:49:30 CET: NI2O>APWEE5,TCPIP*,qAC,CWOP-6:@021445z4002.78N/07524.25W_004/004g006t026r000p000P000b10371h48L189.weewx-5.2.0-ambientweatherapi
2026-03-02 15:49:30 CET: YF9UAG-17>AESPG4,TCPIP*,qAC,T2TAIWAN:T#017,0,0,0,78,0,00000000,@ 14 49 28 2 3 2026
2026-03-02 15:49:30 CET: K4SIP-N>APDG04,TCPIP*,qAC,K4SIP-NS:!3507.96ND08506.61W&/A=00000070cm MMDVM Voice (NXDN) 446.90000MHz +0.0000MHz, K4SIP_Pi-Star_ND
2026-03-02 15:49:30 CET: DG5MLA-9>TX2P6L,DB0TC-10*,qAU,DO7HS-10:''G<0x1c> ,_>/>"8#}Gerald on Tour-DOK T08=
2026-03-02 15:49:30 CET: IR0UFZ>APBM1S,TCPIP*,qAS,BM2222:@021449z4112.43N/01334.57ErPHG0000MONTE ORLANDO - GAETA (LT) 430.1000/435.1000 CC1
2026-03-02 15:49:30 CET: IR0UFZ>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222012
2026-03-02 15:49:30 CET: LU2DKV-10>AESPG4,TCPIP*,qAC,T2SYDNEY::LU2DKV-10:UNIT.Pkt,Pkt,Pkt,dBm,V
2026-03-02 15:49:29 CET: KC3ROS>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z4038.66N/08010.23W_077/005g007t023r000p000P000h69b10315L147AmbientCWOP.com
2026-03-02 15:49:29 CET: F6BAZ-10>APLRG1,WIDE1-1,qAO,DB9VE-10:=L5W_9P+WD& !GLoRa APRS Batt=4.24V
2026-03-02 15:49:29 CET: GW1967>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4405.25N/07319.31W_059/000g003t014r000p000P000h50b10416L430AmbientCWOP.com
2026-03-02 15:49:29 CET: GW1985>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4502.32N/09325.63W_200/000g000t029r000p000P000h68b10265L130AmbientCWOP.com
2026-03-02 15:49:29 CET: SQ5H-3>APRS,TCPIP*,qAC,T2FINLAND:=5218.91N/02053.31E<039/025/GPRS Tracker // Sat: 8 // Bat: 4.13V /A=000306
2026-03-02 15:49:29 CET: SQ5H-3>APRS,TCPIP*,qAC,T2FINLAND:> Honda NX500
2026-03-02 15:49:29 CET: LU4AE-18>APDPRS,TCPIP*,qAS,LU4AE-24:!3441.13S/05828.51WrDMR (TX:434000/RX:439000) Tmp: 51.5 C, PWS: Normal, Usage: 5%, Disk: 41% TLM by arSpot v1.7.1 20230510
2026-03-02 15:49:29 CET: SR6NWS>APMI06,TCPIP*,qAC,T2POLAND:@021449z5040.82NI01629.14E# W1 SP1 Wielka Sowa 12.1V, 7.0C
2026-03-02 15:49:30 CET: N7JJY-4>APN391,qAO,WB7GR-10:;462.600WY*111111z4101.32N/10447.03WrT141.3 +5.00 GMRS RPTR
2026-03-02 15:49:30 CET: BM4LQC-1>APTMAG,TCPIP*,qAC,T2HK:!2504.75N/12128.42EI<0xe5><0x99><0x93><0xe5><0xaf><0x92><0xe5><0x95><0x8f><0xe6><0x9a><0x96><0xe7><0x85><0xa7><0xe4><0xb8><0x89><0xe9><0xa4><0x90> <0xe6><0xa6><0xa8><0xe4><0xb9><0xbe><0xe8><0x8d><0xb7><0xe5><0x8c><0x85><0xe4><0xba><0xba><0xe8><0x92><0xb8><0xe7><0x99><0xbc>
2026-03-02 15:49:30 CET: N3OQM-N>APDG03,TCPIP*,qAC,N3OQM-NS:!4100.00ND08000.00E&/A=00000070cm MMDVM Voice (C4FM) 446.50000MHz +0.0000MHz, N3OQM_Pi-Star_ND
2026-03-02 15:49:30 CET: VK4MJM-3>APMI04,WIDE2-2,qAR,VK4GDW-3:>Join us for the John Moyle Memorial Weekend, 18/19 March 2023
2026-03-02 15:49:30 CET: EW4319>APRS,TCPXX*,qAX,CWOP-5:@021449z3840.55N/09000.47W_088/001g006t035r000p015P004h90b10237L042eCumulusDsVP
2026-03-02 15:49:30 CET: OK2ZIL-10>APDR16,TCPIP*,qAC,T2LAUSITZ:=4907.95N/01726.35E>203/001/A=000761 Z<0xc3><0xa1>silkovna - on the route
2026-03-02 15:49:30 CET: VK4YHT-8>RSR2W7,WIDE1-1,WIDE2-1,qAR,VK4RAR-3:`N;$<0x1c><0x1e>YY/`"3x}X1C3 4.8V S22
2026-03-02 15:49:30 CET: KF8EXA>APN000,TCPIP*,qAC,T2TEXAS:=4902.85N/12245.68W[I don't hoard radios, I curate redundancy
2026-03-02 15:49:30 CET: EW3105>APRS,TCPXX*,qAX,CWOP-5:@021448z3014.30N/09755.18W_180/008g014t066r000p000P000h83b10182.DsVP
2026-03-02 15:49:30 CET: K9FB-N>APDG03,TCPIP*,qAC,K9FB-NS:!4026.68ND08606.97W&/A=00000070cm MMDVM Voice (C4FM) 434.93750MHz +0.0000MHz, K9FB_Pi-Star_ND
2026-03-02 15:49:30 CET: MACPAS>APN382,WIDE1-1,qAR,K7MT:!4635.67ND11217.92W#PHG5490/W2,MTn-N,WR7HLN MACPASS
2026-03-02 15:49:30 CET: VK6MRG-7>APOTC1,WIDE1-1,WIDE2-2,qAR,VK6MRG-1:/144928h3333.90S/11726.07Ej044/000/A=001020 13.8V 28C HDOP01.2 SATS08Hello from Matty G. Prado
2026-03-02 15:49:30 CET: W4PL-C>APJI43,TCPIP*,qAC,W4PL-GS:!3509.30ND08518.33W&RNG0055 2m Voice 145.290 -0.600 Mhz
2026-03-02 15:49:30 CET: N1DTA-2>APMI06,TCPIP*,qAS,N1DTA:@021404z3729.21NI08441.83W# 13.9V, ??.?F, NEW REPEATER 444.9125 + CC1
2026-03-02 15:49:30 CET: K4DFQ-D>APDG03,TCPIP*,qAC,K4DFQ-DS:!4100.00ND07300.00W&/A=00000070cm MMDVM Voice (DMR) 432.10000MHz +0.0000MHz, APRS for DMRGateway
2026-03-02 15:49:30 CET: EA3GKP-7>APRS,TCPIP*,qAC,T2CSNGRAD:=4123.71N/00207.56E&MiniWX Station Barcelona (v1.1f)
2026-03-02 15:49:30 CET: EA3GKP-7>APRS,TCPIP*,qAC,T2CSNGRAD:=4123.71N/00207.56E_.../...g...t062r...p...P...h100b10242
2026-03-02 15:49:30 CET: EA3GKP-7>APRS,TCPIP*,qAC,T2CSNGRAD:T#496,084,294,000,000,000,00000000
2026-03-02 15:49:30 CET: EA3GKP-7>APRS,TCPIP*,qAC,T2CSNGRAD::EA3GKP-7 :PARM.RSSI,VBAT
2026-03-02 15:49:30 CET: EA3GKP-7>APRS,TCPIP*,qAC,T2CSNGRAD::EA3GKP-7 :UNIT.dbm,V
2026-03-02 15:49:30 CET: EA3GKP-7>APRS,TCPIP*,qAC,T2CSNGRAD::EA3GKP-7 :EQNS.0,-1,0,0,0.01,0,0,0,0,0,0,0,0,0,0
2026-03-02 15:49:30 CET: EA3GKP-7>APRS,TCPIP*,qAC,T2CSNGRAD::EA3GKP-7 :BITS.00000000,MiniWX Station Barcelon
2026-03-02 15:49:30 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.40N/01603.36EQ253/050/A=000643 MOBILE DMR TG:21966
2026-03-02 15:49:30 CET: LA9SHA>APGLR1,WIDE1-1,qAR,LB1R-10:!5940.35N/00940.88E-Board B (FW v0.61d)
2026-03-02 15:49:30 CET: F5UJA-7>APRSGW,TCPIP*,qAR,F5UJA-7:!4829.65N/00727.42E-Michel JN38RL#F5UJA/B=099/A=000620
2026-03-02 15:49:30 CET: FW0574>APRS,TCPXX*,qAX,CWOP-7:@021449z3716.10N/12158.30W_270/000g000t051r000p000P000b10128h98eMB39
2026-03-02 15:49:30 CET: I0KTE-10>I0KTE,TCPIP*,qAC,IZ3MEZ-G::I0KTE :Test-A0,0000,1095,1100
2026-03-02 15:49:30 CET: W5ANR-9>APN391,DECATU*,WIDE2-1,qAO,DODD:!3504.34NS09440.55W#PHG5800/ Cavanal Hill; Poteau, OK
2026-03-02 15:49:30 CET: N2PYI-2>APRX26,TCPIP*,qAC,T2USANW:T#241,9.0,7.7,39.0,5.0,32.0,00000000
2026-03-02 15:49:30 CET: BH2TQT-29>NRLBOX,TCPIP*,qAC,T2TAIWAN:>E1[NRL_2100A_D4_260205 12.4V RX:438.6250:100.0 TX:438.6250:100.0] HELLO
2026-03-02 15:49:30 CET: W4PL-A>APJI43,TCPIP*,qAC,W4PL-GS:!3509.30ND08518.33W&RNG0035 1.2 Voice RNG0035 1.2 Voice 1291.0000 -20.00 Mhz
2026-03-02 15:49:30 CET: PU2OMB-Y>APDG03,TCPIP*,qAC,PU2OMB-YS:!2400.74S/04625.44W&/A=00000070cm MMDVM Voice (C4FM) 439.06500MHz +0.0000MHz, APRS for YSFGateway
2026-03-02 15:49:30 CET: PU2OMB-Y>APDG03,qAS,PU2OMB:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:30 CET: KMASWX>APOT30,VE7RLO,VE7CHW,qAR,VE7TPK:!5050.42N/12016.12W_ 13.7V
2026-03-02 15:49:30 CET: EA7EGJ>APSVX1,TCPIP*,qAC,T2AUSTRIA:T#076,000,017,000,008,000,00000000
2026-03-02 15:49:30 CET: YM3KRM>APBM1S,TCPIP*,qAS,BM2841:@021401z3941.02N/02759.88ErPHG0000AKRAD - Anadolu Kesintisiz Iletisim Radyo Amat<0xc3><0xb6>rleri Dernegi 439.1625/431.5625 CC1
2026-03-02 15:49:30 CET: W4PL-A>APJI43,TCPIP*,qAC,W4PL-GS:<IGATE,MSG_CNT=0,LOC_CNT=0
2026-03-02 15:49:30 CET: ZL2TFG-C>APDG02,TCPIP*,qAC,ZL2TFG-CS:!4019.83SD17536.48E&RNG0001 2m Voice 144.55000MHz +0.0000MHz
2026-03-02 15:49:30 CET: IR0UGR>APBM1S,TCPIP*,qAS,BM2222:@021449z4145.71N/01242.80ErPHG0000IR0UGR - Loc. JN61GV 430.5500/435.5500 CC1
2026-03-02 15:49:30 CET: DW5818>APRS,TCPXX*,qAX,CWOP-3:@021449z4741.75N/00315.28W_199/004g008t060r000p004P001h76b10138.DsVP
2026-03-02 15:49:31 CET: DC1MBB-9>APRSWX,WIDE2-2,qAR,DG4MNU-10:!4811.64N/01137.14E_000/000g000t070h37b09640a413m 12.44V_WX-Munich
2026-03-02 15:49:31 CET: HB9AK-7>APMI06,qAR,HB9BB:@021449z4700.75N/00935.30E7PHG2930 WX3in1 Malanser Aelpli GR
2026-03-02 15:49:31 CET: FW2888>APRS,TCPXX*,qAX,CWOP-6:@021449z3819.62N/07724.10W_109/001g004t035r000p000P000b10287h60L183eMB50
2026-03-02 15:49:31 CET: IR0UGR>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222026
2026-03-02 15:49:31 CET: BD6JDU-9>APDR16,TCPIP*,qAC,T2DENMARK::BH1KZQ-15:73{3
2026-03-02 15:49:31 CET: DO8PGG-5>APDR16,TCPIP*,qAC,T2LAUSITZ:=5119.33N/00720.00E$277/002/A=000759 DMR+ Refl. 4671
2026-03-02 15:49:31 CET: KM4LOD-S>APJIO4,TCPIP*,qAC,KM4LOD-GS:;KM4LOD B *201519z3324.31ND08150.25WaRNG0040 440 Voice 440.550 +5.00 MHz
2026-03-02 15:49:31 CET: KM4LOD-S>APJIO4,TCPIP*,qAC,KM4LOD-GS:;KM4LOD C *201508z3324.31ND08150.25WaRNG0040 2m Voice 145.260 -0.600 MHz
2026-03-02 15:49:31 CET: K4OBI>APN000,TCPIP*,qAC,T2TAS:@021449z3622.08N/08011.12W_085/003g006t042r000p000P000b10355h77eMB63
2026-03-02 15:49:31 CET: GW5521>APRS,TCPXX*,qAX,CWOP-6:@021448z4617.85N/12216.00W_310/003g003t036r000p000P000h89b10188.DsVP
2026-03-02 15:49:31 CET: KMASWX>APOT30,VE7RLO,VE7CHW,qAO,VA7AV-5:!5050.42N/12016.12W_160/000g000t030p000h92T2WX
2026-03-02 15:49:30 CET: K4LLL-2>APDR16,TCPIP*,qAC,T2KOBLENZ:=3030.94N\08746.52Wo030/002/A=000102 https://aprsdroid.org/
2026-03-02 15:49:30 CET: LA3BTA-8>VVQY19,WIDE1-1,WIDE2-2,qAR,LA3M-2:`*afl4Q[/`"45}Ivan la3bta@la3bta.no QRV on LA7MR or 145.500 MHz CTCSS 88.5_#
2026-03-02 15:49:30 CET: IR0EF>APBM1S,TCPIP*,qAS,BM2222:@021449z4306.52N/01223.32ErPHG0000Associazione Nazionale Radioamatori CISAR - IQ0SQ Sezione di PERUGIA - Ripetitore PERUGIA CENTRO 430.7625/435.7625 CC1
2026-03-02 15:49:30 CET: IR0EF>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222011
2026-03-02 15:49:30 CET: WINLINK>APWL2K,TCPIP*,qAS,WLNK-1:;KD0TKR *020249z3856. NW10437. Wa441.075MHz Winlink VARA FM Wide Gateway
2026-03-02 15:49:30 CET: ER-EA4HCQ>APSVX1,WIDE1-1,qAS,EA4HCQ:T#076,000,020,000,004,000,00000000
2026-03-02 15:49:30 CET: KJ4SNT>APMI04,TCPIP*,qAC,T2CAEAST:@021449z3657.76N108022.46W#PHG7060/A=02315 Floyd VA finchm@swva.net
2026-03-02 15:49:30 CET: HARIN>APN383,qAO,WEBER:!4219.60NN11339.28W#PHG5940 KC7SNN Mt Harrison APRS Digi
2026-03-02 15:49:30 CET: VE2UMS-3>APN391,VE2RAW-3*,qAR,VA2HMC-1:!4538.42NS07329.44W#PHG6440/W3 APRS Digi VE2UMS-3
2026-03-02 15:49:30 CET: KC8SWY-15>APMI06,TCPIP*,qAS,KC8SWY:T#076,168,000,000,021,152,00000000
2026-03-02 15:49:30 CET: FW9712>APRS,TCPXX*,qAX,CWOP-7:@021449z5058.21N/00635.52E_147/002g008t062r000p000P000h48b10207WeatherCatV331B8H31
2026-03-02 15:49:30 CET: DF8BP-10>APGE01,TCPIP*,qAC,T2CZECH:!5209.43N/01031.10E&APRS iGate Wolfenbuettel 144,800 (RX only)
2026-03-02 15:49:30 CET: FW6434>APN000,TCPXX*,qAX,CWOP-5:@021449z5019.88N/01853.08E_193/000g003t052r000p000P000b10251h61L027eMB54
2026-03-02 15:49:30 CET: DM5SR-15>APRS,TCPIP*,qAC,T2SPAIN:!5002.99N/00810.39E>079/060 Teslalogger MQTT>APRS-IS
2026-03-02 15:49:30 CET: TOQUER>APDW17,WIDE1-1,WIDE2-2,qAR,WEBB:!3717.36NT11316.60W&PHG5140Toquerville, 5,239 feet, W7ZDL, WWW.HAMCONZION.COM
2026-03-02 15:49:30 CET: BA3ME-10>APVRT7,TCPIP*,qAC,T2CS:!3705.54N/11428.52E_000/000g...t093r...p...h00b10204 12.0V
2026-03-02 15:49:30 CET: SQ7SCC-S>APDG01,TCPIP*,qAC,SQ7SCC-GS:;SQ7SCC C *021449z5143.04ND01929.38EaRNG0001/A=000722 2m Voice (D-Star) 144.92500MHz +0.0000MHz
2026-03-02 15:49:31 CET: WW5CRT>S2SU0U,WIDE1-1,WIDE2-1,qAR,W5DCR-3:`|M=r><0x1c>>/`"5u}_4
2026-03-02 15:49:31 CET: W4PL-B>APJI43,TCPIP*,qAC,W4PL-GS:<IGATE,MSG_CNT=0,LOC_CNT=1
2026-03-02 15:49:31 CET: W4PL-B>APJI43,TCPIP*,qAC,W4PL-GS:!3509.30ND08518.33W&RNG0040 440 Voice 443.150 +5.00 Mhz
2026-03-02 15:49:31 CET: ODOT>APODOT,TCPIP*,qAC,WA7BF:!4610.29N\12334.75WjODOT Traffic Alerts - NA7Q
2026-03-02 15:49:31 CET: WD7LEE>APWW11,WIDE1-1,WIDE2-1,qAR,ELMWOD:=4459.70N/08541.22WoLeelanau County EOC
2026-03-02 15:49:31 CET: IZ0ZIP>APBM1S,TCPIP*,qAS,BM2222:@021449z4126.80N/01349.57ErPHG0000IZ0ZIP - Cassino (FR) 430.9625/435.9625 CC1
2026-03-02 15:49:31 CET: IZ0ZIP>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222030
2026-03-02 15:49:31 CET: PD2TX-7>APDR16,TCPIP*,qAC,T2GYOR:=5305.08N/00452.59E<051/001/145.300MHz pd2tx.com
2026-03-02 15:49:31 CET: OE9FWV-10>APLRG1,TCPIP*,qAC,T2CHILE:!L6Wt8Ptis_ G.../...g...t082h24b10246LoRa APRS Gisingen
2026-03-02 15:49:31 CET: E23JWE-1>AESPG4,TCPIP*,qAC,T2INDY::E23JWE-1 :PARM.PM1.0,PM2.5,PM10,RSSI,Voltage
2026-03-02 15:49:31 CET: DD5AT-10>APLRG1,TCPIP*,qAC,T2CHILE:!L4g'4P?P,& GLoRa iGATE Lohmar-Hoeffen 433.775Mhz
2026-03-02 15:49:31 CET: SNTIAM>APTT4,WIDE1-1,WIDE2-2,qAR,BRIVER:=4424.77N/12151.85W#>K7ZQU-11 digi /A=004738
2026-03-02 15:49:31 CET: S51JY-9>APBM1D,S55DCM,DMR*,qAR,S55DCM:@144927h4616.80N/01518.08E>104/000ANDREJ
2026-03-02 15:49:30 CET: N2PYI-3>APRX26,TCPIP*,qAS,N2PYI-2:T#241,0.3,0.1,1.0,0.0,1.0,00000000
2026-03-02 15:49:30 CET: EW8444>APRS,TCPXX*,qAX,CWOP-3:@021449z3541.80N/08238.05W_193/002g005t048r000p001h75b10274.wview_5_21_7
2026-03-02 15:49:30 CET: OE3BCB-14>APRS,qAO,OE3XOC-11:!4812.01N/01553.80E_090/001g009t032r...p...P...h00b0300L026LoRa WX Neulengbach
2026-03-02 15:49:30 CET: F5ADL-10>APLRG1,TCPIP*,qAC,T2MEXICO:!L8I'!P9zza !GLoRa APRS Batt=4.13V
2026-03-02 15:49:30 CET: GW1618>APN000,TCPXX*,qAX,CWOP-5:@021446z4702.72N/12254.42W_184/002g003t033r000p000P000b10183h92eMB62
2026-03-02 15:49:30 CET: ke5jzm-1>APN000,TCPIP*,qAC,T2DENMARK:@021449z3002.62N/09041.96W_105/003g007t074r000p000P000b10216h53L445eMB63
2026-03-02 15:49:31 CET: LY1LB-1>APMI06,WIDE2-1,qAO,LY1RF:;LY0RKU *021449z5453.77N/02357.46Er438.850MHz Toff -760 R50k Repeater
2026-03-02 15:49:31 CET: KC3RVI>APOSB,TCPIP*,qAC,T2PANAMA:@021449z4012.88N\07501.13W&/A=000262SharkRF openSPOT3
2026-03-02 15:49:31 CET: IT9JXC-2>APLRG1,WIDE1-1,qAO,IT9DBI-9:=L;5Y`R<v"a !GFalcone(ME) LoRa APRS I-GATE Op. Emanuele, 73 Batt=4.06V
2026-03-02 15:49:31 CET: ER-EA4URC>APSVX1,WIDE1-1,qAS,EA4URC:T#076,000,080,000,009,000,00000000
2026-03-02 15:49:31 CET: DW3695>APN000,TCPXX*,qAX,CWOP-7:@021449z4645.24N/00737.13E_180/004g005t061r000p001P001b10173h57L315eMB63
2026-03-02 15:49:31 CET: D7126>APN000,TCPXX*,qAX,CWOP-4:@021449z3912.00N/00918.00W_157/003g010t060r000p001P001b10013h81L063eMB63
2026-03-02 15:49:31 CET: 2E0RPZ-D>APDG03,TCPIP*,qAC,2E0RPZ-DS:!5021.93N\00405.07W&/A=00000070cm MMDVM Voice (DMR) 438.80000MHz +0.0000MHz, APRS for DMRGateway
2026-03-02 15:49:31 CET: 2E0RPZ-D>APDG03,qAS,2E0RPZ:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:31 CET: KO4QAH-S>APDG01,TCPIP*,qAC,KO4QAH-GS:;KO4QAH B *021449z3110.50ND08129.10WaRNG0001/A=000010 70cm Voice (D-Star) 445.60000MHz +0.0000MHz
2026-03-02 15:49:31 CET: EW5085>APN000,TCPXX*,qAX,CWOP-6:@021449z3219.80N/08028.68W_.../...g...t...r...p...P000b.....h..eMB61
2026-03-02 15:49:31 CET: 9A7KXP-12>APMI03,WIDE2-2,qAR,9A4QV-2:@021541z4459.58N/01515.94E2Solarni APRS digi Lika-Brinje U=14.8V.
2026-03-02 15:49:31 CET: EL-YO6KXQ>RXTLM-1,TCPIP,qAR,YO6KXQ::EL-YO6KXQ:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:31 CET: AI5BH-D>APDG03,TCPIP*,qAC,AI5BH-DS:!3033.71NW09748.69Wi/A=00000070cm MMDVM Voice (DMR) 446.30000MHz -5.0000MHz, APRS for DMRGateway
2026-03-02 15:49:31 CET: AI5BH-D>APDG03,qAS,AI5BH:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:31 CET: DD3UTS-13>APRSWX,TCPIP*,qAC,T2ERFURT:>WX JO60UX Temp: 12.9C Niederschlag erkannt: nein Luftfeuchte: 36.0%
2026-03-02 15:49:31 CET: EL-YO6KXQ>RXTLM-1,TCPIP,qAR,YO6KXQ:T#182,0.00,0.01,0,1,0.0,00000000,TetraLogic
2026-03-02 15:49:31 CET: HK5FCI-10>APLRG1,TCPIP*,qAC,T2PANAMA:=0327.73NL07631.70WaHKNET-LoRa_APRS_IGATE
2026-03-02 15:49:31 CET: N4SRN-13>APN000,TCPXX*,qAX,CWOP-6:@021447z4258.53N/07130.36W_060/001g005t016r000p000P000b10366h34eMB62
2026-03-02 15:49:31 CET: OE3EMC-9>APLC13,qAO,OK1LPD-1:!/5t9TR<XC>JTQLoRa-System
2026-03-02 15:49:31 CET: KP4LAC-R>APDG03,TCPIP*,qAC,KP4LAC-RS:!1759.80N/06706.02Wr/A=00000070cm MMDVM Voice (DMR) 447.97500MHz -5.0000MHz, https://www.qrz.com/db/KP4NET KP4NET/RPT TG 330050 BM TS1 CC1
2026-03-02 15:49:31 CET: IK7WPI-15>APLRG1,TCPIP*,qAC,T2PANAMA:!L9XXYRXa=# !GLoRa APRS DIGIPEATER 433.775 MHhz Batt=4.34V
2026-03-02 15:49:31 CET: KD4VIF>APW249,K4CCC-9*,WIDE2-1,qAR,W4DEX:_03021439c005s002g006t046r000p000P000h00b10303wU2K
2026-03-02 15:49:31 CET: FW3866>APRS,TCPXX*,qAX,CWOP-5:@021449z4431.47N/09935.58E_.../...g010t...r000p000P000b10186h..eMB43
2026-03-02 15:49:31 CET: DL6BZ-10>APLRG1,TCPIP*,qAC,T2CHILE:=5229.46NL00652.52E&LoRa APRS iGate LILYGo TTGo T-Beam v1.2
2026-03-02 15:49:30 CET: DW8879>APRS,TCPXX*,qAX,CWOP-4:@021449z3456.57N/08952.44E_143/000g000t056r000p003P003h95b10226WeatherCatV331B8H31
2026-03-02 15:49:30 CET: PD0PYL-5>5R0RZL,WIDE1-1,WIDE2-1,qAR,PI1GDA:`z8<0x1c>l-F-\`"3e}Test Yaesu FTM 510_5
2026-03-02 15:49:30 CET: CW6791>APRS,TCPXX*,qAX,CWOP-5:@021449z4434.83N/08805.55W_154/004g006t030r000p000P000h73b10302.DsVP
2026-03-02 15:49:30 CET: KG5RGN-13>APMI06,TCPIP*,qAC,T2BC:@021449z3645.22N/10806.74W_043/002g007t047r000p000P000h00b00000WX3in1 weather
2026-03-02 15:49:30 CET: PAMPA>APOT30,WIDE3-3,qAR,K5NOC:!3530.48N/10101.59W# 13.5V 101F PHG5560/*PCG* DIGI PAMPA, TX W5GAF
2026-03-02 15:49:31 CET: KG5RGN-13>APMI06,TCPIP*,qAC,T2BC:@021449z3645.22N/10806.74W# www.NWNMEMCOMM.com U=14.2V,T=86.0F
2026-03-02 15:49:31 CET: K6SNY-5>ID,qAO,AI6SL-5:Network Node (SNY)
2026-03-02 15:49:31 CET: GW5927>APREST,TCPXX*,qAX,CWOP-3:@021448z4728.47N/12239.21W_086/002g002t027r000P000h97b10180L004eREST
2026-03-02 15:49:31 CET: W4PL-C>APJI43,TCPIP*,qAC,W4PL-GS:<IGATE,MSG_CNT=0,LOC_CNT=0
2026-03-02 15:49:31 CET: YO4FZV-11>APMI06,TCPIP*,qAS,YO4FZV-10:@021449z4458.63N/02853.38E_022/003g004t044r000p000P...h79b10220WX3in1 weather
2026-03-02 15:49:31 CET: 9W2HFO-10>APDR16,TCPIP*,qAC,T2F5KAV:=0259.05N/10148.74Ef145.500MHz/A=000291 V40-V66 & APM.net
2026-03-02 15:49:31 CET: DO1FMI-15>APDW16,TCPIP*,qAC,T2CSNGRAD:!4737.65NR01259.70E&APRS Igate Berchtesgaden 570m NHN
2026-03-02 15:49:31 CET: AB5KT-1>APRS,TCPXX*,qAX,CWOP-3:@021449z2916.90N/09826.72W_286/002g007t068r000p000P000b10194h82eMB32
2026-03-02 15:49:31 CET: YO4FZV-11>APMI06,TCPIP*,qAS,YO4FZV-10:@021449z4458.63N/02853.38E-WX3in1Plus2.0 U=12.3V,T=21.0C/69.8F
2026-03-02 15:49:31 CET: IR3UBZ>APBM1S,TCPIP*,qAS,BM2222:@021449z4603.46N/01314.91ErPHG0000ARI RCE FVG 431.4250/433.0250 CC1
2026-03-02 15:49:31 CET: SNOW>APN383,qAR,WASHOE:!3909.25N111952.99W#PHG2830/NVn,NCAn,TEMPn/WG6D/Snow Valley Peak, NV/A=009214
2026-03-02 15:49:31 CET: KI4WXS-R>APDG03,TCPIP*,qAC,KI4WXS-RS:!3517.84N\08106.92Wo/A=00077770cm MMDVM Voice (C4FM) 444.02500MHz +5.0000MHz, Dstar/Fusion/P25/NXDN
2026-03-02 15:49:31 CET: KI4WXS-R>APDG03,qAS,KI4WXS:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:31 CET: W7JLW-10>APSK21,TCPIP*,qAC,T2PERTH:=3240.83N/11425.43Wk246/004/A=000292!SN!
2026-03-02 15:49:31 CET: DJ1JAY-14>UR1VW6,WIDE1-1,WIDE2-2,qAO,DM0SZ-1:`<0x7f>RSr#?P/`"4s}Jens https://fm-funknetz.de :-)_4
2026-03-02 15:49:31 CET: IR3UBZ>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222032
2026-03-02 15:49:31 CET: FW5122>APRS,TCPXX*,qAX,CWOP-5:@144830z3248.53N/09645.83W_013/000g006t064P000h78b10196.ewund
2026-03-02 15:49:31 CET: EL-EC4DA2>RXTLM-1,TCPIP,qAR,EC4DA2::EL-EC4DA2:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:31 CET: SP6EU-10>APLRG1,WIDE1-1,qAR,SP6AW-10:=L4XYdRU[fa !GLoRa APRS
2026-03-02 15:49:31 CET: EL-EC4DA2>RXTLM-1,TCPIP,qAR,EC4DA2:T#070,8862313.00,0.04,0,5,0.0,00000000,TetraLogic
2026-03-02 15:49:31 CET: 9A3BMY-10>APBM1D,DMR*,qAS,BM2341-10:=4616.84N/01623.48Eb360/000/A=000707Drazen
2026-03-02 15:49:31 CET: KC5DAQ-27>APN000,TCPIP*,qAC,T2TEXAS:@021449z3002.03N/09551.93W_145/009g014t067r000p000P000b10110h94L208eMB60
2026-03-02 15:49:31 CET: ED3YBB-S>APDG01,TCPIP*,qAC,ED3YBB-GS:;ED3YBB A *021449z4215.02ND00311.37EaRNG0019 1.2 Voice 1298.57500MHz -28.0000MHz
2026-03-02 15:49:31 CET: DW0479>APN000,TCPXX*,qAX,CWOP-6:@021449z2801.57N/08132.28W_107/005g013t076r000p000P000b10231h72L554eMB63
2026-03-02 15:49:31 CET: ER-EA2BKH>APSVX1,WIDE1-1,qAS,EA2BKH:T#076,000,061,000,007,000,00000000
2026-03-02 15:49:31 CET: ZS0HTN>APDW17,WIDE1-1,qAO,ZS6VOL-10:<IGATE,MSG_CNT=2,PKT_CNT=0,DIR_CNT=1,LOC_CNT=4,RF_CNT=4,UPL_CNT=13346,DNL_CNT=2173
2026-03-02 15:49:31 CET: BH4DGF-10>APVRT7,TCPIP*,qAC,T2YANTAI:!3020.94N/12045.72Er +13.6C 11.9V
2026-03-02 15:49:31 CET: DF2TH-B>APDG02,TCPIP*,qAC,DF2TH-BS:!5312.11ND00837.89E&RNG0001/A=000010 70cm Voice (D-Star) 439.95000MHz -9.4000MHz
2026-03-02 15:49:31 CET: HB0PJ-13>APRS,TCPIP*,qAS,HB0PJ:@021449z4709.86N/00930.73E_.../...t058h48b09649L062KNX / aprs-weather-submit/1.6
2026-03-02 15:49:31 CET: CW7077>APRS,TCPXX*,qAX,CWOP-6:@021449z3524.00N/08030.00W_086/000g000t046r000p000P000h69b10330.DsVP
2026-03-02 15:49:31 CET: GW3617>APREST,TCPXX*,qAX,CWOP-3:@021447z2833.83N/08120.89W_127/005g007t071r000P000h73b10240L562eREST
2026-03-02 15:49:31 CET: BG2GWN-7>APID98,WIDE1-1,qAO,BG2EFX-10:!4635.65N/12505.21E!000/000/A=000626144.640 8.5V S14
2026-03-02 15:49:31 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.40N/01603.34EQ254/050/A=000639 MOBILE DMR TG:21966
2026-03-02 15:49:31 CET: KO4QAH-B>APDG02,TCPIP*,qAC,KO4QAH-BS:!3110.50ND08129.10W&RNG0001/A=000010 70cm Voice (D-Star) 445.60000MHz +0.0000MHz
2026-03-02 15:49:31 CET: F1SFY>APZ900,WIDE1-1,WIDE2-1,qAR,F5ZFY-3:=4917.11N/00212.99E-En test
2026-03-02 15:49:31 CET: LD4SH>APOT30,WIDE2-1,qAO,LA3YNA-15:;LA6SR *111111z5846.05N/00906.35Er145.775MHz t107 -0600kHz
2026-03-02 15:49:31 CET: KC9YJD-D>APDG03,TCPIP*,qAC,KC9YJD-DS:!3807.13ND08959.98W&/A=00000070cm MMDVM Voice (DMR) 439.00000MHz -5.0000MHz, APRS for DMRGateway
2026-03-02 15:49:31 CET: OE7XXR-10>APMI06,TCPIP*,qAC,OE2XZR-10:;OE7XXR-B *181524z4727.99N/01149.59Er 438,200 -7,6MHz DSTAR DCS009A U=13.7V,T=3.8C
2026-03-02 15:49:31 CET: YM9KE>AESPG4,TCPIP*,qAC,APRSTH:>V.4.4b Rx:2058 Digi:792 Tx:4166 UpTime:74.49 DX:27-02-2026 15:55Loc TA9WDX-7 29km 320<0xc2><0xb0>
2026-03-02 15:49:31 CET: DB0SPB>APOT21,WIDE1-1,WIDE3-3,qAR,DB0NLS-10:!5057.62N/01441.40E#12.2V 29C APRS-Digi Spitzberg
2026-03-02 15:49:31 CET: JA0ZJX-10>APU25N,TCPIP*,qAC,T2FUKUOKA:=I<33(qB3h& BIgate Matsumoto 144.66MHz/1200bps {UIV32N}
2026-03-02 15:49:31 CET: IR3UJ>APBM1S,TCPIP*,qAS,BM2222:@021449z4548.67N/01332.00ErPHG0000ARI RCE FVG 430.0750/431.6750 CC1
2026-03-02 15:49:31 CET: IR3UJ>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222033
2026-03-02 15:49:31 CET: SQ8MAE-1>APLRFD,WIDE1-1,WIDE2-1,QB3N6,qAR,SR8RK:!L5<&{T"Pt# GLoRa APRS Digi Godowa|$Q!q"V!a%V"X|
2026-03-02 15:49:31 CET: YF9UAG-16>AESPG4,TCPIP*,qAC,T2TAIPEI::YF9UAG-16:PARM.RxTraffic,TxTraffic,RxDrop,RSSI,SUN
2026-03-02 15:49:31 CET: JH4GQC>APU25N,TCPIP*,qAC,T2FUKUOKA:;WX RADAR *162315z3416.22N/13235.59E`Mt.HAIGAMINE Weather radar station
2026-03-02 15:49:32 CET: GOLDWX>APOTW1,PMETTO,WIDE1*,WIDE2-2,qAR,KB7REO-10:> 13.2V (Solar) N6EID / Gold Point Ghost Town WX Station
2026-03-02 15:49:32 CET: PY3JRT-15>APNU3B,WIDE1-1,WIDE2-1,qAR,PY3NZ-10:!3023.42S/05144.39W#PHG6000/REDE SUL APRS BARAO DO TRIUNFO RS
2026-03-02 15:49:32 CET: KB1YJ-1>APOT40,TCPIP*,qAC,T2CHILE:>
2026-03-02 15:49:32 CET: KD8IAY-11>APLRG1,TCPIP*,qAC,T2TEXAS:!L9q5b8TNFa GLoRa APRS
2026-03-02 15:49:32 CET: EW3240>APN000,TCPXX*,qAX,CWOP-5:@021449z4347.07N/08745.98W_159/006g012t031r000p000P000b10301h72L283eMB58
2026-03-02 15:49:32 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.39N/01603.32EQ255/051/A=000639 MOBILE DMR TG:21966
2026-03-02 15:49:32 CET: DB0BIN>APGE01,TCPIP*,qAC,T2AUSTRIA:>021449zat Home
2026-03-02 15:49:32 CET: ED7ZAD-3>APMI01,WIDE2-2,qAO,EA7KAV-15:;ED7YAC *021449z3756.87N/00448.58Wr145.675(-0.6) Sub77 & C4FM
2026-03-02 15:49:32 CET: CW9372>APRS,TCPXX*,qAX,CWOP-7:@021449z4248.95N/08607.20W_112/001g005t027r000p002P000h69b10332.DsVP
2026-03-02 15:49:31 CET: EI3JE-9>APAT51,EI2FHP-2*,WIDE2-1,qAR,EI2WCP-1:!5153.46N/00800.20Wj229/000/A=000393 Listening SIRN
2026-03-02 15:49:31 CET: KD4FJ-R>APDG03,TCPIP*,qAC,KD4FJ-RS:!2610.89ND09745.57W&/A=00000070cm MMDVM Voice (C4FM) 443.87500MHz +5.0000MHz, KD4FJ_Pi-Star_RPT
2026-03-02 15:49:31 CET: ONIONM>APLRG1,WIDE1-1,WIDE2-2,qAR,KD7YAT-44:=L=#X>228"# !GLoRa APRS Digi Onion Mtn 433.775 MHz BW125 SF7 CR4:6 DBUF+T de N7UV-61|$N%Z$$|
2026-03-02 15:49:31 CET: KQ9A-3>BEACON,qAR,W9LRT-1:KQ9A-3 APRS Digipeater Bourbon, IN
2026-03-02 15:49:31 CET: HS8KAY>AESPG4,TCPIP*,qAC,T2TAIPEI:T#3076,0,0,0,43,0,00000000
2026-03-02 15:49:31 CET: HS8KAY>AESPG4,TCPIP*,qAC,T2TAIPEI:>V.4.2a SSID:HamShack-2.4G Rx:0 Digi:0 Tx:0 UpTime:23.00
2026-03-02 15:49:31 CET: WINLINK>APWL2K,TCPIP*,qAS,WLNK-1:;KD0ZEA-10*020249z3823. NW09042. Wa145.070MHz Winlink Packet Gateway
2026-03-02 15:49:31 CET: DK5EW-9>APLRT1,WIDE1-1,qAO,DA0Y-10:!/60{>Pm:>>JdQ
2026-03-02 15:49:31 CET: YB3JF-1>APK5C1,YH3NPX-4*,qAR,YH3NPX-2:!0726.64S/11243.18ED
2026-03-02 15:49:31 CET: DG3NZ-10>APLRG1,TCPIP*,qAC,FIRST:!L5E+EQ>i<& GLoRa APRS, oberes Schwabachtal
2026-03-02 15:49:31 CET: KN4JRZ-N>APDG03,TCPIP*,qAC,KN4JRZ-NS:!5000.00ND00300.00W&/A=00000070cm MMDVM Voice (C4FM) 432.80000MHz +0.0000MHz, KN4JRZ_Pi-Star_ND
2026-03-02 15:49:32 CET: DB0FRB>APRX29,TCPIP*,qAC,T2GREECE:;DB0FIS *111111z4802.51N/00810.17ErC4FM-Relais 438.7375MHz -7.6; WIRES-X Raum DL-Suedwest
2026-03-02 15:49:32 CET: CA7TRF-4>APFII0,TCPIP*,qAC,APRSFI:@144929h4107.75S/07122.16W$166/006/A=002714QAP 146.520!wb1!
2026-03-02 15:49:32 CET: VE1BOF-D>APDG03,TCPIP*,qAC,VE1BOF-DS:!4500.89ND06453.47W&/A=00000070cm MMDVM Voice (DMR) 446.28700MHz +0.0000MHz, APRS for DMRGateway
2026-03-02 15:49:32 CET: W6JKL-9>S6PS0X,WIDE1-1,WIDE2-1,qAR,KI5ZMB:`{KPl]Bk/`"6D}_%
2026-03-02 15:49:32 CET: KQ9A-3>APRS,WIDE1-1,qAR,W9LRT-1:!4117.33NS08606.89W#PHG2390/A=000866 Bourbon IN
2026-03-02 15:49:32 CET: DO7DH-5>APRS,TCPIP*,qAC,T2LAUSITZ:@021449z4809.75N/01237.07E_.../...g...t056r...p...P...h61b10238InnSalzachWetter
2026-03-02 15:49:32 CET: KN4PAH-1>APDW18,WIDE1-1,WIDE2-1,qAR,N4VGW-1:!S;YNo:Dtm# ! Town of Bedford-Bedford VA DigiPi
2026-03-02 15:49:32 CET: 9M2EDK>APRX29,9M2EDK-1*,qAR,9M2OCX-1:!0257.18N\10134.70EhHAM RADIO STORE - QUEBEC VENTURES, (Facebook)-https://www.facebook.com/quebecventures | (WEBSITE)-https://www.quebecventures.com.my
2026-03-02 15:49:32 CET: KF0FBG-7>SYQWLZ,STLSMI,WIDE1*,WIDE2-1,qAO,KI0AU-2:`z=8l <0x1c>[/`monitoring w0te & 146.52_3
2026-03-02 15:49:32 CET: ER-OE7XWT>RXTLM-1,TCPIP,qAR,OE7XWT::ER-OE7XWT:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:32 CET: ER-OE7XWT>RXTLM-1,TCPIP,qAR,OE7XWT:T#297,0.00,0.05,0,4,0.0,01000000,RepeaterLogic
2026-03-02 15:49:32 CET: KN4IJF-1>APMI04,WIDE2-2,qAR,W4AD-10:@021331z3846.88N/07725.93W#WX3in1Mini U=13.8V.
2026-03-02 15:49:32 CET: DB0ZD-10>APMI01,TCPIP*,qAS,DB0ZD:!4732.21N/01012.98ErPHG3200 W3 (R84)439,000MHz -7,6MHz
2026-03-02 15:49:32 CET: DB9VL-22>APLT00-1,DJ0QO-10*,qAO,DB0IKS-10:=/5R63P1{8< P[Meshcom4 Z19<<0xff><0x01>F
2026-03-02 15:49:32 CET: AE4OK-10>APRX29,TCPIP*,qAC,T2USANE:!3735.36NI07727.09W#PHG6200 Digi & TX-iGate in Ginter Park Subdivision, Richmond, VA
2026-03-02 15:49:32 CET: IR7UCN>APBM1S,TCPIP*,qAS,BM2222:@021449z4007.13N/01818.23ErPHG0000Repeater UHF 430.4500/435.4500 CC1
2026-03-02 15:49:32 CET: IR7UCN>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222045
2026-03-02 15:49:32 CET: NG7I-B>APDG02,TCPIP*,qAC,NG7I-BS:!4707.32ND11822.38E&RNG0001/A=000010 440 Voice 440.30000MHz +5.0000MHz
2026-03-02 15:49:32 CET: AF4IG-8>EAAQXP,WIDE1-1,qAR,W0NED:`pVL<0x1c><0x1d>0R/4100FF006A667B08
2026-03-02 15:49:31 CET: KD2RCQ>APOSB,TCPIP*,qAC,T2CAEAST:@021449z4044.88N\07348.07W&/A=000000SharkRF openSPOT3
2026-03-02 15:49:31 CET: LA5PPA-1>APMI01,TCPIP*,qAC,SEVENTH:@021449z6033.70N/00915.37E_198/000g006t031r000p000P000h92b10186Liemarka, Nesbyen
2026-03-02 15:49:31 CET: PU5DSM-B>APDG02,TCPIP*,qAC,PU5DSM-BS:!2734.44SD04831.83W&RNG0001 440 Voice 433.09000MHz +0.0000MHz
2026-03-02 15:49:32 CET: IR0UIZ>APBM1S,TCPIP*,qAS,BM2222:@021449z4233.24N/01239.99ErPHG0000IR0UIZ - Terni 430.9375/435.9375 CC1
2026-03-02 15:49:32 CET: IR0UIZ>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222038
2026-03-02 15:49:32 CET: SNOBNK>APN391,qAR,K7RYH-2:!4426.57NS11608.17W#PHG7230 W2,IDn-N,SnowbankMt W7VOI
2026-03-02 15:49:32 CET: BG3OTT>APBM1D,BG3OTT,DMR*,qAR,BG3OTT:@144925h3222.68N/11557.77E8000/000Hytera PNC380 Pro
2026-03-02 15:49:32 CET: EA1BQO>APU25N,TCPIP*,qAC,T2SPAIN:>261849zUI-View32 V2.03
2026-03-02 15:49:32 CET: 9M2PHQ-7>P4TRW7,WIDE1-1,qAR,9M2SGN-1:`lTkq7$>/"4i}
2026-03-02 15:49:32 CET: IR1DO>APBM1S,TCPIP*,qAS,BM2222:@021449z4407.70N/00951.25EQPHG0000Simplex VHF Hotspot 145.4250/145.4250 CC1
2026-03-02 15:49:32 CET: IR1DO>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222041
2026-03-02 15:49:32 CET: BH2UMM>APET51,TCPIP*,qAC,T2YANTAI:!3827.78N/10615.92EyBR9OO <0xe9><0x93><0xb6><0xe5><0xb7><0x9d><0xe4><0xb8><0x9a><0xe4><0xbd><0x99><0xe6><0x97><0xa0><0xe7><0xba><0xbf><0xe7><0x94><0xb5><0xe6><0x95><0xb0><0xe6><0xa8><0xa1><0xe4><0xb8><0xad><0xe7><0xbb><0xa7> 439.550 -8 T88.5 / S1~
2026-03-02 15:49:32 CET: F6GVH-1>APNW01,TCPIP*,qAS,F6GVH::F6GVH-1 :PARM.I1,I2,U1,U2,Temp
2026-03-02 15:49:32 CET: F6GVH-1>APNW01,TCPIP*,qAS,F6GVH::F6GVH-1 :UNIT.mA,mA,Volt,Volt,C
2026-03-02 15:49:32 CET: F6GVH-1>APNW01,TCPIP*,qAS,F6GVH::F6GVH-1 :EQNS.0,19.5,0,0,19.5,0,0,0.1,0,0,0.1,0,0,0.5,-64
2026-03-02 15:49:32 CET: WG6G-5>APDR16,TCPIP*,qAC,T2SWEDEN:=3347.41N\11805.06W>019/025/A=-00109 https://aprsdroid.org/
2026-03-02 15:49:32 CET: LZ0SUN-B>APDG02,TCPIP*,qAC,LZ0SUN-BS:!4233.78ND02317.06E&RNG0062 440 Voice 438.42500MHz -7.6000MHz
2026-03-02 15:49:32 CET: CW7929>APRS,TCPXX*,qAX,CWOP-4:@021449z3406.02N/11623.30W_259/000g000t059r000p000P000h46b10143.DsVP
2026-03-02 15:49:32 CET: DL1NBU-11>APLG01,TCPIP*,qAC,T2SWEDEN:=5020.62NL01135.66E&LoRa_APRS_iGate DL1NBU-11 Frankenwald
2026-03-02 15:49:32 CET: DL4MGR-8>APRS,qAO,DL4MGR-10:T#084,45,48,31
2026-03-02 15:49:32 CET: DL9CK>APMI0A,WIDE1-1,WIDE2-1,qAR,DO7AD:!5202.29N\01127.02E-Fill-in-Digi, 3W, X200, T=13.0C, U=12.2V
2026-03-02 15:49:32 CET: K7DCJ>T7TTQQ,WIDE1-1,WIDE2-1,qAR,K7NWS-2:'2PJl <0x1c>-/]445.950MHz-500 T110 V-Alert
2026-03-02 15:49:32 CET: W4IFI>APRS,TCPIP*,qAC,CWOP-3:@021449z3711.65N/10802.20W_039/003g006t040r000p000P000h62b10187eCumulusDsVP
2026-03-02 15:49:32 CET: VE9GM-15>TVUP4T,VE9GFL-3*,WIDE2-1,qAR,VE9GM-10:`_Frr6 k/`"6(}147.195MHz Toff +060 Gino On IRG_%
2026-03-02 15:49:32 CET: N4WDH-9>RXQQ2W,qAR,N4WDH-7:`n11p!lj/`"4-}_%
2026-03-02 15:49:32 CET: W0NE-4>BEACON,WIDE1-1,WIDE2-2,qAR,W0NE-3:;146.835-R*111111z4404.61N/09136.25Wr146.835MHz C131 -060 R45m NET:Sun@830p
2026-03-02 15:49:32 CET: DW2589>APN000,TCPXX*,qAX,CWOP-3:@021449z4210.96N/07046.26W_037/001g009t020r000p002P000b10387h41eMB63
2026-03-02 15:49:32 CET: DC1CHS-2>APLT00-1,WIDE1-1,qAO,DE0KFD-1:=/6+;NQ`_Gb P[
2026-03-02 15:49:32 CET: N7HRZ-9>SPRS8W,W4NN-1*,WIDE1*,KJ4YNR-1*,WIDE2*,qAR,WA4USN-5:`r?tlr<0x1f>>/`"3n}_%
2026-03-02 15:49:32 CET: EW6940>APRS,TCPXX*,qAX,CWOP-3:@021449z4140.10N/07234.40W_084/000g000t020r000p000P000b10379h35eMB42
2026-03-02 15:49:31 CET: ZS4PR-4>APCLEY,TCPIP*,qAC,APRS-ZA:/021449z2610.99S/02756.38E>131/005/A=005555 53C 1Mv 4.16V In 13.06V 9kmh
2026-03-02 15:49:31 CET: BI9BBL-10>APE32A,TCPIP*,qAC,T2TOKYO:>https://github.com/nakhonthai/ESP32APRS_Audio
2026-03-02 15:49:31 CET: EW2561>APN000,TCPXX*,qAX,CWOP-6:@021449z5056.52N/00553.25E_259/001g004t064r000p000P000b10163h49L313eMB63
2026-03-02 15:49:32 CET: K5CS-1>APX01,qAO,K5FRP-10:!3513.50NS09315.26W#PHG6760/ W2, ARn-N RUSSnet APRS Digi
2026-03-02 15:49:32 CET: KC3PWY-13>APRS,TCPXX*,qAX,CWOP-3:@021449z4032.09N/07955.84W_337/001g005t024r000p000P000h64b10349
2026-03-02 15:49:32 CET: HB9TQL-10>APMI06,TCPIP*,qAS,HB9TQL:@021449z4731.36NI00742.50E&iGate Pratteln (RX only) http://hb9tql.ch
2026-03-02 15:49:32 CET: DN9HAN-7>APDR17,TCPIP*,qAC,T2LAUSITZ:=5308.37N/01115.81EU034/027/A=000190 Andy@Work
2026-03-02 15:49:32 CET: ON7DS-B>APDG02,TCPIP*,qAC,ON7DS-BS:!5107.66ND00428.33E&RNG0001/A=000010 70cm Voice (D-Star) 433.45000MHz +0.0000MHz
2026-03-02 15:49:32 CET: HAMLTN>APMI06,TCPIP*,qAC,W8FSM-MI:!4242.32NS08600.33W#W2,HamiltonMI Digi/Igate W8FSM U=14.2V
2026-03-02 15:49:32 CET: NJ3T-4>APN382,WIDE2-2,qAR,AC3DB-10:;443.725PA*111111z3948.43N/07910.31Wr443.725MHz T127.3 Allmode
2026-03-02 15:49:32 CET: SM5YNB-9>UX2U01,qAR,SM0RGQ-11:`+C&l>Uj/`"44}434.600MHz 145.400 alt 434.825Mhz_%
2026-03-02 15:49:32 CET: E27AC-1>APRX20,TCPIP*,qAC,T2INDY:!1329.18NI10115.85E&FRIEND DEPTH OF NIGHT 144.6625MHz
2026-03-02 15:49:32 CET: GW0NEN-2>APDW18,TCPIP*,qAC,T2BC:!R3uhBMKG@& ! DigiPi http://digipi.org/
2026-03-02 15:49:32 CET: OE3MZC-2>APMI01-2,TCPIP*,qAS,KW4XC:@021449z4812.06N/01555.87E& Igate in Neulengbach 144.800/432.500Mhz
2026-03-02 15:49:32 CET: IR4UCO>APBM1S,TCPIP*,qAS,BM2222:@021449z4434.47N/01122.11ErPHG0000IR4UCO - Bologna 430.6125/435.6125 CC1
2026-03-02 15:49:32 CET: IR4UCO>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222046
2026-03-02 15:49:32 CET: SP8NFE-1>APMI01,TCPIP*,qAS,K2RPF:@021449z4951.44N/02221.40E_180/000g000t046r000p000P000h81b00000WX3in1 weather
2026-03-02 15:49:32 CET: IR3AS>APMI06,TCPIP*,qAC,T2POLAND:@021449z4603.03N/01120.08E_315/000g000t031r000p000P...h86b08575Meteo Cima Panarotta 2000 mt
2026-03-02 15:49:32 CET: SP8NFE-1>APMI01,TCPIP*,qAS,K2RPF:@021449z4951.44N/02221.40E-WX3in1Plus
2026-03-02 15:49:32 CET: VK4CWL-13>APOTW1,WIDE1-1,WIDE2-1,qAR,VK4GDW-3:!2338.72S/14805.91E_090/001g002t073P000h83b10122V124OTW1
2026-03-02 15:49:32 CET: EW5498>APN000,TCPXX*,qAX,CWOP-4:@021449z4648.14N/00709.08E_.../...g...t053r000p000P000b10217h65eMB63
2026-03-02 15:49:33 CET: VE1PRB-1>APWW11,TCPIP*,qAC,T2PERTH:<IGATE,MSG_CNT=0,LOC_CNT=0,DIR_CNT=0,RF_CNT=0,DX=1*VE1HAM(25mi@252<0xb0>)
2026-03-02 15:49:33 CET: HS3LSE-10>APINDY,WIDE1-1,APRS,qAR,HS3LSE-1:)SR-BANRAE!1458.68N/10336.44Ea090/011
2026-03-02 15:49:33 CET: SQ2MSL-10>APLRG1,TCPIP*,qAC,T2POLAND:!L3puuS>rC# !GLoRa APRS W<0xc5><0x82>oc<0xc5><0x82>awek Wsch<0xc3><0xb3>d - Zapora Batt=4.20V Ext=0.55V
2026-03-02 15:49:33 CET: 2E0SWI-1>APMI04,TCPIP*,qAS,2E0SWI-13:@021449z5047.47NR00109.96W&WX3in1Mini RX iGate U=12.3V.
2026-03-02 15:49:33 CET: DH7SW-D>APDG03,TCPIP*,qAC,DH7SW-DS:!4841.12ND00955.83E&/A=00000070cm MMDVM Voice (DMR) 430.03750MHz +9.8000MHz, APRS for DMRGateway
2026-03-02 15:49:33 CET: DB3BEF-S>APDG01,TCPIP*,qAC,DB3BEF-GS:;DB3BEF B *021449z5018.60ND00736.60EaRNG0001/A=000010 70cm Voice (D-Star) 433.67500MHz +0.0000MHz
2026-03-02 15:49:33 CET: ER-DB0SRS>RXTLM-1,TCPIP,qAR,DB0SRS::ER-DB0SRS:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:33 CET: ER-DB0SRS>RXTLM-1,TCPIP,qAR,DB0SRS:T#362,0.00,0.02,0,1,0.0,00000000,RepeaterLogic
2026-03-02 15:49:33 CET: BG6KQI-7>APN000,TCPIP*,qAC,T2OSAKA:=2505.81N\11701.60Ev<0xe6><0xac><0xa2><0xe8><0xbf><0x8e>QSO438.500
2026-03-02 15:49:32 CET: HEAT-60>APRPJH,TCPIP*,qAC,APRPJU:=0417.28N/10325.72E:High heat signature detected by 9M2PJU APRS Bot. For more info, visit https://hamradio.my
2026-03-02 15:49:32 CET: N1TKS>APU25N,WB2ZII,KB2EAR-1,WIDE2,qAR,N2ARC:@021445z4100.51N/07339.16W_067/002g005t025r000p000P000h46b10394Davis Pro Wx station/ 50 watts out {UIV32N}
2026-03-02 15:49:32 CET: SR9LC-1>APRX29,TCPIP*,qAC,T2CSNGRAD:;438.700LC*161707z5041.95N/01838.54Er438.700MHz c071 -760 R60k SR9LC Przemiennik Lubliniecki
2026-03-02 15:49:32 CET: EI6HKB-10>APWW11,TCPIP*,qAC,T2CSNGRAD:>Baywatch Station
2026-03-02 15:49:32 CET: EL-EA3GXK>APSVX1,WIDE1-1,qAS,EA3GXK:T#076,000,000,000,000,000,00000000
2026-03-02 15:49:32 CET: DO7RMO-2>APRSGW,TCPIP*,qAR,DO7RMO-2:!4853.90N/01256.44E>gerade da#Magnus/B=100/A=001601
2026-03-02 15:49:32 CET: FW1210>APRS,TCPXX*,qAX,CWOP-5:!4258.16N/08804.13W_194/001g004t029r000P000p000h67b10300.VWS-DavisVP2
2026-03-02 15:49:32 CET: VA3WWD-2>APTW14,VE3ZAP,qAR,VE3GSJ:_03051029c180s000g000t010r000p000P000h..b.....tU2k
2026-03-02 15:49:32 CET: F4IRV-1>APRS,TCPIP*,qAS,F4GOH-10:!4749.57N\00016.98E-Home
2026-03-02 15:49:32 CET: IW9HRH-12>APLRG1,WIDE1-1,qAO,IW9HRH-10:!L;9w-Qf@(# GLoRa APRS
2026-03-02 15:49:32 CET: DL9MBI-10>APLG01,TCPIP*,qAC,T2UK:=4806.70NL01244.35E&LoRa iGATE DL9MBI Plattenberg/Burgkirchen
2026-03-02 15:49:32 CET: 8P6PE-1>APMI04,TCPIP*,qAC,T2SYDNEY:@021449z1309.05N/05933.36W-WX3in1Mini Hello from BARBADOS
2026-03-02 15:49:32 CET: GW1754>APRS,TCPXX*,qAX,CWOP-6:@021445z5209.39N/00709.49W_216/008g015t051r000p022P006b10118h78.weewx-4.5.1-MetpakPro
2026-03-02 15:49:32 CET: AC0ID-10>APRS,TCPIP*,qAC,T2USANW:>MyPos v2.1.2 for Android
2026-03-02 15:49:33 CET: YB0SPS-14>APE32L,TCPIP*,qAC,T2TAS:>Lora iGate APRS with ESP32 Lora
2026-03-02 15:49:33 CET: IR0ZXU-B>APDG02,TCPIP*,qAC,IR0ZXU-BS:!4147.67ND01245.80E&RNG0037/A=000082 70cm Voice (D-Star) 430.21250MHz +1.6000MHz
2026-03-02 15:49:33 CET: BI9BZG-3>APFMO1,TCPIP*,qAC,T2HK:=3421.72NF10844.41EiFMO-STATION,CN,485,<0xe9><0x99><0x95>D-FMO<0xe4><0xb8><0xad><0xe7><0xbb><0xa7>,8.137.152.203,P1883,F5000KM,U5/25,PASS:yW5IhrxSGDXAD0ZLG2LiVQ==,SIGN:xMAP0ZYmQ1jdUcGSbQ5/JA==
2026-03-02 15:49:33 CET: BI9BZG-3>APFMO1,TCPIP*,qAC,T2HK:><0xe6><0xad><0xa3><0xe5><0xb8><0xb8>,<0xe5><0x9c><0xa8><0xe7><0xba><0xbf>/<0xe5><0xb3><0xb0><0xe5><0x80><0xbc>:5/25,<0xe5><0x92><0xb8><0xe9><0x98><0xb3><0xe5><0x85><0xac><0xe5><0xae><0x89><0xe6><0xb8><0xa9><0xe9><0xa6><0xa8><0xe6><0x8f><0x90><0xe9><0x86><0x92> <0xe6><0x8b><0x92><0xe7><0xbb><0x9d><0xe7><0x87><0x83><0xe6><0x94><0xbe><0xe7><0x83><0x9f><0xe8><0x8a><0xb1><0xe7><0x88><0x86><0xe7><0xab><0xb9>
2026-03-02 15:49:33 CET: HP1MRA-1>APET51,WIDE1-1,WIDE2-1,qAS,HP1UPR-1:!0904.25N/07925.95W#UNI<0xd3>N PANAME<0xd1>A DE RADIOAFICIONADOS TG7146 DMR UPRA 11.9V
2026-03-02 15:49:33 CET: VA7MLZ-9>TY0PPU,WIDE1-1,WIDE2-1,qAO,MSPK39:`3M;l"-k/`"3x}_"
2026-03-02 15:49:33 CET: KC5DAQ-25>APN000,TCPIP*,qAC,T2CAEAST:@021449z2946.99N/09540.93W_189/001g004t068r000p000P000b10114h85L188eMB63
2026-03-02 15:49:33 CET: IW0EFJ-9>APN000,WIDE1-1,WIDE2-1,qAR,IZ0ANE-2:=4127.62N/01348.69E>360/000/A=000314APRS 432ARICASSINO 12.80V
2026-03-02 15:49:33 CET: TA5ADB>APDR16,TCPIP*,qAC,T2KA:=3706.39N/03722.90Ee144.850MHz/A=003061 73!...
2026-03-02 15:49:33 CET: N7QIN-1>EGFYPR,WIDE1-1,WIDE2-1,qAR,UCAPK:'3=hl <0x1c>K\]147.160MHzHome Station=
2026-03-02 15:49:33 CET: SUNLGT>APMI04,TCPIP*,qAC,T2TEXAS:T#107,131,135,003,069,166,00000000
2026-03-02 15:49:33 CET: WA1EM>APDR16,TCPIP*,qAC,T2SWEDEN:=3852.49N/07708.65W$147.300MHz/A=000226 Skywarn <0xe2><0x80><0xa2> CERT
2026-03-02 15:49:33 CET: FW5399>APN000,TCPXX*,qAX,CWOP-7:@021445z3805.82S/17641.26E_264/000g002t048r000p000P000b10099h70eMB63
2026-03-02 15:49:33 CET: K3ORB-10>APMI06,TCPIP*,qAS,K3ORB:@021449z3825.22N/07503.31W#Worcester Radio Grp U=13.1V,T=63.1F
2026-03-02 15:49:33 CET: G0NMP-N>APDG03,TCPIP*,qAC,T2SPAIN:!5234.22ND00051.84E&/A=000000440 MMDVM Voice 438.80000MHz +0.0000MHz, G0NMP_Pi-Star
2026-03-02 15:49:33 CET: DB0BIN>APGE01,TCPIP*,qAC,T2AUSTRIA:!4818.27N/00845.69E_180/000g...t056r...p000h..b10199
2026-03-02 15:49:32 CET: KAZTUBAC1>APRS,TCPXX*,qAX,CWOP-4:@021449z3137.36N/11104.12E_000/000g003t048r000p000P000h40b10127WeatherCatV320B364H31
2026-03-02 15:49:32 CET: K4GVE-N>APDG03,TCPIP*,qAC,K4GVE-NS:!3809.36ND07809.95W&/A=00000070cm MMDVM Voice (C4FM) 446.52500MHz +0.0000MHz, K4GVE_Pi-Star_ND
2026-03-02 15:49:32 CET: 9W2ICC-4>P3PYW0,WIDE1-1,qAO,9W4GHT-1:`mC%l#X>/"5F}Mobile Batt=4.26V100% Sats:6 Dist:0.0km MaxSpd:0.0kmh Time:0h5m AvgSpd:0.0kmh|(@%_|
2026-03-02 15:49:32 CET: DB0OE>APMI06,TCPIP*,qAC,T2POLAND::DB0OE :PARM.Vin,Rx1h,Dg1h,Eff1h,Eff,O1,O2,O3,O4,I1,I2,I3,I4
2026-03-02 15:49:32 CET: BG5DZF-9>APRS4G,TCPIP*,qAC,T2FUKUOKA:/224932h2859.00N/12024.93Eb319/003/A=001268 A:-48 B:20/31 C:3.8V D:43<0xc2><0xb0>C
2026-03-02 15:49:32 CET: N0KFB-11>T5PT3P,WIDE1-1,WIDE2-1,qAR,KD0TLS-4:`y*hl|B>/"6{}Out runnin' 'round / Mon 444.750,146.58,14].52
2026-03-02 15:49:32 CET: DB0OE>APMI06,TCPIP*,qAC,T2POLAND::DB0OE :UNIT.Volt,Pkt,Pkt,Pcnt,Pcnt,On,On,On,On,Hi,Hi,Hi,Hi
2026-03-02 15:49:32 CET: DB0TN-10>AP4R10,TCPIP*,qAC,HAMCLOUD3:!4820.32NI00809.24E&APRS4R IGATE Brandenkopf
2026-03-02 15:49:32 CET: FK4RG-9>TW5SQR,WIDE1-1,WIDE2-2,qAR,F5ILB:`w1-m"ov/`"55}_#
2026-03-02 15:49:32 CET: DB0OE>APMI06,TCPIP*,qAC,T2POLAND::DB0OE :EQNS.0,0.075,0,0,10,0,0,10,0,0,1,0,0,1,0
2026-03-02 15:49:32 CET: DB0OE>APMI06,TCPIP*,qAC,T2POLAND::DB0OE :BITS.11111111,WX3in1Plus20 Telemetry
2026-03-02 15:49:32 CET: W4GCW>APWW11,N1KSC-1*,WIDE2-1,qAR,KM4ZYG-10:@144928h/@4Vs:*)2- sT(Time 0:00:00)
2026-03-02 15:49:32 CET: DO1NIC-S>APDG01,TCPIP*,qAC,DO1NIC-GS:;DO1NIC B *021449z5234.00ND01320.00EaRNG0001/A=000010 70cm Voice (D-Star) 435.00000MHz +0.0000MHz
2026-03-02 15:49:32 CET: WB6YNM-4>ID,qAO,KK6SEN-2:WB6YNM-4/R DISABL/D BBOX/B KBERR/N
2026-03-02 15:49:33 CET: HB9EKJ-B>APDG02,TCPIP*,qAC,HB9EKJ-BS:!4702.40ND00904.80E&RNG0001/A=000010 70cm Voice (D-Star) 438.57500MHz -7.6000MHz
2026-03-02 15:49:33 CET: SR8MBR-1>APRX29,WIDE2-1,TCPIP*,qAS,SR8MBR-1:!4956.40N/02202.89E#W2,SPn Tyczyn Digi/iGate cross LoRa SR8MBR-2
2026-03-02 15:49:33 CET: J73Z-10>APMI06,TCPIP*,qAC,T2POLAND:@021449z1519.92NI06123.22W-WX3in1Plus2.0 I-GATE , 145.05MHz R15m
2026-03-02 15:49:33 CET: WINLINK>APWL2K,TCPIP*,qAS,WLNK-1:;KD0ZEA-10*020249z3823. NW09042. Wa145.070MHz Winlink VARA FM Wide Gateway
2026-03-02 15:49:33 CET: OE3RPU-7>APTETR,OE3XWW-T,TCPIP*,qAU,OE3XHQ:!4730.62N/01602.25E[000/000 via OE3XWW-T (TETRA-Austria)
2026-03-02 15:49:33 CET: WB2BWU-2>APK102,WIDE1-1,WIDE2-2,qAR,KC2MDN-2::WLNK-1 :rej7953
2026-03-02 15:49:33 CET: KD4BNQ-3>APDW14,TCPIP*,qAC,T2USANW:!3721.00NG08032.23W#
2026-03-02 15:49:33 CET: PY5KT-13>APRS,TCPIP*,qAC,T2BRAZIL:T#2808,1249,507,446,240,06642,11111000
2026-03-02 15:49:33 CET: KD7MPG-3>APRS,TCPIP*,qAC,WG3K-CA:@021449z4456.91N/12259.69W_065/000g001t048r000p000P000b10272h93eMB42
2026-03-02 15:49:33 CET: DL1FOX-9>APBM1D,DMR*,qAS,DL3OCK-12:=4823.53N/01144.47E[360/000/A=000000Robert HyTalk
2026-03-02 15:49:33 CET: VA3NB-10>KITTY,qAr,VE9QLE-2:=4332.82N/08014.99Wy[https://cats.radio] Guelph CATS igate, 1W with whip antenna on roof. /A=000033
2026-03-02 15:49:33 CET: BD4TUC-1>AP51G2,TCPIP*,qAC,T2CS:!3137.26N/12021.96E>011/003/A=000055<0xe6><0x97><0xa0><0xe9><0x94><0xa1><0xe5><0xb8><0x82><0xe6><0x97><0xa0><0xe7><0xba><0xbf><0xe7><0x94><0xb5><0xe5><0x8d><0x8f><0xe4><0xbc><0x9a> <0xe5><0x9c><0xb0><0xe5><0x9d><0x80> 4668.113Km 021m 4.2V S10
2026-03-02 15:49:33 CET: EL-CN8EAA>RXTLM-1,TCPIP,qAR,CN8EAA::EL-CN8EAA:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:33 CET: NM2V-N>APDG03,TCPIP*,qAC,NM2V-NS:!2814.93ND08116.88W&/A=00000070cm MMDVM Voice (C4FM) 446.52500MHz +0.0000MHz, NM2V_Pi-Star_ND
2026-03-02 15:49:33 CET: HS8KGG>AESPG4,TCPIP*,qAC,T2CS:T#24606,0,0,0,32,0,00000000
2026-03-02 15:49:33 CET: HS8KGG>AESPG4,TCPIP*,qAC,T2CS:>V.4.2a SSID:HamShack-2.4G Rx:0 Digi:0 Tx:0 UpTime:184.22
2026-03-02 15:49:33 CET: F4BKE-10>APLRG1,TCPIP*,qAC,T2PANAMA:!L6.j=P,EBa GLoRa APRS
2026-03-02 15:49:33 CET: LA3RIA-7>APU25N,LD1BK,WIDE2*,qAR,LD1FE:=5956.86N/01039.09E_Current forcast ... {UIV32N}
2026-03-02 15:49:33 CET: SZ3P>APRX28,TCPIP*,qAC,T2GREECE:!3813.24N\02143.28E-DMR Rpt 439.075 -7.6 MHz CC1 TS1:202 TS2:2023 | Tx27.1C Pi41.9C
2026-03-02 15:49:33 CET: DL6NEC-10>APLRG1,TCPIP*,qAC,T2ROMANIA:!L53wjQ$9M& GLoRa APRS iGate Test
2026-03-02 15:49:33 CET: UR0DVC-10>APMI04,WIDE3-3,qAR,UR4SWG-14:;UR0DVC *041021z4806.51N/02412.53Er145.625 TSQ 173.8
2026-03-02 15:49:33 CET: IZ8EQA>APDR16,TCPIP*,qAC,T2UK:=3805.0 N/01538.0 Ey Op Antonio 145.425 R.C
2026-03-02 15:49:33 CET: SV5DDT-12>APE32L,TCPIP*,qAC,T2SPAIN:!3625.80NL02814.07EzPHG1060/A=000016
2026-03-02 15:49:33 CET: LU8EB-1>APNU19,WIDE2-1,qAR,LW7EFM-1:!3515.14S/05840.86W#PHG5250 W2, APRS-DIGI Udaondo Bs As
2026-03-02 15:49:33 CET: IR1UDQ>APBM1S,TCPIP*,qAS,BM2222:@021449z4454.56N/00902.04ErPHG0000MMDVM 431.3000/432.9000 CC1
2026-03-02 15:49:33 CET: DG2KKR-4>APLT00-1,qAR,DB0NOR:!5049.91N/00648.09E>107/000/A=000293LoRa Tracker !wU$!
2026-03-02 15:49:33 CET: 2E0KIO-10>APLRG1,TCPIP*,qAC,T2RDU:!L3aSLMuX[& GLoRa APRS
2026-03-02 15:49:33 CET: W5RKJ-N>APDG03,TCPIP*,qAC,W5RKJ-NS:!2953.73ND09540.10W&/A=0000002m MMDVM Voice (C4FM) 144.95000MHz +0.0000MHz, W5RKJ_Pi-Star_ND
2026-03-02 15:49:33 CET: BD4TSK-10>AP51D8,WIDE1-1,qAS,BI4VNM-10:!3149.46N/12026.60ErJYRSA APRS DIGI Huaxi China 13.5V
2026-03-02 15:49:33 CET: FUQING>APE32I,TCPIP*,qAC,T2TAIPEI:/021449z2542.69N/11917.27E&PHG1020 E2[Device status]system self checking OK!
2026-03-02 15:49:33 CET: VK2FS-5>APMI06,TCPIP*,qAC,T2SYDNEY:@021449z3347.82S\15110.27E&145.175MHz iGate, Chatswood U=13.8V,T=27.7C
2026-03-02 15:49:33 CET: OK0HCS-S>APDG01,TCPIP*,qAC,OK0HCS-GS:;OK0HCS B *021449z5004.93ND01423.36EaRNG0012 440 Voice 439.45000MHz -7.6000MHz
2026-03-02 15:49:33 CET: 9W2DCM-1>AESPG4,TCPIP*,qAC,T2SYDNEY::9W2DCM-1 :PARM.RxTraffic,TxTraffic,RxDrop,RSSI,Voltage
2026-03-02 15:49:33 CET: 9W2DCM-1>AESPG4,TCPIP*,qAC,T2SYDNEY::9W2DCM-1 :UNIT.Pkt,Pkt,Pkt,dBm,V
2026-03-02 15:49:33 CET: 9W2DCM-1>AESPG4,TCPIP*,qAC,T2SYDNEY::9W2DCM-1 :EQNS.0,1,0,0,1,0,0,1,0,0,-1,0,0,1,0
2026-03-02 15:49:33 CET: 9W2DCM-1>AESPG4,TCPIP*,qAC,T2SYDNEY:T#235,1,4,1,32,5.25,00000000
2026-03-02 15:49:33 CET: IR1UDQ>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222052
2026-03-02 15:49:33 CET: BG5AAB-9>R80TQ1,TCPIP*,qAC,T2FUKUOKA:`0V><0x1c><0x1d>EP\`"4$}51G3 A2117.0Km 13.6V 25.8C S08 51G3
2026-03-02 15:49:33 CET: N0GIO-15>APRX29,TCPIP*,qAC,T2USANW:!3700.96NI10724.97W#N0GIO-15 I-Gate/Digi Arboles, CO
2026-03-02 15:49:33 CET: EW1335>APRS,TCPXX*,qAX,CWOP-4:!4416.41N/07512.34W_286/001g002t016r000P000p000h40b10366.VWS-DavisVVue
2026-03-02 15:49:33 CET: F4FEB-L>APLRG1,TCPIP*,qAC,T2CSNGRAD:!L6`;?O{M5a GLoRa APRS Igate Batt=4.25V
2026-03-02 15:49:34 CET: SR3NPB>APMI06,TCPIP*,qAC,T2CSNGRAD:T#219,174,034,013,067,000,00000000
2026-03-02 15:49:34 CET: PA3FNT-1>APDW17,qAS,PA3FNT:!5243.76NR00544.62E&PA3FNT-1 iGate RX-only | DireWolf 1.6 | RPi+RTL-SDR
2026-03-02 15:49:34 CET: BG5DVF-7>APFMO2,TCPIP*,qAC,T2YANTAI:=3026.12NF12016.83Ei<0xe4><0xb8><0xb4><0xe5><0xb9><0xb3><0xe6><0xac><0xa2><0xe8><0xbf><0x8e><0xe6><0x82><0xa8><0xef><0xbc><0x81><0xef><0xbc><0x81>
2026-03-02 15:49:34 CET: BG5DVF-7>APFMO2,TCPIP*,qAC,T2YANTAI:><0xe6><0xad><0xa3><0xe5><0x9c><0xa8> <0xe7><0x94><0x98><0xe8><0x82><0x83><0xe9><0x9b><0x86><0xe7><0xbe><0xa4><0xe4><0xb8><0xad><0xe7><0xbb><0xa7>(BG9JYT) <0xe4><0xb8><0x8a><0xe5><0xae><0x88><0xe5><0x90><0xac>
2026-03-02 15:49:34 CET: IR2UDV>APBM1S,TCPIP*,qAS,BM2222:@021449z4550.30N/00924.31ErPHG0000IR2UDV Ari Sez Lecco 430.1125/431.7125 CC1
2026-03-02 15:49:33 CET: YC1JEA-12>AESPG4,TCPIP*,qAC,T2FUKUOKA:@021449z0651.51S\10734.68E_000/000g000t077r...p...P...h96b09120L000 YC1JEA Bandung : [[Sunny]] SUN T=25<0xc2><0xb0> H=96% P=912
2026-03-02 15:49:33 CET: K4IKR>APWW10,TCPIP*,qAC,T2PANAMA:T#210,100,048,002,500,000,10000000
2026-03-02 15:49:33 CET: F1DUI-9>F1DUI-9,F5ZHR-4*,WIDE2-1,qAR,F4FEB-13:!4718.52N/00600.63E>010/000/A=001378F1DUI ANYTONE AT-D878UV-1.26
2026-03-02 15:49:33 CET: OK1FET-4>APRS,qAR,OK1SE-12:!4956.28N/01420.12E__RESETi<0x00>
2026-03-02 15:49:33 CET: OK1FET-4>APRS,qAO,OK2JIB-10:!4956.28N/01420.12E__RESETi
2026-03-02 15:49:33 CET: F4JBM>APOSW,TCPIP*,qAC,T2DENMARK:@021449z4850.32N\00215.10E&/A=000000SharkRF openSPOT2
2026-03-02 15:49:33 CET: IZ3GTH>APRS,TCPIP*,qAC,T2BIO:!4529.48N/01207.21EEEmgNet - https://www.cleverguy.it/emgnet OK-00-S
2026-03-02 15:49:33 CET: SQ1OHF-4>APLRG1,TCPIP*,qAC,T2POLAND:!L3DmzR7Si& GLoRa APRS iGate - RX 434.855 MHz (SF9/CR7)|%#%G|
2026-03-02 15:49:33 CET: SUNLGT>APMI04,WIDE2-1,qAR,ANVIL:T#106,131,135,003,069,166,00000000
2026-03-02 15:49:33 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.39N/01603.30EQ255/051/A=000643 MOBILE DMR TG:21966
2026-03-02 15:49:33 CET: DD3QQ-10>APLRG1,TCPIP*,qAC,T2CZECH:!L46ZDPFyp& GLoRa-Digi Muenster
2026-03-02 15:49:34 CET: OK1FET-4>APRS,qAS,OK1TPG-27:!4956.28N/01420.12E__RESETi SNR=-9dB RSSI=-78db
2026-03-02 15:49:34 CET: OK1FET-4>APRS,qAS,OK1KZE-10:!4956.28N/01420.12E__RESETi SNR=+6dB RSSI=-71db
2026-03-02 15:49:34 CET: JH4GQC>APU25N,TCPIP*,qAC,T2FUKUOKA:;XLX904 *170650z3415.44ND13231.55EaREFLECTOR XLX904H & XLX380Q 430.95MHz
2026-03-02 15:49:34 CET: IR4UBS>APBM1S,TCPIP*,qAS,BM2222:@021449z4413.63N/01154.91ErPHG0000RU101 - RIPETITORE IR4UBS Oriolo Dei Fichi (Ra) Altitudine 141 mt S.l.m - Info: ir4ubs@gmail.com 431.2625/432.8625 CC1
2026-03-02 15:49:34 CET: IR4UBS>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222054
2026-03-02 15:49:34 CET: VE7SML-4>APMI01,TCPIP*,qAC,T2BC:T#186,124,128,130,000,000,00000000
2026-03-02 15:49:34 CET: VE3CGR-4>APRX29,TCPIP*,qAC,T2CAEAST:T#973,0.0,0.9,0.0,0.0,3.0,00000000
2026-03-02 15:49:34 CET: EB7DFA-5>APDR16,TCPIP*,qAC,T2BELGIUM:=3644.83N/00410.32W>060/060/A=000602 canal 3 pmr plan 333
2026-03-02 15:49:34 CET: LD9TB>APBPQ1,TCPIP*,qAC,LB5ZH-14:!6938.03NB01850.86Eaax.25 1200 baud @ 144.925 og 70.3125MHz. -1 for BBS.
2026-03-02 15:49:34 CET: GJ8RRP-3>APTT4,qAR,GJ7DNI-13:T#593,123,096,255,094,088,00000011
2026-03-02 15:49:34 CET: YU1TTA-13>APFTIS,TCPIP*,qAO,AE5PL-JF:!4404.76N/02213.16E_.../...g...t054r...p...P...h100b09931QFE 993 mbar, QNH 1023 mbar
2026-03-02 15:49:34 CET: EL-EC4DA>APSVX1,WIDE1-1,qAS,EC4DA:T#076,000,025,000,004,000,00000000
2026-03-02 15:49:34 CET: SR9WXP>AKLPRZ,qAR,SR7NST:!4932.18N/01919.18E_034/001g002t035r...p...P...b08618h88
2026-03-02 15:49:34 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.38N/01603.28EQ256/051/A=000643 MOBILE DMR TG:21966
2026-03-02 15:49:34 CET: HS1AH-10>AESPG4,qAR,E22LPB:@021449z1453.85N/10023.88E#HS1AH 145.700 T=36<0xc2><0xb0> H=40% P=1005 PM 1:[21] , 2.5:[31] , 10:[33] (<0xce><0xbc>g/m3)
2026-03-02 15:49:34 CET: HS1AH-10>AESPG4,TCPIP*,qAC,T2HAKATA:@021449z1453.85N/10023.88E#HS1AH 145.700 T=36<0xc2><0xb0> H=40% P=1005 PM 1:[21] , 2.5:[31] , 10:[33] (<0xce><0xbc>g/m3)
2026-03-02 15:49:34 CET: N7XAO>APMI04,TCPIP*,qAC,FIRST:@021449z4434.90N/12318.05W-WX3in1Mini U=10.0V
2026-03-02 15:49:34 CET: VK7KT-7>APDR16,TCPIP*,qAO,AE5PL-JF:=4104.46S\14553.63Ek146.500MHz/A=000517 https://aprsdroid.org/q
2026-03-02 15:49:34 CET: HK4DAP-19>HK4DAP,TCPIP*,qAC,T2PANAMA:>Estacion Meterologica APRS con SCRIP Python, Raspberry Pi,ESP32 y Sensor BMP280 por HK4DAP
2026-03-02 15:49:32 CET: OE7MZH-B>APDG02,TCPIP*,qAC,OE7MZH-BS:!4716.80ND01127.00E&RNG0001/A=000010 70cm Voice (D-Star) 430.10000MHz +0.0000MHz
2026-03-02 15:49:33 CET: LZ0VRC-10>APE32L,WIDE1-1,qAR,YO7MGH-7:!4313.30NL02332.68E#PHG1060/A=001147Vratsa,Bulgaria
2026-03-02 15:49:33 CET: IR5UEC>APBM1S,TCPIP*,qAS,BM2222:@021449z4349.62N/01027.59ErPHG0000MMDVM 430.0875/435.0875 CC1
2026-03-02 15:49:33 CET: HB9EKJ-S>APDG01,TCPIP*,qAC,HB9EKJ-GS:;HB9EKJ B *021449z4702.40ND00904.80EaRNG0001/A=000010 70cm Voice (D-Star) 438.57500MHz -7.6000MHz
2026-03-02 15:49:33 CET: IW0RED-10>APMI06,TCPIP*,qAC,IW0RED-IT:!4251.95NI01248.01E#PHG3800 IGate Monte Serano CRLNet - http://crlnet.it
2026-03-02 15:49:33 CET: SM5XDH-3>APDR3,WIDE1-1,WIDE2-2,qAR,SM0RGQ-11:!5854.38N/01612.15E3000/000/A=000351
2026-03-02 15:49:33 CET: IR5UEC>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222048
2026-03-02 15:49:33 CET: LTROCK>APN391,qAR,K3KIN-1:!3447.95NS09229.95W#PHG7660 W3,ARn-N LTROCK W5DI Little Rock, AR
2026-03-02 15:49:33 CET: IR0UJM>APBM1S,TCPIP*,qAS,BM2222:@021449z4129.72N/01303.93ErPHG0000Sul Ripetitore <0xc3><0xa8> attivo il TG222059 Roma/Latina del 773 Radio Group (ex TG 222773) SLOT2 mail: radioteam@773radiogroup.it 430.5250/435.5250 CC1
2026-03-02 15:49:33 CET: IR0UJM>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222049
2026-03-02 15:49:33 CET: W2TTT-5>APDR16,TCPIP*,qAC,T2BIO:=3040.25N/08140.08W$342/054/146.520MHz/A=-00235 https://aprsdro
2026-03-02 15:49:33 CET: K0JDD-1>APRX28,TCPIP*,qAC,T2PERTH:T#943,30.2,0.0,138.0,17.0,0.0,00000000
2026-03-02 15:49:33 CET: XE2DAK-9>SRSQSW,WIDE1-1,WIDE2-1,qAR,XE2SI-10:`,U<0x1f>l zv/"5D}438.075MHz / Escape / pateando el botecito
2026-03-02 15:49:33 CET: F1FOO-10>APLRG1,TCPIP*,qAC,T2CAEAST:!L7YycN]ota GLoRa Aprs 433,775 MHz
2026-03-02 15:49:33 CET: TB8ATA-13>AESPG4,TCPIP*,qAC,T2BELGIUM:T#1837,0,0,0,25,85,00000000
2026-03-02 15:49:33 CET: KB3AWQ>APREST,TCPIP*,qAC,THIRD:@021448z4114.63N/07702.04W_343/001g002t023r000P000h71b10369L256eREST
2026-03-02 15:49:33 CET: ZL2MTB-3>APTT4,WIDE1-1,WIDE2-1,qAR,ZL2ARG-1:>GOLDEN BAY DIGI 26C 13.6V
2026-03-02 15:49:34 CET: HS6BUE-3>AESPG4,TCPIP*,qAC,T2TOKYO:=1656.25N/10009.33E_ T=31<0xc2><0xb0> H=45% P=1003 PM 1:[13] , 2.5:[22] , 10:[28] (<0xce><0xbc>g/m3)
2026-03-02 15:49:34 CET: PG1ADW-4>APNL51,TCPIP*,qAI,PG1ADW-2,PG1ADW-4,T2AUSTRIA,T2HUB1,APRSFI-C1:!4819.54N/01425.57E`iGate PG1ADW RX@OCI
2026-03-02 15:49:34 CET: IK1WGX-7>APOSB,IK1WGX-1,C4FM*,qAS,IK1WGX:@021449z4440.62N/00802.29E[SharkRF openSPOT3
2026-03-02 15:49:34 CET: CW5738>APRS,TCPXX*,qAX,CWOP-7:@021449z3256.45N/10550.52W_182/008g012t055r000p000P000h33b10219L000.DsVP
2026-03-02 15:49:34 CET: MB7UKC>APDW18,WIDE1-1,WIDE2-1,qAO,M0ZZX-5:!5157.73N/00116.07E#PHG4020Digipeater & iGate - Shotley Gate JO01PX - Keeper M0HKC
2026-03-02 15:49:34 CET: W7SWT-11>APNU19,WIDE,qAR,VE6AAH-10:!4851.28NS11108.41W#PHG5660 W3 SWEETGRASS W7SWT-11 B1
2026-03-02 15:49:34 CET: IU2HUQ-B>APDG02,TCPIP*,qAC,IU2HUQ-BS:!4626.20ND01107.40E&RNG0001/A=000010 70cm Voice (D-Star) 433.65000MHz +0.0000MHz
2026-03-02 15:49:34 CET: FW5467>APRS,TCPXX*,qAX,CWOP-7:@021449z4139.46N/07444.33W_192/000g000t019r000p001P000b10366h64L290eMB50
2026-03-02 15:49:34 CET: OZ8BLR-9>APDR16,TCPIP*,qAC,T2ROMANIA:=5449.63N/01144.19E>260/054/A=000127 on the road <0xf0><0x9f><0x98><0x80>
2026-03-02 15:49:34 CET: LU7HCN-12>APBM1D,LU7HCN,DMR*,qAR,LU7HCN:@144931h3121.90S/06408.61W[121/000Fede en movimiento!!
2026-03-02 15:49:34 CET: MB7UVB>APDW14,qAO,2E1HJE-10:;GB7TH *111111z5122.86N/00124.29Er430.2875MHz DMR: www.trig.org.uk/gb7th
2026-03-02 15:49:34 CET: N9ICV-C>APDG02,TCPIP*,qAC,N9ICV-CS:!3847.60ND08545.49W&RNG0001/A=000010 2m Voice (D-Star) 145.51250MHz +0.0000MHz
2026-03-02 15:49:34 CET: KI4YTE-N>APDG03,TCPIP*,qAC,KI4YTE-NS:!4515.78ND09338.24W&/A=00000070cm MMDVM Voice (C4FM) 446.52500MHz +0.0000MHz, KI4YTE_Pi-Star_ND
2026-03-02 15:49:33 CET: GM7GRH-10>APLRG1,TCPIP*,qAC,T2UKRAINE:!L2FykMgoYa GSelkirk District LoRa APRS iGate Batt=4.21V
2026-03-02 15:49:33 CET: WA6LCN-8>SXPQWX,WIDE1-1,WIDE2-1,qAO,WA6LCN-10:`2<dl##YY`"4$}_3
2026-03-02 15:49:33 CET: EL-CN8EAA>RXTLM-1,TCPIP,qAR,CN8EAA:T#541,0.00,0.02,0,1,0.0,00000000,RepeaterLogic
2026-03-02 15:49:33 CET: EW4266>APRS,TCPXX*,qAX,CWOP-4:@021449z4415.33N/07305.98W_202/003g008t010r000p000P000h51b10384L499.DsVP
2026-03-02 15:49:33 CET: NX2I-11>APRX29,TCPIP*,qAC,T2RDU::NX2I-11 :PARM.Avg 10m,Avg 10m,RxPkts,IGateDropRx,TxPkts
2026-03-02 15:49:34 CET: W1HS-11>APMI06,TCPIP*,qAC,T2NALA:@021449z4315.93NT07221.29W&PHG7480/RX/TX iGate Perry Mountain, Charlestown, NH (14.2V 25.4F)
2026-03-02 15:49:34 CET: IW4DGS-S>APDG01,TCPIP*,qAC,IW4DGS-GS:;IW4DGS B *021449z4412.29NW01203.57EiRNG0001/A=000010 70cm Voice (D-Star) 430.00000MHz +0.0000MHz, APRS for ircDDBGateway
2026-03-02 15:49:34 CET: IW4DGS-S>APDG01,qAS,IW4DGS:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:34 CET: DW9097>APN000,TCPXX*,qAX,CWOP-5:@021449z3737.47N/01502.74E_272/002g007t056r000p000P000b10231h74L348eMB63
2026-03-02 15:49:34 CET: IU2HUQ-S>APDG01,TCPIP*,qAC,IU2HUQ-GS:;IU2HUQ B *021449z4626.20ND01107.40EaRNG0001/A=000010 70cm Voice (D-Star) 433.65000MHz +0.0000MHz
2026-03-02 15:49:34 CET: LW2HAH>APBM1D,LW2HAH,DMR*,qAR,LW2HAH:@144931h3237.96S/06241.52W[116/001APRS_DMR_LW2HAH-7_DV
2026-03-02 15:49:34 CET: EI7JQ-14>APDR16,TCPIP*,qAC,T2CSNGRAD:=5323.87N/00802.87Wu020/050/A=000444 I monitor 145.500 and DMR
2026-03-02 15:49:34 CET: PY2NET-5>APDR16,TCPIP*,qAC,T2LAUSITZ:=2258.77S/04704.69W(/A=002158 Robson 439.725 DStar Valinhos SP
2026-03-02 15:49:34 CET: W4GCW-9>APT314,N1KSC-1*,WIDE1*,KM4ZYG-10*,WIDE2*,qAR,W4KBW:>Voice on 146.520
2026-03-02 15:49:34 CET: KD1KE>APU25N,TCPIP*,qAC,T2PERTH:@021449z4428.85N/06920.58W_178/001g005t009r000p000P000h52b10347WX Station of KD1KE {UIV32N}
2026-03-02 15:49:34 CET: EL-E71ACU>RXTLM-1,TCPIP,qAR,E71ACU::EL-E71ACU:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:34 CET: EL-E71ACU>RXTLM-1,TCPIP,qAR,E71ACU:T#010,0.00,0.67,0,21,0.0,00000000,SimplexLogic
2026-03-02 15:49:34 CET: FW3802>APN000,TCPXX*,qAX,CWOP-4:@021449z4732.40N/11710.20W_302/000g001t046r000p000P000b10322h70eMB63
2026-03-02 15:49:34 CET: KG7JQP>APWW11,TCPIP*,qAC,T2PANAMA:>021449zDX: W7SWT-11 38.5mi 78<0xb0> 14:49 4851.28N 11108.41W
2026-03-02 15:49:34 CET: HK4RAU-40>HK4DAP,TCPIP*,qAC,T2CAEAST:>Estacion Meterologica APRS con SCRIP Python, Raspberry Pi,ESP32 y Sensor BMP280 por HK4DAP
2026-03-02 15:49:34 CET: HK4RAU-40>HK4DAP,TCPIP*,qAC,T2CAEAST:@021449z0728.01N/07651.04W_010/010g000t082r000p000h78b10100ESTACION DEL CLIMA APRS ZONA 4 RADIOAFICIONADOS UNIDOS 146.520 MHz en Simplex Fonia - Reporte del clima en Apartad<0xc3><0xb3>: nubes dispersas
2026-03-02 15:49:34 CET: VA6AEA>APDW16,TCPIP*,qAC,T2CAEAST:!5304.71NR11408.68W&VA6AEA iGate
2026-03-02 15:49:34 CET: EA5JMY-13>APRS,TCPIP*,qAC,T2UK:=3929.52N/00022.44W_.../...g...t065r...p...P...h54b10187
2026-03-02 15:49:34 CET: EA5JMY-13>APRS,TCPIP*,qAC,T2UK:T#070,050,304,000,000,000,00000000
2026-03-02 15:49:34 CET: WINLINK>APWL2K,TCPIP*,qAS,WLNK-1:;KD2DO-10 *020249z4306. NW07737. Wa145.030MHz Winlink Packet Gateway
2026-03-02 15:49:34 CET: SM0TCZ-13>APMI01,TCPIP*,qAS,SM0TCZ:@021449z5914.43N/01757.80EI#WX3in1Plus
2026-03-02 15:49:34 CET: DB0DB>APU25N,TCPIP*,qAC,T2POLNW:=4746.60N/00742.07EIigate Blauen {UIV32N}
2026-03-02 15:49:34 CET: K4KJQ-13>APMI06,TCPIP*,qAS,K4KJQ:;146.76-KY*111111z3802.38N/08424.18Wr146.760MHz T67 -060 R12m
2026-03-02 15:49:34 CET: YD3BHZ-7>APBTUV,YH3NPX-4*,WIDE2-1,qAR,YH3NPX-2:!0745.47S/11312.58E>208/001/A=0001295RH PRO JATIM-CLUB
2026-03-02 15:49:34 CET: OH3KUN-9>APLT00,qAO,OH3ERV-L1:!6128.15N/02346.49E>174/019/A=000341
2026-03-02 15:49:34 CET: IR2UDV>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222055
2026-03-02 15:49:34 CET: W9MID-10>GPS,qAR,W9RCA-10:MARC MEETING 8am 3rd Satur<0x7f><0x7f> at REMC Hq. Club Net Sun 7pm. [Unsupported packet format]
2026-03-02 15:49:34 CET: WV8AR-10>APRX29,qAR,KG4DVE-10:;WV8AR-10 *111111z3824.36N/08154.06W#APRS Digi - Scott Depot, WV -WV8AR-
2026-03-02 15:49:34 CET: ED1ZAK-3>UIDIGI,qAR,ED1ZBA-3:UIDIGI 1.9
2026-03-02 15:49:34 CET: TB3DEU-13>APLRT1,WIDE1-1,qAR,TB3DEU-16:=/:1(lUmLgbD@Q
2026-03-02 15:49:34 CET: G8DHE-1>APDR16,TCPIP*,qAC,T2KA:=5049.68N/00022.95W$097/000/A=000196 GP9
2026-03-02 15:49:34 CET: EA5IHI-13>AESPG4,TCPIP*,qAC,T2BIO:>V.4.4b Rx:0 Digi:0 Tx:32 UpTime:00.28
2026-03-02 15:49:34 CET: E21ZSS-S>APDG01,TCPIP*,qAC,E21ZSS-GS:;E21ZSS C *021449z1240.13ND10116.52EaRNG0001/A=000010 2m Voice (D-Star) 145.56250MHz +0.0000MHz
2026-03-02 15:49:34 CET: EA3EW-N>APDG03,TCPIP*,qAC,EA3EW-NS:!4115.00ND00110.20E&/A=00000070cm MMDVM Voice (C4FM) 430.01250MHz +0.0000MHz, EA3EW_Pi-Star_ND
2026-03-02 15:49:34 CET: KB8SCS-1>APMI06,WIDE2-2,qAR,KD9MAB-15:@021355z4024.51N/08448.21W#W9JCA Coffee and Donuts SAT'S 8am JAY CNTY FAIRGROUNDS SCOUT CABIN
2026-03-02 15:49:34 CET: IR3UIB>APBM1S,TCPIP*,qAS,BM2222:@021449z4630.42N/01255.97ErPHG0000ARI RCE FVG 433.1250/431.5250 CC1
2026-03-02 15:49:34 CET: DL8FMA-10>APLG01,TCPIP*,qAC,T2UKRAINE:=4759.79NL01200.42E&LoRa iGATE APRS 70cm 433.775 MHz, Info: github.com/lora-aprs/LoRa_APRS_iGate
2026-03-02 15:49:34 CET: IZ1ZCT-15>APLRT1,WIDE2-2,qAO,IZ1RWC-15:=/82Q?PyYC>=1QARI La Spezia - sysop: Roberto - iz1zct@gmail.com|(%%;|
2026-03-02 15:49:34 CET: IR3UIB>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222056
2026-03-02 15:49:34 CET: GW3184>APRS,TCPXX*,qAX,CWOP-4:@021450z4024.40N/08005.77W_085/000g004t025r000p000P000h62b10326.WFL
2026-03-02 15:49:34 CET: LU2DKV-10>AESPG4,TCPIP*,qAC,T2SYDNEY::LU2DKV-10:EQNS.0,1,0,0,1,0,0,1,0,0,-1,0,0,1,0
2026-03-02 15:49:34 CET: GW2602>APN000,TCPXX*,qAX,CWOP-7:@021449z3821.45N/12200.01W_186/002g005t056r000p000P000b10171h81eMB60
2026-03-02 15:49:34 CET: DO0KL-S>APDG01,TCPIP*,qAC,DO0KL-GS:;DO0KL B *021449z5129.70NW00632.85EiRNG0019/A=000262 70cm Voice (D-Star) 439.13750MHz -7.6000MHz, APRS for ircDDBGateway
2026-03-02 15:49:34 CET: DO0KL-S>APDG01,qAS,DO0KL:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:34 CET: GW0DQW-10>APLRG1,TCPIP*,qAC,T2PRT:!L4D:7Mczua GLoRa APRS
2026-03-02 15:49:34 CET: WA8LMF-42>APU25N,TCPIP*,qAC,WG3K-CA:>251749zAPRS-over-VARA Webservers http://WA8LMF.net/map
2026-03-02 15:49:34 CET: DL1NUX>APFII0,TCPIP*,qAC,APRSFI:@144934h5014.07N/01058.85Eb086/006/A=000998Attila B37!w>o!
2026-03-02 15:49:34 CET: TI0ARC-13>APRS,TCPIP*,qAC,FIRST:=0949.91N/08352.86W_.../...g...t070r...p...P...h69b10122
2026-03-02 15:49:34 CET: TI0ARC-13>APRS,TCPIP*,qAC,FIRST:T#239,080,306,000,000,000,00000000
2026-03-02 15:49:34 CET: IR0UJN>APBM1S,TCPIP*,qAS,BM2222:@021449z4144.79N/01239.02ErPHG0000Sono attivi i seguenti TG: 222059 773 Radio Group Roma/Latina (ex TG222773) SLOT2 e 222094 R.N.R.E. Protezione Civile (ex TG22211) SLOT1 430.5375/435.5375 CC1
2026-03-02 15:49:34 CET: IR0UJN>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222059
2026-03-02 15:49:35 CET: GW5315>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3523.12N/08054.20W_005/005g010t048r000p000P000h64b10333L305AmbientCWOP.com
2026-03-02 15:49:35 CET: F4HQI-10>APLRG1,TCPIP*,qAC,T2FRANCE:=L5zz_P2v`a !GLoRa APRS 73'
2026-03-02 15:49:35 CET: IR0UEI>APBM1S,TCPIP*,qAS,BM2222:@021449z4226.04N/01235.84ErPHG0000IR0UEI - S. Pancrazio TR 431.4625/433.0625 CC1
2026-03-02 15:49:35 CET: IR0UEI>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222060
2026-03-02 15:49:34 CET: ON0ABT-2>APMI03,WIDE2-2,qAR,ON1TG-10:=5106.91N200318.93E# DIGI Beernem - WOC vzw
2026-03-02 15:49:34 CET: DO7AD>APU25N,TCPIP*,qAC,T2FINLAND:<IGATE,MSG_CNT=296,LOC_CNT=30
2026-03-02 15:49:34 CET: N4YSA-9>S8RT3W,WIDE1-1,qAR,KN4FM-8:`jA-l!sk/`"7-}_%
2026-03-02 15:49:34 CET: WX9EMS>APU25N,TCPIP*,qAC,T2USANW:=4233.11NI08824.12W&PHG5260/Lake Geneva IGate {UIV32N}
2026-03-02 15:49:34 CET: KJ7OGG>APN000,TCPXX*,qAX,CWOP-3:@021449z3328.39N/11204.47W_330/001g003t067r000p000P000b10099h38eMB63
2026-03-02 15:49:34 CET: G0HWW-3>UR2RRR-2,WIDE1-1,WIDE2-2,qAR,MB7URG:`w9Ml<0x1d>H#/"4f}PicoAPRS by DB1NTO
2026-03-02 15:49:34 CET: YO6IOG-10>APMI04,WIDE2-2,qAO,YO6GZJ-10:@021449z4539.48N/02537.22E& I-Gate Brasov
2026-03-02 15:49:34 CET: WA8LMF-10>APU25N,qAo,WA8LMF-42:>251749zAPRS-over-VARA Webservers http://WA8LMF.net/map
2026-03-02 15:49:34 CET: GW4151>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4248.99N/08601.12W_339/002g004t026r000p000P000h75b10133L086AmbientCWOP.com
2026-03-02 15:49:34 CET: G7PAZ-S>APDG01,TCPIP*,qAC,G7PAZ-GS:;G7PAZ B *021449z5257.98ND00038.25WaRNG0001/A=000010 70cm Voice (D-Star) 438.80000MHz +0.0000MHz
2026-03-02 15:49:34 CET: VK6JI-11>APN000,TCPIP*,qAC,T2TAS:@021449z3149.22S/11548.55E_210/000g001t067r000p000P000b10123h75L000eMB59
2026-03-02 15:49:34 CET: IU0PNB-D>APDG03,TCPIP*,qAC,IU0PNB-DS:!4127.97N\01239.23E-/A=00000070cm MMDVM Voice (DMR) 431.60000MHz +0.0000MHz, APRS for DMRGateway
2026-03-02 15:49:34 CET: IU0PNB-D>APDG03,qAS,IU0PNB:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:34 CET: SR6NWS>APMI06,TCPIP*,qAC,T2POLAND:;145.7875S*021449z5040.82N/01629.14Er145.7875MHz c094 -060 R60k SR6S Wielka Sowa 7.1C
2026-03-02 15:49:34 CET: OE6CFD-1>APRSGW,TCPIP*,qAR,OE6CFD-1:!4703.96N\01540.02ELEuropaberg#GAFF/B=100/A=001637
2026-03-02 15:49:34 CET: KG5APU>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3310.81N/09629.88W_171/001g001t063r000p000P000h87b10201L063AmbientCWOP.com
2026-03-02 15:49:35 CET: WB2UTI-S>APDG01,TCPIP*,qAC,WB2UTI-GS:;WB2UTI B *021449z2852.89ND08200.11WaRNG0001/A=000010 70cm Voice (D-Star) 433.50000MHz +0.0000MHz
2026-03-02 15:49:35 CET: K1LVA>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3334.73N/08133.90W_013/003g005t059r000p000P000h86b10284L567AmbientCWOP.com
2026-03-02 15:49:35 CET: FW9876>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3320.33N/11713.29W_175/000g000t053r000p000P000h96b10136L014AmbientCWOP.com
2026-03-02 15:49:35 CET: JP3PYY-10>APU25N,TCPIP*,qAC,T2HAKATA:>192248zI-GATE 144.66MHz 1200bps
2026-03-02 15:49:35 CET: KE5FEN-N>APDG03,TCPIP*,qAC,KE5FEN-NS:!2924.28ND09843.12W&/A=00000070cm MMDVM Voice (C4FM) 438.80000MHz +0.0000MHz, KE5FEN_Pi-Star_ND
2026-03-02 15:49:35 CET: N6LKA-R>APDG03,TCPIP*,qAC,N6LKA-RS:!3359.88ND11702.91W&/A=00000070cm MMDVM Voice (C4FM) 449.64000MHz -5.0000MHz, N6LKA_Pi-Star_RPT
2026-03-02 15:49:35 CET: K1RFS>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z4032.46N/11202.08W_186/000g000t043r000p001P001h94b09988L018AmbientCWOP.com
2026-03-02 15:49:35 CET: M0NOM-10>APLRG1,TCPIP*,qAC,T2CZECH:!L3!v7Mf'Ma GWindermere LoRa APRS iGATE
2026-03-02 15:49:35 CET: GW5686>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4443.54N/06723.77W_329/004g004t013r000p000P000h45b10336AmbientCWOP.com
2026-03-02 15:49:35 CET: GW5761>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3232.66N/09421.96W_225/001g006t064r000p000P000h81b10216L395AmbientCWOP.com
2026-03-02 15:49:35 CET: VK2RTZ-S>APDG01,TCPIP*,qAC,VK2RTZ-GS:;VK2RTZ B *021449z3234.20SD15115.60EaRNG0001 440 Voice 438.15000MHz -7.0000MHz
2026-03-02 15:49:35 CET: SR8NZ-10>APLRFD,TCPIP*,qAC,EIGHTH:!L5FswSv7?# GLoRa APRS Digi 70cm Batt=4.05V|'l#.!!!!!!!!|
2026-03-02 15:49:35 CET: F4ETJ-1>APX210,TCPIP*,qAC,T2POLAND:;F1ZAG/R *021449z4532.10N/00021.57WrEntre 1270.025_Sortie 1298.025 Shift-28Mhz
2026-03-02 15:49:35 CET: ER-DB0NLS>APSVX1,TCPIP*,qAS,DB0NLS:T#012,000,006,000,001,000,00000000
2026-03-02 15:49:34 CET: HK4DAP-19>HK4DAP,TCPIP*,qAC,T2PANAMA:@021449z0613.13N/07514.61W_270/003g000t069r000p000h68b10120ESTACION DEL CLIMA APRS - Reporte del clima en El Pe<0xc3><0xb1>ol: muy nuboso
2026-03-02 15:49:34 CET: XE3RFM-9>R0UY7V,WIDE1-1,WIDE2-1,qAR,XE3EA:`uAPl{>>/GRUPO APRS MERIDA
2026-03-02 15:49:34 CET: DB0ZOD>APRS,TCPIP*,qAC,T2GREECE:;EL-DB0ZOD*111111z5231.43NE01325.90E0145.662MHz T000 R31k 145.6625 MHz -600 kHz / 1750Hz / FM-Repeater / Echolink Node: 969258
2026-03-02 15:49:34 CET: KK7LLM-D>APDG03,TCPIP*,qAC,KK7LLM-DS:!4011.19N/11136.52Wx/A=00000070cm MMDVM Voice (DMR) 438.20000MHz -5.0000MHz, APRS for DMRGateway
2026-03-02 15:49:34 CET: KK7LLM-D>APDG03,qAS,KK7LLM:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:34 CET: CE4PLU-10>APLRG1,TCPIP*,qAC,T2CHILE:!L_pu8<;vya GAPRS RC PICHILEMU Batt=4.25V
2026-03-02 15:49:34 CET: DL7ATR-2>APWW11,WIDE1-1,WIDE2-1,qAR,DB0SDA:@144840h5036.70N/00618.14E-(Time 0:00:00)PHG4100 Hans-Juergen in Simmerath, DOK G26, dl7atr.darc.de
2026-03-02 15:49:34 CET: JA9VEZ-1>APNU19,WIDE1-1,qAR,JA9UZF-10:!3650.16N113659.47E#144.66 1k2 Himi 1
2026-03-02 15:49:34 CET: N4YH-10>APWW11,TCPIP*,qAC,SECOND:@144934h3520.29N/08510.28W#John 3:16
2026-03-02 15:49:34 CET: HB9RMW-41>APRSMC,TCPIP*,qAR,HB9RMW-40:!4714.53N/00827.91E>/B=100/A=001608
2026-03-02 15:49:34 CET: DO0KL-B>APDG02,TCPIP*,qAC,DO0KL-BS:!5129.70NW00632.85EiRNG0019/A=000262 70cm Voice (D-Star) 439.13750MHz -7.6000MHz, APRS for ircDDBGateway
2026-03-02 15:49:34 CET: DO0KL-B>APDG02,qAS,DO0KL:>Powered by WPSD (https://wpsd.radio)
2026-03-02 15:49:34 CET: PD7EDI-14>UQ5WP8,qAO,PI1ZLD-2:`zZ9l ok/`"3m}_4
2026-03-02 15:49:35 CET: FW4237>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3724.20N/07935.89W_081/005g006t038r000p000P000h65b10338L061AmbientCWOP.com
2026-03-02 15:49:35 CET: CW7220>APRS,TCPXX*,qAX,CWOP-7:@021449z3854.00N/00124.00E_043/010g...t059L091P000h86b10184.DsVP
2026-03-02 15:49:35 CET: DO8PGG-5>APDR16,TCPIP*,qAC,T2LAUSITZ:=5119.34N/00719.99E$277/002/A=000763 DMR+ Refl. 4671
2026-03-02 15:49:35 CET: N8HON>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3838.08N/07920.77W_171/000g000t035r000p000P000h54b10293L188AmbientCWOP.com
2026-03-02 15:49:35 CET: CA3RJR-10>APLRG1,TCPIP*,qAC,T2MEXICO:!L_CEq<[$2a GLoRa APRS Batt=4.19V Ext=0.71V
2026-03-02 15:49:35 CET: EW8955>APREST,TCPXX*,qAX,CWOP-6:@021448z4009.03N/07947.55W_078/004g004t025r000P000h66b10320L058eREST
2026-03-02 15:49:35 CET: GW5727>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3845.13N/07900.35W_112/001g002t033r000p000P000h69b10343L128AmbientCWOP.com
2026-03-02 15:49:35 CET: DB0NLS>APSVX1,TCPIP*,qAC,T2LAUSITZ::ER-DB0NLS:PARM.RX Avg 15m,TX Avg 15m,RX Count 15m,TX Count 15m,,RX,TX
2026-03-02 15:49:35 CET: DB0NLS>APSVX1,TCPIP*,qAC,T2LAUSITZ::ER-DB0NLS:UNIT.erlang,erlang,receptions,transmissions
2026-03-02 15:49:35 CET: DB0NLS>APSVX1,TCPIP*,qAC,T2LAUSITZ::ER-DB0NLS:BITS.11111111,SvxLink RepeaterLogic
2026-03-02 15:49:35 CET: DB0NLS>APSVX1,TCPIP*,qAC,T2LAUSITZ::ER-DB0NLS:EQNS.0,0.00100,0,0,0.00100,0
2026-03-02 15:49:35 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.38N/01603.26EQ257/051/A=000643 MOBILE DMR TG:21966
2026-03-02 15:49:35 CET: F4JIJ-10>APLRG1,TCPIP*,qAC,T2UK:!L5g>MO$AUa GLoRa APRS http://www.f6kbf.fr/
2026-03-02 15:49:35 CET: BR4IN>APDR16,TCPIP*,qAC,T2UKRAINE:=3638.3 N/11702.2 Er439.110MHz -5 88.5 <0xe6><0xb5><0x8e><0xe5><0x8d><0x97><0xe9><0xbb><0x84><0xe6><0xb2><0xb3><0xe4><0xb8><0x9a><0xe4><0xbd><0x99><0xe6><0x97><0xa0><0xe7><0xba><0xbf><0xe7><0x94><0xb5> <0xe7><0xa4><0xba><0xe4><0xbd><0x8d><0xe7><0x82><0xb9>
2026-03-02 15:49:35 CET: S57DV-9>APLRT1,WIDE1-1,qAR,S55DLM-10:=\74@zR/pl>9HG
2026-03-02 15:49:34 CET: K7SIE-N>APDG03,TCPIP*,qAC,K7SIE-NS:!5000.00ND00300.00W&/A=00000070cm MMDVM Voice (C4FM) 438.80000MHz +0.0000MHz, K7SIE_Pi-Star_ND
2026-03-02 15:49:34 CET: N2RWE>APMI06,TCPIP*,qAC,T2RDU:T#018,189,007,000,077,141,00000000
2026-03-02 15:49:34 CET: UN7GWP-1>APLOX1,WIDE1-1,qAO,UN9GIE-1:!L8Z-{aw'R# XLORA DIGI i-GATE 434,855 @300 bps P=5.27V
2026-03-02 15:49:35 CET: KC5TXI>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3243.04N/09708.37W_194/001g001t068r000p000P000h79b10204L086AmbientCWOP.com
2026-03-02 15:49:35 CET: GW3614>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3849.17N/12115.33W_214/000g000t051r000p000P000h90b10179L006AmbientCWOP.com
2026-03-02 15:49:35 CET: GW3348>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4804.41N/12207.71W_093/004g005t032r000p000P000h95b10173L004AmbientCWOP.com
2026-03-02 15:49:35 CET: OK9DOB-9>APRS,TCPIP*,qAC,T2CZECH:T#158,0000,422,00,000000,211,01000000
2026-03-02 15:49:35 CET: SR9NDJ>APRX28,TCPIP*,qAC,T2RADOM:!5033.45NI01927.02E&PHG3280 Digi & IGate Jurajski W2,SPn by SQ9NFI on Linux operator SP9JKL ==> http://sq9nfi.pzk.pl
2026-03-02 15:49:35 CET: SR9NDJ>APRX28,TCPIP*,qAC,T2RADOM:;SR9DJK *000000h5033.45N/01927.01E&DMR Repeater Static TG260/TS1 2609/TS2 operator SP9JKL
2026-03-02 15:49:35 CET: WB2UTI-B>APDG02,TCPIP*,qAC,WB2UTI-BS:!2852.89ND08200.11W&RNG0001/A=000010 70cm Voice (D-Star) 433.50000MHz +0.0000MHz
2026-03-02 15:49:35 CET: EW2895>APRS,TCPXX*,qAX,CWOP-3:@021449z3953.17N/08609.68W_043/001g005t029r000p000P000b10282h81eMB32
2026-03-02 15:49:35 CET: GW5656>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3836.38N/07914.81W_315/002g003t034r000p000P000h66b10352L214AmbientCWOP.com
2026-03-02 15:49:35 CET: GW5669>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3830.60N/07857.88W_123/002g003t033r000p000P000h53b10347L174AmbientCWOP.com
2026-03-02 15:49:35 CET: GW5845>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3855.60N/07828.06W_056/004g006t031r000p000P000h61b10345L046AmbientCWOP.com
2026-03-02 15:49:35 CET: GW5801>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z2935.78N/09507.33W_020/000g001t068r000p000P000h97b10203L060AmbientCWOP.com
2026-03-02 15:49:35 CET: HB9DM-3>APWW10,TCPIP*,qAC,T2PERTH:>021449zSwissDMR 438.325 MHz (-7.6 MHz) CC2
2026-03-02 15:49:35 CET: PD0JOY-9>APDR15,TCPIP*,qAC,T2LAUSITZ:=5154.25N/00420.21E>295/001/144.725MHz/A=000219 bicycle
2026-03-02 15:49:35 CET: DL1GHN-9>TX2QX5,WIDE1-1,WIDE2-1,qAS,DB0LHR-10:`}QumR">/]"5p}=
2026-03-02 15:49:35 CET: PA1ZF-9>UR1UU7,WIDE1-1,WIDE2-1,qAO,3UTR1-10:`{8>o-Gv/`"3k}430.125MHz T077 +160 Bartel Gytsjerk FRYSLAN_%
2026-03-02 15:49:35 CET: E23JWE-1>AESPG4,TCPIP*,qAC,T2INDY::E23JWE-1 :UNIT.ug/m3,ug/m3,ug/m3,dBm,V
2026-03-02 15:49:35 CET: KD7LBV-9>S3RVQW,WIDE1-1,WIDE2-1,qAO,W9EN-10:`'QBmU7k/`"7k}Monitoring 440.375 MHz T100 -5.0_%
2026-03-02 15:49:35 CET: CE7DE-5>APLRG1,WIDE1-1,qAR,CE7DE-4:!Lcy1:;oVh# xG147.180 +600 T107.2 - 146.730 -600 T107.2 http://trauko.org/radioclub
2026-03-02 15:49:35 CET: IR4ZZW>APBM1S,TCPIP*,qAS,BM2222:@021449z4454.40N/00932.38ErPHG0000Pigazzano - Piacenza 431.4375/433.0375 CC1
2026-03-02 15:49:35 CET: FW9998>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4223.78N/07108.04W_140/000g000t019r000p000P000h34b10392L372AmbientCWOP.com
2026-03-02 15:49:35 CET: GW0220>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3549.89N/09438.07W_308/000g001t048r000p000P000h99b10200L036AmbientCWOP.com
2026-03-02 15:49:35 CET: GW0383>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3835.81N/07809.11W_119/000g000t033r000p000P000h46b10326L136AmbientCWOP.com
2026-03-02 15:49:35 CET: GW0415>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4454.11N/09316.74W_135/002g005t031r000p000P000h61b10237L095AmbientCWOP.com
2026-03-02 15:49:35 CET: GW0484>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4824.86N/11058.25W_099/000g000t018r000p000P000h78b10090L027AmbientCWOP.com
2026-03-02 15:49:35 CET: GW0554>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3044.31N/08907.69W_299/001g001t061r000p000P000h99b10239L246AmbientCWOP.com
2026-03-02 15:49:35 CET: FW3002>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z2708.62N/08010.72W_152/003g009t077r000p000P000h72b10234L551AmbientCWOP.com
2026-03-02 15:49:35 CET: PA3FAS>APDR16,TCPIP*,qAC,T2ROMANIA:=5209.62N/00624.58E//A=000223 TYT TH-9800D
2026-03-02 15:49:35 CET: SP1TRX-7>APLRT1,WIDE1-1,qAR,SP1TRX-10:!/38!=R.$S>5SQ
2026-03-02 15:49:35 CET: DW8BRQ-A>APSTAR,TCPIP*,qAC,T2PERTH:!0830.61N/12317.04EaPHG1210/A=000033AllStarLink 3 MicroNode 524255 Dodoy Mobile @ 432.700MHz
2026-03-02 15:49:35 CET: PI1PTN>APSVX1,TCPIP*,qAC,T2PRT:;EL-PI1PTN*111111z5215.46NE00536.60E0PHG3360/438.450MHz Toff R21k SvxLink by SM0SVX (www.svxlink.org)
2026-03-02 15:49:35 CET: GW5579>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z5359.64N/02354.97E_253/001g002t041r000p000P000h78b10246L016AmbientCWOP.com
2026-03-02 15:49:35 CET: GW5755>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z2801.78N/03437.47E_344/016g017t072r000p000P000h30b10141L030AmbientCWOP.com
2026-03-02 15:49:35 CET: GRYLCP-11>APDW17,HOP7-7,HOP7-7,qAR,AB1PH-15:!4208.85N/07113.60We212/001/A=000264AB1PH ATGP
2026-03-02 15:49:35 CET: OE1XZS>APRS,TCPIP*,qAC,T2AUSTRIA:;EL-OE1XZS*111111z4811.46NE01619.25E0430.562MHz T000 R21k OE1XZS-R TETRA-DMO-RPT OE:VIENNA/AUSTRIA
2026-03-02 15:49:35 CET: SV9DRS-N>APDG03,TCPIP*,qAC,SV9DRS-NS:!3520.11ND02503.77E&/A=00000070cm MMDVM Voice (C4FM) 438.80000MHz +0.0000MHz, SV9DRS_Pi-Star_ND
2026-03-02 15:49:35 CET: GW5795>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4203.25N/09725.76W_154/004g007t032r000p000P000h65b10243L065AmbientCWOP.com
2026-03-02 15:49:35 CET: GW5939>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z0657.61N/15812.61E_109/000g000t078r000p000P000h91b10089AmbientCWOP.com
2026-03-02 15:49:35 CET: GW6033>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3939.04N/10446.70W_106/004g005t048r000p000P000h42b10148L049AmbientCWOP.com
2026-03-02 15:49:35 CET: FW5255>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3953.76N/07457.51W_150/000g001t028r000p000P000h53b10391L110AmbientCWOP.com
2026-03-02 15:49:35 CET: GW6055>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3531.55N/08433.09W_026/002g003t055r000p000P000h73b10262L170AmbientCWOP.com
2026-03-02 15:49:35 CET: GW0552>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3825.13N/08212.90W_093/003g005t035r000p000P000h59b10257L073AmbientCWOP.com
2026-03-02 15:49:35 CET: FW1299>APN000,TCPXX*,qAX,CWOP-3:@021449z4028.63N/07434.87W_065/000g001t027r000p000P000b10391h51eMB55
2026-03-02 15:49:35 CET: F6KLI-10>APDW16,WIDE1-1,WIDE2-1,qAR,F6KLI-10:!4453.90NI00014.55W&Igate Radio club F6KLI
2026-03-02 15:49:35 CET: KG5JPJ-R>APDG03,TCPIP*,qAC,KG5JPJ-RS:!3612.26ND09406.12W&/A=00155570cm MMDVM Voice (C4FM) 442.52500MHz +5.0000MHz, KG5JPJ_Pi-Star_RPT
2026-03-02 15:49:35 CET: WX4ET>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3633.45N/08233.72W_081/002g002t048r000p004P004h92b10235L063AmbientCWOP.com
2026-03-02 15:49:35 CET: JK2QUE-10>APU25N,TCPIP*,qAC,T2ROMANIA:=3441.31N913724.06E&PHG3230/9600bps 144.64MHz Toyohashi-City
2026-03-02 15:49:35 CET: HZ1AX-10>APSN01,TCPIP*,qAC,T2TAIWAN:=2642.24N/05003.06E#/A=000094CSN iGate
2026-03-02 15:49:35 CET: DW2447>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3258.95N/08011.00W_096/002g003t057r000p000P000h81b10285L239AmbientCWOP.com
2026-03-02 15:49:35 CET: N3TTT-13>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z3908.90N/07535.36W_026/006g008t031r000p000P000h60b10380L166AmbientCWOP.com
2026-03-02 15:49:35 CET: DL7TA-1>APRS,TCPIP*,qAS,DB0ZOD:@021449z5232.80NT01321.80EATetra TMO (2635176)
2026-03-02 15:49:35 CET: YG1CBG-13>AESPG4,TCPIP*,qAC,T2TAIWAN:>V.4.4c Rx:0 Digi:0 Tx:933 UpTime:12.33
2026-03-02 15:49:35 CET: CQ0EPTM>APRS,TCPIP*,qAC,T2CSNGRAD:;ER-CQ0EPT*111111z3709.73NE00832.11W0433.800MHz T000 R23k [Tetra-DMO] 433.800
2026-03-02 15:49:35 CET: ER-DB0DMO>RXTLM-1,TCPIP,qAR,DB0DMO::ER-DB0DMO:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:36 CET: DW3024>APRS,TCPXX*,qAX,CWOP-4:@021449z3259.05N/07947.98W_033/003g010t053L162r000P000p000h90b10279VL1252
2026-03-02 15:49:36 CET: ER-DB0DMO>RXTLM-1,TCPIP,qAR,DB0DMO:T#076,0.00,0.01,0,1,0.0,00000000,TetraLogic
2026-03-02 15:49:36 CET: YM2KG>BEACON,WIDE2-2,qAR,YM1KE-1:;YM1KWB *021410z4056.71N/02911.47E# ANARAD 145.775 67TON KURBAN BAYRAMINIZI KUTLARIZ
2026-03-02 15:49:35 CET: KB1RMA-13>APRS,TCPIP*,qAC,AMBCWOP-2:@021449z4247.75N/07050.40W_320/006g009t019r000p000P000h35b10405L519AmbientCWOP.com
2026-03-02 15:49:35 CET: GW5769>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4321.43N/07329.99W_031/000g000t020r000p000P000h44b10415L411AmbientCWOP.com
2026-03-02 15:49:35 CET: GW5833>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3507.33N/10629.37W_023/002g002t053r000p000P000h36b10221L038AmbientCWOP.com
2026-03-02 15:49:35 CET: FW0062>APRS,TCPXX*,qAX,CWOP-6:@021449z3223.12N/11101.45W_108/001g002t054r000p000P000b10167h38L049eMB51
2026-03-02 15:49:35 CET: Z32CPU>APDW16,qAO,Z32CPU:!4159.92NR02123.79E& Weather in Skopje => 15 <0xc2><0xb0>C - Wind => 1.03 m/s N - Humidity => 47 % - Pressure => 1023 hPa
2026-03-02 15:49:35 CET: G7TKI-13>APWEE5,TCPIP*,qAC,CWOP-7:@021445z5152.50N/00022.30E_249/002g004t063r000p001P000b10183h63L197.weewx-5.1.0-Interceptor
2026-03-02 15:49:35 CET: AB5KT-1>APRS,TCPXX*,qAX,CWOP-3:@021444z2916.90N/09826.72W_111/003g007t067r000p000P000b10192h82eMB32
2026-03-02 15:49:35 CET: FW6592>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3500.14N/07905.04W_134/002g008t044r000p000P000h85b10314L073AmbientCWOP.com
2026-03-02 15:49:35 CET: 2E0UCD-10>APLRG1,TCPIP*,qAC,T2TAS:!L4b:FMHNGa GLandcross LoRa APRS Igate
2026-03-02 15:49:35 CET: GW0634>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3626.88N/10310.39W_237/005g006t045r000p000P000h46b08902L066AmbientCWOP.com
2026-03-02 15:49:35 CET: HB4LO-1>APLRG1,TCPIP*,qAC,T2CZECH:=4649.70NL00656.38E&HB4LO-P - IGATE ELO
2026-03-02 15:49:35 CET: EL-EA3URE>RXTLM-1,TCPIP,qAR,EA3URE::EL-EA3URE:UNIT.RX Erlang,TX Erlang,RXcount/10m,TXcount/10m,none1,STxxxxxx,logic
2026-03-02 15:49:35 CET: EL-EA3URE>RXTLM-1,TCPIP,qAR,EA3URE:T#017,0.00,0.01,0,5,0.0,00000000,SimplexLogic
2026-03-02 15:49:35 CET: GW2294>APN000,TCPXX*,qAX,CWOP-5:@021442z4724.67N/00732.70E_053/002g006t061r000p000P000b10198h48L230eMB58
2026-03-02 15:49:35 CET: GW1778>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z2525.55S/04918.22W_316/001g001t071r000p000P000h71b10213L262AmbientCWOP.com
2026-03-02 15:49:35 CET: IQ9TP-10>APWW11,TCPIP*,qAC,T2IRELAND:>021449zAPRSISCE/32
2026-03-02 15:49:35 CET: OH6DL-10>APWW11,TCPIP*,qAC,T2CHILE:<IGATE,MSG_CNT=8,LOC_CNT=9,DIR_CNT=9,RF_CNT=9,DX=1*EI6HKB-9(2332km@256<0xb0>)
2026-03-02 15:49:35 CET: N4GQY-10>APWW11,TCPIP*,qAC,T2SYDNEY:@144935h3045.70N/08607.35W#APRS-IS
2026-03-02 15:49:35 CET: IQ9TP-10>APWW11,TCPIP*,qAC,T2IRELAND:)IQ9TP!3800.34N\01232.07E-Sez. ARI Trapani - dom h9:30 mer h20:30
2026-03-02 15:49:35 CET: IQ9TP-10>APWW11,TCPIP*,qAC,T2IRELAND:)IR9D!3802.30N/01235.22ErPHG3840RV62/R7 Mt. Erice ARI TP 145.775MHz T094 -060
2026-03-02 15:49:35 CET: IQ9TP-10>APWW11,TCPIP*,qAC,T2IRELAND:)IR9UAC!3802.30N/01235.22ErPHG3860RU014/RU7 Mt. Erice ARI TP 430.175MHz T094 +5000 C4FM
2026-03-02 15:49:35 CET: IQ9TP-10>APWW11,TCPIP*,qAC,T2IRELAND:)IW9FRA-6!3800.53N/01235.83E%DX Cluster ARI TP dxpisder.iw9fra.com:7300
2026-03-02 15:49:35 CET: IQ9TP-10>APWW11,TCPIP*,qAC,T2IRELAND:)IW9FRA-ND!3800.71NW01232.69E&144.900MHz ARI TP C4FM #27644 ROOM ITALY
2026-03-02 15:49:36 CET: SQ6CZH-1>APRX29,TCPIP*,qAC,T2BIO:!5109.07N/01651.85E_(untested) Weather station
2026-03-02 15:49:36 CET: YO8SDE-8>APMI06,TCPIP*,qAS,YO8SDE:@021449z4742.73NI02639.03E# WX3in1Plus2.0 U=12.3V,T=2.5C/36.5F at Curtesti, BT
2026-03-02 15:49:36 CET: IR8ZZZ>APBM1S,TCPIP*,qAS,BM2222:@021449z4049.73N/01407.36ErPHG0000IR8ZZZ Pozzuoli ( Na ) 431.3750/432.9750 CC1
2026-03-02 15:49:36 CET: AB6MB-6>APOSB4,TCPIP*,qAS,AB6MB:@021449z3731.49N/12202.78Wv/A=000000Jeff's openSPOT4
2026-03-02 15:49:36 CET: IW4DST-10>APLRG1,TCPIP*,qAC,T2PERTH:!L7uF'Q(jR& GLoRa_APRS_iGate
2026-03-02 15:49:36 CET: IU5LRH-S>APDG01,TCPIP*,qAC,IU5LRH-GS:;IU5LRH B *021449z4321.09ND01117.50EaRNG0001/A=000010 70cm Voice (D-Star) 431.15000MHz +0.0000MHz
2026-03-02 15:49:36 CET: VE7MYA-10>APJYC1,TCPIP*,qAC,T2PR:;RCMP *111111z5848.58N/12241.61W!
2026-03-02 15:49:35 CET: FW7629>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3515.80N/08034.96W_057/000g000t045r000p000P000h71b10318L143AmbientCWOP.com
2026-03-02 15:49:35 CET: FW8057>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3537.27N/07847.04W_032/003g003t043r000p000P000h72b10311L079AmbientCWOP.com
2026-03-02 15:49:35 CET: CW0333>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z4304.92N/07042.82W_263/003g004t022r000p000P000h35b10324L437AmbientCWOP.com
2026-03-02 15:49:35 CET: GW0586>APRS,TCPXX*,qAX,AMBCWOP-1:@021449z3509.05N/10621.05W_122/001g006t053r000p000P000h35b10208L041AmbientCWOP.com
2026-03-02 15:49:35 CET: OM5KV-15>APNL51,TCPIP*,qAI,OM5KV-15,T2CSNGRAD,T2HUB1,T2HUB3,APRSFI-C1:!4746.48N/01805.55E`Radiosonde Tracker - Based on kxyTrack 1.8-dxl_smod2501
2026-03-02 15:49:35 CET: KN4CI-1>APTW01,WIDE1-1,WIDE2-1,qAR,K4HNH-10:_03060812c140s004g007t063r000p000P000h18b10236tRSW
2026-03-02 15:49:35 CET: SP6CRO-1>TU4WT3,F4KIT-5*,WIDE2-1,qAR,F4GMA:`{-~<0x1c><0x1d>Sb/"8^}Hello world!
2026-03-02 15:49:35 CET: YM3KBU>APBM1S,TCPIP*,qAS,BM2841:@021401z4020.31N/02901.14ErPHG0000AKRAD - Anadolu Kesintisiz Iletisim Radyo Amat<0xc3><0xb6>rleri Dernegi 439.1625/431.5625 CC1
2026-03-02 15:49:35 CET: IK3XJT>APRS,TCPXX*,qAX,CWOP-3:@021448z4533.83N/01213.67E_146/000g000t058r000p000P000h77b10230L114.DsVP
2026-03-02 15:49:35 CET: IR3ZZW>APBM1S,TCPIP*,qAS,BM2222:@021449z4629.85N/01306.05ErPHG0000IR3ZZW Trelli di Paularo (UD) 430.8375/435.8375 CC2
2026-03-02 15:49:35 CET: IR3ZZW>APBM1S,TCPIP*,qAS,BM2222:>https://brandmeister.network/?page=repeater&id=222073
2026-03-02 15:49:35 CET: YF9UAG-16>AESPG4,TCPIP*,qAC,T2TAIPEI::YF9UAG-16:UNIT.Pkt,Pkt,Pkt,dBm,%
2026-03-02 15:49:35 CET: OK2KQM-7>T9TW46,WIDE1-1,WIDE2-1,qAR,OK0DSK-1:`.6Dl^a>/"71}
2026-03-02 15:49:36 CET: YD1ION-13>AESPG4,TCPIP*,qAC,T2OSAKA::YD1ION-13:PARM.RxTraffic,TxTraffic,RxDrop,RSSI,Voltage
2026-03-02 15:49:36 CET: JH4GQC>APU25N,TCPIP*,qAC,T2FUKUOKA:;ONDO SETO*100541z3411.77N\13232.59EdDX SPOT for Matsuyama & KITA-Hiroshima
2026-03-02 15:49:36 CET: HB9ZF-10>APRS,qAR,DJ8KL-2:!4717.27N/00852.88E&G/D -I-R-R H24
2026-03-02 15:49:36 CET: HUMANITY>APRS,TCPIP*,qAC,NINTH:=3130.87N/03401.12EsSTOP TERRORISM, SAVE BABIES AND CATS
2026-03-02 15:49:36 CET: CW1648>APN000,TCPXX*,qAX,CWOP-5:@021449z4008.61N/07522.67W_048/000g000t026r000p000P000b10354h49L227eMB63
2026-03-02 15:49:36 CET: KB8BMY-10>APDR16,TCPIP*,qAC,T2SWEDEN:=4658.94N/12255.55W[209/054/A=000118 Mike - Pacific Northwest
2026-03-02 15:49:36 CET: OE5XIM-10>APMI06,TCPIP*,qAC,OE5XOL::OE5XIM-10:PARM.RX 1h,RX 10m,EF 1h,Total,DC,O1,O2,O3,O4,I1,I2,I3,I4
2026-03-02 15:49:36 CET: OE5XIM-10>APMI06,TCPIP*,qAC,OE5XOL::OE5XIM-10:UNIT.Pkt,Pkt,Pcnt,Pcnt,Volt,On,On,On,On,Hi,Hi,Hi,Hi
2026-03-02 15:49:36 CET: OE5XIM-10>APMI06,TCPIP*,qAC,OE5XOL::OE5XIM-10:EQNS.0,10,0,0,1,0,0,1,0,0,1,0,0,0.075,0
2026-03-02 15:49:36 CET: OE5XIM-10>APMI06,TCPIP*,qAC,OE5XOL::OE5XIM-10:BITS.11111111,Telemetry test
2026-03-02 15:49:36 CET: BG7RAV-5>APN000,TCPIP*,qAC,T2HK:=2128.13N/10907.42E5072/012<0xe5><0x8c><0x97><0xe6><0xb5><0xb7><0xe4><0xb8><0xad><0xe7><0xbb><0xa7>439.350-8-88.5 3.8V
2026-03-02 15:49:36 CET: F4DVI-7>APDR16,TCPIP*,qAC,T2PRT:=4355.45N/00454.72E$162/008/A=000189 https://aprsdroid.org/
2026-03-02 15:49:36 CET: 9A3HVZ>APDR16,TCPIP*,qAC,T2ERFURT:=4602.38N/01603.24EQ259/051/A=000646 MOBILE DMR TG:21966
2026-03-02 15:49:36 CET: KK7YCF-9>TW3YWS,WIDE1-1,WIDE2-1,qAO,NT7U-10:`21?n](>/`"4q}_5
2026-03-02 15:49:36 CET: JIGEN>WIDE1-1,TCPIP*,qAC,T2CSNGRAD:!4056.23N/01451.23EI jCLNT V 5.03.3, PC Only, Descr:"Sysop Voip Italia" - FRN System DE - FRN Server server.voip-italia.net:10024, Net:Nazionale
2026-03-02 15:49:36 CET: YO3IXW-14>APRX29,TCPIP*,qAC,T2CSNGRAD:;YO3RAM *111111z4425.52N/02608.92EyOp. Edi | http://www.yo3ram.ro
2026-03-02 15:49:36 CET: BX1AAK-B>APDG02,TCPIP*,qAC,BX1AAK-BS:!2509.59ND12146.03E&RNG0001/A=000010 70cm Voice (D-Star) 431.64000MHz +0.0000MHz

View File

@@ -1,33 +0,0 @@
package aprs
import (
"fmt"
"time"
)
type TimeFormatError struct {
Time string
}
func (err TimeFormatError) Error() string {
return fmt.Sprintf("aprs: unknown time stamp %q", err.Time)
}
func ParseTime(s string) (time.Time, error) {
if len(s) < 7 {
return time.Time{}, TimeFormatError{s}
}
switch {
case s[6] == 'z': // Day/Hours/Minutes (DHM) format
return time.Parse("021504", s[:6])
case s[6] == '/': // Day/Hours/Minutes (DHM) format
return time.Parse("021504", s[:6])
case s[6] == 'h': // Hours/Minutes/Seconds (HMS) format
return time.Parse("150405", s[:6])
case len(s) >= 8: // Month/Day/Hours/Minutes (MDHM) format
return time.Parse("01021504", s[:8])
default:
return time.Time{}, TimeFormatError{s}
}
}

56
protocol/aprs/util.go Normal file
View File

@@ -0,0 +1,56 @@
package aprs
import (
"strconv"
"strings"
)
func isDigit(b byte) bool {
return b >= '0' && b <= '9'
}
func feetToMeters(v float64) float64 {
return v * 0.3048
}
func knotsToMetersPerSecond(v float64) float64 {
return v * 0.514444444
}
func milesToMeters(v float64) float64 {
return v * 1609.344
}
func fahrenheitToCelcius(v float64) float64 {
return (v - 32) / 1.8
}
func fillZeros(s string) string {
s = strings.Replace(s, " ", "0", -1)
s = strings.Replace(s, ".", "0", -1)
return s
}
func pow(n, exp int) int {
if n <= 0 {
return 1
}
result := 1
for i := 0; i < exp; i++ {
result *= n
}
return result
}
func parseBytesWithSpaces(b []byte) (int, error) {
for i := len(b) - 1; i >= 0; i-- {
if b[i] == ' ' {
b[i] = '0'
} else {
break
}
}
return strconv.Atoi(string(b))
}

12
protocol/aprs/weather.go Normal file
View File

@@ -0,0 +1,12 @@
package aprs
type weatherDecoder struct{}
func (weatherDecoder) CanDecode(frame Frame) bool {
switch frame.Raw.Type() {
case '!', '#', '$', '*':
return true
default:
return false
}
}

View File

@@ -1,16 +1,8 @@
package meshcore
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"math/rand/v2"
"slices"
"strings"
"sync"
"time"
"git.maze.io/go/ham/protocol"
"git.maze.io/go/ham/radio"
@@ -40,6 +32,18 @@ func NewCompanion(conn io.ReadWriteCloser) (*Node, error) {
}, nil
}
func NewRepeater(conn io.ReadWriteCloser) (*Node, error) {
driver := newRepeaterDriver(conn)
if err := driver.Setup(); err != nil {
return nil, err
}
return &Node{
driver: driver,
}, nil
}
func (dev *Node) Close() error {
return dev.driver.Close()
}
@@ -56,6 +60,10 @@ func (dev *Node) Info() *radio.Info {
return dev.driver.Info()
}
func (dev *Node) Stats() <-chan map[string]any {
return dev.driver.Stats()
}
func (dev *Node) Trace(path []byte) (snr []float64, err error) {
if tracer, ok := dev.driver.(nodeTracer); ok {
return tracer.Trace(path)
@@ -70,463 +78,13 @@ type nodeDriver interface {
Setup() error
Packets() <-chan *Packet
Stats() <-chan map[string]any
}
type nodeTracer interface {
Trace(path []byte) (snr []float64, err error)
}
type CompanionError struct {
Code byte
}
func (err CompanionError) Error() string {
switch err.Code {
case companionErrCodeUnsupported:
return "meshcore: companion: unsupported"
case companionErrCodeNotFound:
return "meshcore: companion: not found"
case companionErrCodeTableFull:
return "meshcore: companion: table full"
case companionErrCodeBadState:
return "meshcore: companion: bad state"
case companionErrCodeFileIOError:
return "meshcore: companion: file input/output error"
case companionErrCodeIllegalArgument:
return "meshcore: companion: illegal argument"
default:
return fmt.Sprintf("meshcore: companion: unknown error code %#02x", err.Code)
}
}
type companionDriver struct {
conn io.ReadWriteCloser
mu sync.Mutex
packets chan *Packet
rawPackets chan *protocol.Packet
waiting chan *companionDriverWaiting
info companionInfo
traceTag uint32
traceAuthCode uint32
}
type companionDriverWaiting struct {
expect []byte
response chan []byte
err chan error
}
func newCompanionDriverWaiting(expect []byte) *companionDriverWaiting {
return &companionDriverWaiting{
expect: expect,
response: make(chan []byte),
err: make(chan error),
}
}
func (wait *companionDriverWaiting) Respond(response []byte) {
select {
case wait.response <- response:
default:
Logger.Warnf("meshcore: waiting for %02x discard response: %02x", wait.expect, response)
}
}
func (wait *companionDriverWaiting) Error(err error) {
select {
case wait.err <- err:
default:
Logger.Warnf("meshcore: waiting for %02x discard error: %v", wait.expect, err)
}
}
func (wait *companionDriverWaiting) Close() {
Logger.Tracef("meshcore: waiting for %02x closing", wait.expect)
close(wait.response)
close(wait.err)
}
func (wait *companionDriverWaiting) Wait() ([]byte, error) {
Logger.Tracef("meshcore: waiting for %02x", wait.expect)
select {
case err := <-wait.err:
Logger.Tracef("meshcore: waiting for %02x received error: %v", wait.expect, err)
return nil, err
case response := <-wait.response:
Logger.Tracef("meshcore: waiting for %02x received response: %d", wait.expect, len(response))
return response, nil
}
}
type companionInfo struct {
// Fields returns by CMD_APP_START.
Type NodeType
Power byte // in dBm
MaxPower byte // in dBm
PublicKey [32]byte
Latitude float64
Longitude float64
HasMultiACKs bool
AdvertLocationPolicy byte
TelemetryFlags byte
ManualAddContacts byte
Frequency float64 // in MHz
Bandwidth float64 // in kHz
SpreadingFactor byte
CodingRate byte
Name string
// Fields returns by CMD_DEVICE_QUERY.
FirmwareVersion string
FirmwareVersionCode byte
FirmwareBuildDate string
Manufacturer string
MaxContacts int
MaxGroupChannels int
BLEPIN [4]byte
}
func newCompanionDriver(conn io.ReadWriteCloser) *companionDriver {
return &companionDriver{
conn: conn,
waiting: make(chan *companionDriverWaiting, 16),
traceTag: rand.Uint32(),
//traceAuthCode: rand.Uint32(),
}
}
func (drv *companionDriver) Close() error {
return drv.conn.Close()
}
func (drv *companionDriver) Setup() (err error) {
go drv.poll()
if err = drv.sendAppStart(); err != nil {
return
}
if err = drv.sendDeviceInfo(); err != nil {
return
}
return
}
func (drv *companionDriver) Packets() <-chan *Packet {
if drv.packets == nil {
drv.packets = make(chan *Packet, 16)
}
return drv.packets
}
func (drv *companionDriver) RawPackets() <-chan *protocol.Packet {
if drv.rawPackets == nil {
drv.rawPackets = make(chan *protocol.Packet, 16)
}
return drv.rawPackets
}
func (drv *companionDriver) Info() *radio.Info {
var pos *radio.Position
if drv.info.Latitude != 0 && drv.info.Longitude != 0 {
pos = &radio.Position{
Latitude: drv.info.Latitude,
Longitude: drv.info.Longitude,
}
}
var firmwareDate time.Time
for _, layout := range []string{
"02 Jan 2006",
"02-01-2006",
} {
var terr error
if firmwareDate, terr = time.Parse(layout, drv.info.FirmwareBuildDate); terr == nil {
break
}
}
var (
manufacturerPart = strings.SplitN(drv.info.Manufacturer, " ", 2)
manufacturer = manufacturerPart[0]
device string
)
if len(manufacturerPart) > 1 {
device = manufacturerPart[1]
}
return &radio.Info{
Name: drv.info.Name,
Manufacturer: manufacturer,
Device: device,
FirmwareDate: firmwareDate,
FirmwareVersion: drv.info.FirmwareVersion,
Modulation: protocol.LoRa,
Position: pos,
Frequency: drv.info.Frequency,
Bandwidth: drv.info.Bandwidth,
Power: float64(drv.info.Power),
LoRaSF: drv.info.SpreadingFactor,
LoRaCR: drv.info.CodingRate,
}
}
func (drv *companionDriver) Trace(path []byte) (snr []float64, err error) {
var (
args = make([]byte, 4+4+1+len(path))
data []byte
)
binary.LittleEndian.PutUint32(args[0:], drv.traceTag) // tag
binary.LittleEndian.PutUint32(args[4:], drv.traceAuthCode) // authcode
args[8] = 0 // flags
copy(args[9:], path) // path
Logger.Debugf("meshcore: trace %02x tag %08x authcode %08x", path, drv.traceTag, drv.traceAuthCode)
if data, err = drv.writeCommand(companionSendTracePath, args, companionResponseSent); err != nil {
return
}
Logger.Debugf("trace response:\n%s", hex.Dump(data))
return
}
func (drv *companionDriver) readFrame() ([]byte, error) {
var frame [3 + maxCompanionFrameSize]byte
for {
n, err := drv.conn.Read(frame[:])
if err != nil {
return nil, err
} else if n < 3 {
continue
}
if frame[0] != '>' {
// not a companion frame
continue
}
size := int(binary.LittleEndian.Uint16(frame[1:]))
if size > maxCompanionFrameSize {
return nil, fmt.Errorf("meshcore: companion sent frame size of %d, which exceeds maximum of %d", size, maxCompanionFrameSize)
}
// Make sure we have read all bytes
o := n
for (o - 3) < size {
if n, err = drv.conn.Read(frame[o:]); err != nil {
return nil, err
}
o += n
}
Logger.Tracef("read %d:\n%s", size, hex.Dump(frame[:3+size]))
return frame[3 : 3+size], nil
}
}
func (drv *companionDriver) writeFrame(b []byte) (err error) {
if len(b) > maxCompanionFrameSize {
return fmt.Errorf("meshcore: companion: frame size %d exceed maximum of %d", len(b), maxCompanionFrameSize)
}
var frame [3 + maxCompanionFrameSize]byte
frame[0] = '<'
binary.LittleEndian.PutUint16(frame[1:], uint16(len(b)))
n := copy(frame[3:], b)
//Logger.Tracef("send %d:\n%s", n, hex.Dump(frame[:3+n]))
_, err = drv.conn.Write(frame[:3+n])
return
}
func (drv *companionDriver) writeCommand(cmd byte, args []byte, wait ...byte) ([]byte, error) {
drv.mu.Lock()
defer drv.mu.Unlock()
if err := drv.writeFrame(append([]byte{cmd}, args...)); err != nil {
return nil, err
}
return drv.wait(wait...)
}
func (drv *companionDriver) wait(expect ...byte) ([]byte, error) {
wait := newCompanionDriverWaiting(expect)
defer wait.Close()
drv.waiting <- wait
return wait.Wait()
}
func (drv *companionDriver) handlePushFrame(b []byte) {
if len(b) < 1 {
return // illegal
}
switch b[0] {
case companionPushAdvert:
case companionPushMessageWaiting:
case companionPushLogRXData:
drv.handleRXData(b[1:])
case companionPushcompanionPushNewAdvert:
default:
Logger.Warnf("meshcore: unhandled push %02x:\n%s", b[0], hex.Dump(b[1:]))
}
}
func (drv *companionDriver) handleRXData(b []byte) {
if drv.packets == nil && drv.rawPackets == nil {
return // not listening for packets, discard
}
if len(b) < 2+minPacketSize {
return // too short
}
var (
now = time.Now().UTC()
snr = float64(b[0]) / 4
rssi = int8(b[1])
)
// Decode raw
if drv.rawPackets != nil {
select {
case drv.rawPackets <- &protocol.Packet{
Time: now,
Protocol: protocol.MeshCore,
SNR: snr,
RSSI: rssi,
Raw: b[2:],
}:
default:
Logger.Warn("meshcore: raw packet channel full, dropping packet")
}
}
// Decode payload
if drv.packets != nil {
packet := new(Packet)
if err := packet.UnmarshalBytes(b[2:]); err == nil {
packet.SNR = snr
packet.RSSI = rssi
select {
case drv.packets <- packet:
default:
Logger.Warn("meshcore: packet channel full, dropping packet")
}
}
}
}
func (drv *companionDriver) sendAppStart() (err error) {
var (
b []byte
args = append(make([]byte, 8), []byte("git.maze.io/go/ham")...)
)
if b, err = drv.writeCommand(companionAppStart, args, companionResponseSelfInfo); err != nil {
return fmt.Errorf("meshcore: can't send application start: %v", err)
}
//log.Printf("companion app start response:\n%s", hex.Dump(b))
const expect = 1 + 1 + 1 + 1 + 32 + 4 + 4 + 1 + 1 + 1 + 1 + 4 + 4 + 1 + 1
if len(b) < expect {
return fmt.Errorf("companion: expected %d bytes of self info, got %d", expect, len(b))
}
if b[0] != companionResponseSelfInfo {
return fmt.Errorf("companion: expected self info response, got %#02x", b[0])
}
b = b[1:]
drv.info.Type = NodeType(b[0])
drv.info.Power = b[1]
drv.info.MaxPower = b[2]
copy(drv.info.PublicKey[:], b[3:])
drv.info.Latitude, drv.info.Longitude = decodeLatLon(b[35:])
drv.info.HasMultiACKs = b[43] != 0
drv.info.AdvertLocationPolicy = b[44]
drv.info.TelemetryFlags = b[45]
drv.info.ManualAddContacts = b[46]
drv.info.Frequency = decodeFrequency(b[47:])
drv.info.Bandwidth = decodeFrequency(b[51:])
drv.info.SpreadingFactor = b[55]
drv.info.CodingRate = b[56]
drv.info.Name = strings.TrimRight(string(b[57:]), "\x00")
return
}
func (drv *companionDriver) sendDeviceInfo() (err error) {
var (
args = []byte{0x03}
data []byte
)
if data, err = drv.writeCommand(companionDeviceQuery, args, companionResponseDeviceInfo); err != nil {
return
}
const expect = 4 + 4 + 12 + 40 + 20
if len(data) < expect {
return fmt.Errorf("companion: expected %d bytes of self info, got %d", expect, len(data))
}
if data[0] != companionResponseDeviceInfo {
return fmt.Errorf("companion: expected device info response, got %#02x", data[0])
}
data = data[1:]
drv.info.FirmwareVersionCode = data[0]
drv.info.MaxContacts = int(data[1]) * 2
drv.info.MaxGroupChannels = int(data[2])
drv.info.FirmwareBuildDate = decodeCString(data[7:19])
drv.info.Manufacturer = decodeCString(data[19:59])
drv.info.FirmwareVersion = decodeCString(data[59:79])
return
}
func (drv *companionDriver) poll() {
for {
frame, err := drv.readFrame()
if err != nil {
Logger.Errorf("meshcore: unrecoverable error: %v", err)
return
} else if len(frame) < 1 {
continue
}
response := frame[0]
Logger.Tracef("meshcore: handle %s (%02x, %d bytes)", companionResponseName(response), response, len(frame[1:]))
switch {
case response == companionResponseError:
err := CompanionError{Code: frame[1]}
select {
case waiting := <-drv.waiting:
Logger.Debugf("meshcore: sending error to waiting: %v", err)
waiting.Error(err)
default:
Logger.Debugf("meshcore: unexpected error: %v", err)
}
case response >= 0x80:
drv.handlePushFrame(frame)
default:
select {
case waiting := <-drv.waiting:
if len(waiting.expect) == 0 {
//Logger.Debugf("meshcore: respond %02x verbatim", response)
waiting.Respond(frame)
} else if slices.Contains(waiting.expect, response) {
//Logger.Debugf("meshcore: respond %02x to expected %02x", response, waiting.expect)
waiting.Respond(frame)
} else {
//Logger.Debugf("meshcore: unexpected %02x response (want %02x)", response, waiting.expect)
waiting.Error(fmt.Errorf("unexpected %02x response", response))
}
default:
Logger.Warnf("meshcore: unhandled %02x response", response)
}
}
}
}
var (
_ protocol.PacketReceiver = (*Node)(nil)
_ nodeDriver = (*companionDriver)(nil)

View File

@@ -0,0 +1,478 @@
package meshcore
import (
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"math/rand/v2"
"slices"
"strings"
"sync"
"time"
"git.maze.io/go/ham/protocol"
"git.maze.io/go/ham/radio"
)
type CompanionError struct {
Code byte
}
func (err CompanionError) Error() string {
switch err.Code {
case companionErrCodeUnsupported:
return "meshcore: companion: unsupported"
case companionErrCodeNotFound:
return "meshcore: companion: not found"
case companionErrCodeTableFull:
return "meshcore: companion: table full"
case companionErrCodeBadState:
return "meshcore: companion: bad state"
case companionErrCodeFileIOError:
return "meshcore: companion: file input/output error"
case companionErrCodeIllegalArgument:
return "meshcore: companion: illegal argument"
default:
return fmt.Sprintf("meshcore: companion: unknown error code %#02x", err.Code)
}
}
type companionDriver struct {
conn io.ReadWriteCloser
mu sync.Mutex
packets chan *Packet
rawPackets chan *protocol.Packet
waiting chan *companionDriverWaiting
info companionInfo
traceTag uint32
traceAuthCode uint32
stats chan map[string]any
}
type companionDriverWaiting struct {
expect []byte
response chan []byte
err chan error
}
func newCompanionDriverWaiting(expect []byte) *companionDriverWaiting {
return &companionDriverWaiting{
expect: expect,
response: make(chan []byte),
err: make(chan error),
}
}
func (wait *companionDriverWaiting) Respond(response []byte) {
select {
case wait.response <- response:
default:
Logger.Warnf("meshcore: waiting for %02x discard response: %02x", wait.expect, response)
}
}
func (wait *companionDriverWaiting) Error(err error) {
select {
case wait.err <- err:
default:
Logger.Warnf("meshcore: waiting for %02x discard error: %v", wait.expect, err)
}
}
func (wait *companionDriverWaiting) Close() {
Logger.Tracef("meshcore: waiting for %02x closing", wait.expect)
close(wait.response)
close(wait.err)
}
func (wait *companionDriverWaiting) Wait() ([]byte, error) {
Logger.Tracef("meshcore: waiting for %02x", wait.expect)
select {
case err := <-wait.err:
Logger.Tracef("meshcore: waiting for %02x received error: %v", wait.expect, err)
return nil, err
case response := <-wait.response:
Logger.Tracef("meshcore: waiting for %02x received response: %d", wait.expect, len(response))
return response, nil
}
}
type companionInfo struct {
// Fields returns by CMD_APP_START.
Type NodeType
Power byte // in dBm
MaxPower byte // in dBm
PublicKey [32]byte
Latitude float64
Longitude float64
HasMultiACKs bool
AdvertLocationPolicy byte
TelemetryFlags byte
ManualAddContacts byte
Frequency float64 // in MHz
Bandwidth float64 // in kHz
SpreadingFactor byte
CodingRate byte
Name string
// Fields returns by CMD_DEVICE_QUERY.
FirmwareVersion string
FirmwareVersionCode byte
FirmwareBuildDate string
Manufacturer string
MaxContacts int
MaxGroupChannels int
BLEPIN [4]byte
}
func newCompanionDriver(conn io.ReadWriteCloser) *companionDriver {
return &companionDriver{
conn: conn,
waiting: make(chan *companionDriverWaiting, 16),
traceTag: rand.Uint32(),
stats: make(chan map[string]any, 2),
//traceAuthCode: rand.Uint32(),
}
}
func (drv *companionDriver) Close() error {
return drv.conn.Close()
}
func (drv *companionDriver) Setup() (err error) {
go drv.poll()
if err = drv.sendAppStart(); err != nil {
return
}
if err = drv.getDeviceInfo(); err != nil {
return
}
return
}
func (drv *companionDriver) Packets() <-chan *Packet {
if drv.packets == nil {
drv.packets = make(chan *Packet, 16)
}
return drv.packets
}
func (drv *companionDriver) RawPackets() <-chan *protocol.Packet {
if drv.rawPackets == nil {
drv.rawPackets = make(chan *protocol.Packet, 16)
}
return drv.rawPackets
}
func (drv *companionDriver) Info() *radio.Info {
var pos *radio.Position
if drv.info.Latitude != 0 && drv.info.Longitude != 0 {
pos = &radio.Position{
Latitude: drv.info.Latitude,
Longitude: drv.info.Longitude,
}
}
var firmwareDate time.Time
for _, layout := range []string{
"02 Jan 2006",
"02-01-2006",
} {
var terr error
if firmwareDate, terr = time.Parse(layout, drv.info.FirmwareBuildDate); terr == nil {
break
}
}
var (
manufacturerPart = strings.SplitN(drv.info.Manufacturer, " ", 2)
manufacturer = manufacturerPart[0]
device string
)
if len(manufacturerPart) > 1 {
device = manufacturerPart[1]
}
return &radio.Info{
Name: drv.info.Name,
Manufacturer: manufacturer,
Device: device,
FirmwareDate: firmwareDate,
FirmwareVersion: drv.info.FirmwareVersion,
Modulation: protocol.LoRa,
Position: pos,
Frequency: drv.info.Frequency,
Bandwidth: drv.info.Bandwidth,
Power: float64(drv.info.Power),
LoRaSF: drv.info.SpreadingFactor,
LoRaCR: drv.info.CodingRate,
}
}
func (drv *companionDriver) Stats() <-chan map[string]any {
return drv.stats
}
func (drv *companionDriver) Trace(path []byte) (snr []float64, err error) {
var (
args = make([]byte, 4+4+1+len(path))
data []byte
)
binary.LittleEndian.PutUint32(args[0:], drv.traceTag) // tag
binary.LittleEndian.PutUint32(args[4:], drv.traceAuthCode) // authcode
args[8] = 0 // flags
copy(args[9:], path) // path
Logger.Debugf("meshcore: trace %02x tag %08x authcode %08x", path, drv.traceTag, drv.traceAuthCode)
if data, err = drv.writeCommand(companionSendTracePath, args, companionResponseSent); err != nil {
return
}
Logger.Debugf("trace response:\n%s", hex.Dump(data))
return
}
func (drv *companionDriver) readFrame() ([]byte, error) {
var frame [3 + maxCompanionFrameSize]byte
for {
n, err := drv.conn.Read(frame[:])
if err != nil {
return nil, err
} else if n < 3 {
continue
}
if frame[0] != '>' {
// not a companion frame
continue
}
size := int(binary.LittleEndian.Uint16(frame[1:]))
if size > maxCompanionFrameSize {
return nil, fmt.Errorf("meshcore: companion sent frame size of %d, which exceeds maximum of %d", size, maxCompanionFrameSize)
}
// Make sure we have read all bytes
o := n
for (o - 3) < size {
if n, err = drv.conn.Read(frame[o:]); err != nil {
return nil, err
}
o += n
}
Logger.Tracef("read %d:\n%s", size, hex.Dump(frame[:3+size]))
return frame[3 : 3+size], nil
}
}
func (drv *companionDriver) writeFrame(b []byte) (err error) {
if len(b) > maxCompanionFrameSize {
return fmt.Errorf("meshcore: companion: frame size %d exceed maximum of %d", len(b), maxCompanionFrameSize)
}
var frame [3 + maxCompanionFrameSize]byte
frame[0] = '<'
binary.LittleEndian.PutUint16(frame[1:], uint16(len(b)))
n := copy(frame[3:], b)
//Logger.Tracef("send %d:\n%s", n, hex.Dump(frame[:3+n]))
_, err = drv.conn.Write(frame[:3+n])
return
}
func (drv *companionDriver) writeCommand(cmd byte, args []byte, wait ...byte) ([]byte, error) {
drv.mu.Lock()
defer drv.mu.Unlock()
if err := drv.writeFrame(append([]byte{cmd}, args...)); err != nil {
return nil, err
}
return drv.wait(wait...)
}
func (drv *companionDriver) wait(expect ...byte) ([]byte, error) {
wait := newCompanionDriverWaiting(expect)
defer wait.Close()
drv.waiting <- wait
return wait.Wait()
}
func (drv *companionDriver) handlePushFrame(b []byte) {
if len(b) < 1 {
return // illegal
}
switch b[0] {
case companionPushAdvert:
case companionPushMessageWaiting:
case companionPushLogRXData:
drv.handleRXData(b[1:])
case companionPushNewAdvert:
default:
Logger.Warnf("meshcore: unhandled push %02x:\n%s", b[0], hex.Dump(b[1:]))
}
}
func (drv *companionDriver) handleRXData(b []byte) {
if drv.packets == nil && drv.rawPackets == nil {
return // not listening for packets, discard
}
if len(b) < 2+minPacketSize {
return // too short
}
var (
now = time.Now().UTC()
snr = float64(b[0]) / 4
rssi = int(b[1])
)
// Decode raw
if drv.rawPackets != nil {
select {
case drv.rawPackets <- &protocol.Packet{
Time: now,
Protocol: protocol.MeshCore,
SNR: snr,
RSSI: rssi,
Raw: b[2:],
}:
default:
Logger.Warn("meshcore: raw packet channel full, dropping packet")
}
}
// Decode payload
if drv.packets != nil {
packet := new(Packet)
if err := packet.UnmarshalBytes(b[2:]); err == nil {
packet.SNR = snr
packet.RSSI = rssi
select {
case drv.packets <- packet:
default:
Logger.Warn("meshcore: packet channel full, dropping packet")
}
}
}
}
func (drv *companionDriver) sendAppStart() (err error) {
var (
b []byte
args = append(make([]byte, 8), []byte("git.maze.io/go/ham")...)
)
if b, err = drv.writeCommand(companionAppStart, args, companionResponseSelfInfo); err != nil {
return fmt.Errorf("meshcore: can't send application start: %v", err)
}
//log.Printf("companion app start response:\n%s", hex.Dump(b))
const expect = 1 + 1 + 1 + 1 + 32 + 4 + 4 + 1 + 1 + 1 + 1 + 4 + 4 + 1 + 1
if len(b) < expect {
return fmt.Errorf("companion: expected %d bytes of self info, got %d", expect, len(b))
}
if b[0] != companionResponseSelfInfo {
return fmt.Errorf("companion: expected self info response, got %#02x", b[0])
}
b = b[1:]
drv.info.Type = NodeType(b[0])
drv.info.Power = b[1]
drv.info.MaxPower = b[2]
copy(drv.info.PublicKey[:], b[3:])
drv.info.Latitude, drv.info.Longitude = decodeLatLon(b[35:])
drv.info.HasMultiACKs = b[43] != 0
drv.info.AdvertLocationPolicy = b[44]
drv.info.TelemetryFlags = b[45]
drv.info.ManualAddContacts = b[46]
drv.info.Frequency = decodeFrequency(b[47:])
drv.info.Bandwidth = decodeFrequency(b[51:])
drv.info.SpreadingFactor = b[55]
drv.info.CodingRate = b[56]
drv.info.Name = strings.TrimRight(string(b[57:]), "\x00")
return
}
func (drv *companionDriver) getDeviceInfo() (err error) {
var (
args = []byte{0x03}
data []byte
)
if data, err = drv.writeCommand(companionDeviceQuery, args, companionResponseDeviceInfo); err != nil {
return
}
const expect = 4 + 4 + 12 + 40 + 20
if len(data) < expect {
return fmt.Errorf("companion: expected %d bytes of self info, got %d", expect, len(data))
}
if data[0] != companionResponseDeviceInfo {
return fmt.Errorf("companion: expected device info response, got %#02x", data[0])
}
data = data[1:]
drv.info.FirmwareVersionCode = data[0]
drv.info.MaxContacts = int(data[1]) * 2
drv.info.MaxGroupChannels = int(data[2])
drv.info.FirmwareBuildDate = decodeCString(data[7:19])
drv.info.Manufacturer = decodeCString(data[19:59])
drv.info.FirmwareVersion = decodeCString(data[59:79])
return
}
func (drv *companionDriver) getPublicKey() (err error) {
// TODO
return err
}
func (drv *companionDriver) poll() {
for {
frame, err := drv.readFrame()
if err != nil {
Logger.Errorf("meshcore: unrecoverable error: %v", err)
return
} else if len(frame) < 1 {
continue
}
response := frame[0]
Logger.Tracef("meshcore: handle %s (%02x, %d bytes)", companionResponseName(response), response, len(frame[1:]))
switch {
case response == companionResponseError:
err := CompanionError{Code: frame[1]}
select {
case waiting := <-drv.waiting:
Logger.Debugf("meshcore: sending error to waiting: %v", err)
waiting.Error(err)
default:
Logger.Debugf("meshcore: unexpected error: %v", err)
}
case response >= 0x80:
drv.handlePushFrame(frame)
default:
select {
case waiting := <-drv.waiting:
if len(waiting.expect) == 0 {
//Logger.Debugf("meshcore: respond %02x verbatim", response)
waiting.Respond(frame)
} else if slices.Contains(waiting.expect, response) {
//Logger.Debugf("meshcore: respond %02x to expected %02x", response, waiting.expect)
waiting.Respond(frame)
} else {
//Logger.Debugf("meshcore: unexpected %02x response (want %02x)", response, waiting.expect)
waiting.Error(fmt.Errorf("unexpected %02x response", response))
}
default:
Logger.Warnf("meshcore: unhandled %02x response", response)
}
}
}
}

View File

@@ -0,0 +1,549 @@
package meshcore
import (
"bufio"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
"strconv"
"strings"
"time"
"git.maze.io/go/ham/protocol"
"git.maze.io/go/ham/radio"
)
type repeaterDriver struct {
conn io.ReadWriteCloser
packets chan *Packet
rawPackets chan *protocol.Packet
waiting chan *repeaterDriverWaiting
hasSNR bool
lastFrame []byte
lastFrameAt time.Time
info repeaterInfo
stats chan map[string]any
err error
}
type repeaterDriverWaiting struct {
response chan string
err chan error
}
func newRepeaterDriverWaiting() *repeaterDriverWaiting {
return &repeaterDriverWaiting{
response: make(chan string),
err: make(chan error),
}
}
func (wait *repeaterDriverWaiting) Respond(response string) {
select {
case wait.response <- response:
default:
Logger.Warnf("meshcore: waiting discard response: %q", response)
}
}
func (wait *repeaterDriverWaiting) Error(err error) {
select {
case wait.err <- err:
default:
Logger.Warnf("meshcore: waiting discard error: %q", err)
}
}
func (wait *repeaterDriverWaiting) Close() {
Logger.Trace("meshcore: waiting closing")
close(wait.response)
close(wait.err)
}
func (wait *repeaterDriverWaiting) Wait() (string, error) {
Logger.Trace("meshcore: waiting for response")
select {
case err := <-wait.err:
Logger.Tracef("meshcore: waiting received error: %v", err)
return "", err
case response := <-wait.response:
Logger.Tracef("meshcore: waiting received response: %d", len(response))
return response, nil
}
}
type repeaterInfo struct {
// Fields returns by CMD_APP_START.
Type NodeType
Power byte // in dBm
MaxPower byte // in dBm
PublicKey string
Latitude float64
Longitude float64
HasMultiACKs bool
AdvertLocationPolicy byte
TelemetryFlags byte
ManualAddContacts byte
Frequency float64 // in MHz
Bandwidth float64 // in kHz
SpreadingFactor byte
CodingRate byte
Name string
// Fields returns by CMD_DEVICE_QUERY.
FirmwareVersion string
FirmwareVersionCode byte
FirmwareBuildDate string
Manufacturer string
}
func newRepeaterDriver(conn io.ReadWriteCloser) *repeaterDriver {
return &repeaterDriver{
conn: conn,
waiting: make(chan *repeaterDriverWaiting, 16),
stats: make(chan map[string]any, 2),
}
}
func newRepeaterDriverWithSNRPatch(conn io.ReadWriteCloser) *repeaterDriver {
driver := newRepeaterDriver(conn)
driver.hasSNR = true
return driver
}
func (drv *repeaterDriver) Close() error {
return drv.conn.Close()
}
func (drv *repeaterDriver) Setup() (err error) {
go drv.poll()
if err = drv.queryDeviceInfo(); err != nil {
return err
}
go drv.pollStats()
return drv.err
}
func (drv *repeaterDriver) Info() *radio.Info {
var (
firmwareVersion = drv.info.FirmwareVersion
firmwareDate time.Time
)
if strings.Contains(firmwareVersion, "(Build: ") {
// "v1.13.0 (Build: 15 Feb 2026)"
part := strings.SplitN(firmwareVersion, "(Build: ", 2)
firmwareVersion = strings.TrimSpace(part[0])
firmwareDate, _ = time.Parse("2 Jan 2006", strings.TrimRight(part[1], ")"))
}
var (
manufacturerPart = strings.SplitN(drv.info.Manufacturer, " ", 2)
manufacturer = manufacturerPart[0]
device string
)
if len(manufacturerPart) > 1 {
device = manufacturerPart[1]
}
var pos *radio.Position
if drv.info.Latitude != 0 && drv.info.Longitude != 0 {
pos = &radio.Position{
Latitude: drv.info.Latitude,
Longitude: drv.info.Longitude,
}
}
return &radio.Info{
ID: drv.info.PublicKey,
Name: drv.info.Name,
Manufacturer: manufacturer,
Device: device,
FirmwareVersion: firmwareVersion,
FirmwareDate: firmwareDate,
Modulation: protocol.LoRa,
Position: pos,
Frequency: math.Ceil(drv.info.Frequency*1000) / 1000,
Bandwidth: drv.info.Bandwidth,
Power: float64(drv.info.Power),
LoRaSF: drv.info.SpreadingFactor,
LoRaCR: drv.info.CodingRate,
}
}
func (drv *repeaterDriver) Packets() <-chan *Packet {
if drv.packets == nil {
drv.packets = make(chan *Packet, 16)
}
return drv.packets
}
func (drv *repeaterDriver) RawPackets() <-chan *protocol.Packet {
if drv.rawPackets == nil {
drv.rawPackets = make(chan *protocol.Packet, 16)
}
return drv.rawPackets
}
func (drv *repeaterDriver) Stats() <-chan map[string]any {
return drv.stats
}
func (drv *repeaterDriver) queryDeviceInfo() (err error) {
if drv.info.FirmwareVersion, err = drv.writeCommand("ver"); err != nil {
return
}
// Fetch name
if drv.info.Name, err = drv.writeCommand("get", "name"); err != nil {
return
}
// Fetch public key
if drv.info.PublicKey, err = drv.writeCommand("get", "public.key"); err != nil {
return
}
var line string
// Fetch frequency, bandwidth and LoRa settings
if line, err = drv.writeCommand("get", "radio"); err != nil {
return
}
if _, err = fmt.Sscanf(line, "%f,%f,%d,%d", &drv.info.Frequency, &drv.info.Bandwidth, &drv.info.SpreadingFactor, &drv.info.CodingRate); err != nil {
return err
}
// Fetch tx power
if line, err = drv.writeCommand("get", "tx"); err != nil {
return
}
if _, err = fmt.Sscanf(line, "%d", &drv.info.Power); err != nil {
return
}
// Fetch location
if line, err = drv.writeCommand("get", "lat"); err != nil {
return
}
if line != "0.0" {
drv.info.Latitude, _ = strconv.ParseFloat(line, 64)
if line, err = drv.writeCommand("get", "lon"); err != nil {
return
}
drv.info.Longitude, _ = strconv.ParseFloat(line, 64)
}
// Fetch node type
if line, err = drv.writeCommand("get", "role"); err != nil {
return err
}
switch line {
case "repeater":
drv.info.Type = Repeater
case "room_server":
drv.info.Type = Room
case "sensor":
drv.info.Type = Sensor
}
// Fetch board type
if drv.info.Manufacturer, err = drv.writeCommand("board"); err != nil {
return
}
return err
}
func (drv *repeaterDriver) writeCommand(command string, args ...string) (response string, err error) {
full := append([]string{command}, args...)
if _, err = fmt.Fprintf(drv.conn, "%s\r\n", strings.Join(full, " ")); err != nil {
return
}
return drv.wait()
}
func (drv *repeaterDriver) wait() (string, error) {
wait := newRepeaterDriverWaiting()
defer wait.Close()
drv.waiting <- wait
return wait.Wait()
}
func (drv *repeaterDriver) handleRXData(line string) {
part := strings.SplitN(line, " RAW: ", 2)
if len(part) == 1 || len(part[0]) < 21 {
return
}
b, err := hex.DecodeString(part[1])
if err != nil {
Logger.Warnf("meshcore: corrupt raw packet: %v", err)
return
} else if len(b) < 2 {
return // nothing to do!
}
/* companion/repeater time isn't reliable, as they often don't have an RTC:
when, err := time.Parse("15:04:05 - 02/3/2006", strings.TrimSpace(line[:21]))
if err != nil {
Logger.Warnf("meshcore: corrupt raw packet: %v", err)
}
*/
var (
when = time.Now()
snr float64
rssi int
data []byte
)
if drv.hasSNR {
snr = float64(b[0]) / 4
rssi = int(b[1])
data = b[2:]
if drv.rawPackets != nil {
select {
case drv.rawPackets <- &protocol.Packet{
Time: when,
Protocol: protocol.MeshCore,
SNR: snr,
RSSI: rssi,
Raw: data,
}:
default:
Logger.Warn("meshcore: raw packet channel full, dropping packet")
}
}
// Decode payload
if drv.packets != nil {
packet := new(Packet)
if err := packet.UnmarshalBytes(data); err == nil {
packet.SNR = snr
packet.RSSI = rssi
select {
case drv.packets <- packet:
default:
Logger.Warn("meshcore: packet channel full, dropping packet")
}
}
}
} else {
// Record last frame
drv.lastFrame = b
drv.lastFrameAt = when
}
}
func (drv *repeaterDriver) handleRX(line string) {
if drv.hasSNR || len(drv.lastFrame) == 0 {
return // nothing to do here
}
var snr, rssi int
// "11:08:55 - 15/5/2024 U: RX, len=74 (type=5, route=F, payload_len=67) SNR=12 RSSI=-94 score=1000 time=738 hash=C46CD3BB7D0D99E1"
for _, part := range strings.Split(line, " ") {
switch {
case strings.HasPrefix(part, "SNR="):
snr, _ = strconv.Atoi(part[4:])
case strings.HasPrefix(part, "RSSI="):
rssi, _ = strconv.Atoi(part[5:])
}
}
if drv.rawPackets != nil {
select {
case drv.rawPackets <- &protocol.Packet{
Time: drv.lastFrameAt,
Protocol: protocol.MeshCore,
SNR: float64(snr),
RSSI: rssi,
Raw: drv.lastFrame,
}:
default:
Logger.Warn("meshcore: raw packet channel full, dropping packet")
}
}
// Decode payload
if drv.packets != nil {
packet := new(Packet)
if err := packet.UnmarshalBytes(drv.lastFrame); err == nil {
packet.SNR = float64(snr)
packet.RSSI = rssi
select {
case drv.packets <- packet:
default:
Logger.Warn("meshcore: packet channel full, dropping packet")
}
}
}
// Clear state
drv.lastFrame = nil
}
func (drv *repeaterDriver) poll() {
r := bufio.NewReader(drv.conn)
for {
line, err := r.ReadString('\n')
if err != nil {
Logger.Errorf("meshcore: unrecoverable error: %v", err)
drv.err = err
return
}
line = strings.TrimSpace(line)
Logger.Tracef("meshcore: handle %q", line)
switch {
case strings.HasPrefix(line, "-> "):
select {
case waiting := <-drv.waiting:
waiting.Respond(strings.TrimPrefix(line[3:], "> "))
default:
Logger.Warnf("meshcore: unhandled response %q", line[3:])
}
case strings.Contains(line, " RAW: "):
// "10:53:04 - 15/5/2024 U RAW: 0917081DE1B03B130A270E01233EA0FB261218CCABBAA02278F51E97585B9B3285B95EFEEC83BE91E3D1E4F79D88B2C9484AA6882EB217C992B5C3C99C"
drv.handleRXData(line)
case strings.Contains(line, ": RX,"):
drv.handleRX(line)
case strings.Contains(line, ": TX,"):
// ignore (for now)
default:
Logger.Tracef("meshcore: repeater sent gibberish %q", line)
}
}
}
func (drv *repeaterDriver) pollStats() {
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
stats := make(map[string]any)
neighbors, err := drv.getNeighbors()
if err != nil {
Logger.Warnf("meshcore: failed to get neighbors: %v", err)
} else {
stats["neighbors"] = neighbors
}
response, err := drv.writeCommand("stats-core")
if err != nil {
Logger.Warnf("meshcore: failed to get stats: %v", err)
return
}
neighborStats := make(map[string]any)
for _, line := range strings.Split(response, "\n") {
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := parts[0]
value := parts[1]
if i, err := strconv.Atoi(value); err == nil {
neighborStats[key] = i
} else if f, err := strconv.ParseFloat(value, 64); err == nil {
neighborStats[key] = f
} else {
neighborStats[key] = value
}
}
var (
coreStats = make(map[string]any)
radioStats = make(map[string]any)
packetStats = make(map[string]any)
)
if response, err := drv.writeCommand("stats-core"); err == nil {
if err = json.Unmarshal([]byte(response), &coreStats); err != nil {
Logger.Warnf("meshcore: failed to decode core stats: %v", err)
}
} else {
Logger.Warnf("meshcore: failed to get core stats: %v", err)
}
if response, err := drv.writeCommand("stats-radio"); err == nil {
if err = json.Unmarshal([]byte(response), &radioStats); err != nil {
Logger.Warnf("meshcore: failed to decode radio stats: %v", err)
}
} else {
Logger.Warnf("meshcore: failed to get radio stats: %v", err)
}
if response, err := drv.writeCommand("stats-packets"); err == nil {
if err = json.Unmarshal([]byte(response), &packetStats); err != nil {
Logger.Warnf("meshcore: failed to decode packet stats: %v", err)
}
} else {
Logger.Warnf("meshcore: failed to get packet stats: %v", err)
}
stats["neighbors"] = neighborStats
stats["core"] = coreStats
stats["radio"] = radioStats
stats["packets"] = packetStats
select {
case drv.stats <- stats:
default:
Logger.Warn("meshcore: stats channel full, dropping stats")
}
<-ticker.C
}
}
func (drv *repeaterDriver) getNeighbors() (neighbors map[string]float64, err error) {
// "neighbors" command returns a list of neighbors, one per line, in the format: "78CCC5A3:470:47\n"
var response string
if response, err = drv.writeCommand("neighbors"); err != nil {
return
}
Logger.Tracef("meshcore: neighbors response: %q", response)
neighbors = make(map[string]float64)
for _, line := range strings.Split(response, "\n") {
parts := strings.SplitN(line, ":", 3)
if len(parts) != 3 {
continue
}
var (
prefix = parts[0]
snr float64
)
if snr, err = strconv.ParseFloat(parts[2], 64); err != nil {
return
}
neighbors[prefix] = snr
}
return
}
func (drv *repeaterDriver) parsePacket(line string) (time.Time, string, error) {
// "11:08:38 - 15/5/2024 U: TX, len=124 (type=4, route=D, payload_len=122)"
if len(line) < 22 {
return time.Time{}, "", io.EOF
}
i := strings.IndexByte(line, ':') - 2
if i < 0 {
return time.Time{}, "", io.EOF
}
t, err := time.Parse("15:04:05 - 02/3/2006", line[:i])
if err != nil {
return time.Time{}, "", err
}
return t, line[i:], nil
}

View File

@@ -21,11 +21,14 @@ type Packet struct {
SNR float64 `json:"snr"`
// RSSI is the received signal strength indicator (in dBm).
RSSI int8 `json:"rssi"`
RSSI int `json:"rssi"`
// Raw bytes (optional).
Raw []byte `json:"raw,omitempty"`
// Version for this packet.
Version int `json:"version"`
// RouteType is the type of route for this packet.
RouteType RouteType `json:"route_type"`
@@ -35,7 +38,14 @@ type Packet struct {
// TransportCodes are set by transport route types.
TransportCodes []uint16 `json:"transport_codes,omitempty"`
// Path are repeater hashes.
// PathHashSize is the number of bytes per node in the path hash.
PathHashSize int `json:"path_hash_size"`
// PathHashCount is the number of nodes in the path hash.
PathHashCount int `json:"path_hash_count"`
// Path are repeater hashes. The first hash is the source node, the last hash is the destination node, and the middle hashes are repeaters.
// The path is empty for direct packets.
Path []byte `json:"path"`
// Payload is the raw (encoded) payload.
@@ -111,7 +121,7 @@ func (packet *Packet) MarshalBytes() []byte {
data [1 + 4 + 1 + maxPathSize + maxPayloadSize]byte
offset int
)
data[offset] = byte(packet.RouteType&0x03) | byte((packet.PayloadType&0x0f)<<2)
data[offset] = byte(packet.RouteType&0x03) | byte((packet.PayloadType&0x0f)<<2) | byte((packet.Version&0x03)<<6)
offset += 1
if packet.RouteType.HasTransportCodes() {
@@ -138,6 +148,7 @@ func (packet *Packet) UnmarshalBytes(data []byte) error {
packet.RouteType = RouteType(data[0] & 0x03)
packet.PayloadType = PayloadType((data[0] >> 2) & 0x0f)
packet.Version = int((data[0] >> 6) & 0x03)
offset := 1
if packet.RouteType.HasTransportCodes() {
@@ -154,14 +165,21 @@ func (packet *Packet) UnmarshalBytes(data []byte) error {
packet.TransportCodes = nil
}
pathLength := int(data[offset])
var (
pathLength = int(data[offset])
pathHashSize = pathLength>>6 + 1
pathHashCount = pathLength & 63
pathByteLength = pathHashSize * pathHashCount
)
offset += 1
if pathLength > maxPathSize {
return fmt.Errorf("meshcore: path length %d exceeds maximum of %d", pathLength, maxPathSize)
} else if pathLength > len(data[offset:]) {
if pathByteLength > maxPathSize {
return fmt.Errorf("meshcore: path length %d exceeds maximum of %d", pathByteLength, maxPathSize)
} else if pathByteLength > len(data[offset:]) {
return io.ErrUnexpectedEOF
}
packet.Path = make([]byte, pathLength)
packet.Path = make([]byte, pathByteLength)
packet.PathHashSize = pathHashSize
packet.PathHashCount = pathHashCount
offset += copy(packet.Path, data[offset:])
payloadLength := len(data[offset:])
@@ -187,7 +205,7 @@ func (packet *Packet) Hash() []byte {
func (packet *Packet) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
SNR float64 `json:"snr"`
RSSI int8 `json:"rssi"`
RSSI int `json:"rssi"`
RouteType RouteType `json:"route_type"`
PayloadType PayloadType `json:"payload_type"`
TransportCodes []uint16 `json:"transport_codes,omitempty"`

View File

@@ -0,0 +1,46 @@
# Meshtastic Protocol Buffers Generation
This internal package manages the generation of Go types from the [official Meshtastic protocol buffer definitions](https://github.com/meshtastic/protobufs).
## Upstream Source
The protobuf definitions are included as a git submodule at:
```
protocol/meshtastic/internal/pbgen/upstream
```
Tracked repository: `https://github.com/meshtastic/protobufs.git`
## Generating Go Code
To regenerate the Go protobuf types after updating the submodule:
```bash
# Update the submodule to latest (optional)
git submodule update --remote protocol/meshtastic/internal/pbgen/upstream
# Regenerate Go code
go generate ./protocol/meshtastic/internal/pbgen
```
### Requirements
- `protoc` (Protocol Buffers compiler)
- `protoc-gen-go` (install via `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest`)
## Generated Package
Generated Go types are placed in:
```
protocol/meshtastic/pb/
```
Import path:
```go
import meshtasticpb "git.maze.io/go/ham/protocol/meshtastic/pb"
```
All Meshtastic protocol buffer messages (Position, User, Data, etc.) are available from this package.

View File

@@ -0,0 +1,5 @@
package protobufs
//go:generate sh ./generate.sh
const _ = 0

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env sh
set -eu
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
UPSTREAM_DIR="$SCRIPT_DIR/upstream"
OUT_DIR="$SCRIPT_DIR/../../pb"
MODULE_IMPORT="git.maze.io/go/ham/protocol/meshtastic/pb"
if ! command -v protoc >/dev/null 2>&1; then
echo "error: protoc not found in PATH" >&2
exit 1
fi
GOBIN_DIR="$(go env GOPATH 2>/dev/null)/bin"
if [ -n "$GOBIN_DIR" ] && [ -d "$GOBIN_DIR" ]; then
PATH="$GOBIN_DIR:$PATH"
export PATH
fi
if ! command -v protoc-gen-go >/dev/null 2>&1; then
echo "error: protoc-gen-go not found in PATH" >&2
exit 1
fi
if [ ! -d "$UPSTREAM_DIR/meshtastic" ]; then
echo "error: upstream proto directory not found: $UPSTREAM_DIR/meshtastic" >&2
exit 1
fi
PROTO_FILES=$(find "$UPSTREAM_DIR" -type f -name '*.proto' | LC_ALL=C sort)
if [ -z "$PROTO_FILES" ]; then
echo "error: no proto files found in $UPSTREAM_DIR" >&2
exit 1
fi
M_OPTS=""
for file in $PROTO_FILES; do
rel="${file#$UPSTREAM_DIR/}"
M_OPTS="$M_OPTS --go_opt=M$rel=$MODULE_IMPORT"
done
rm -rf "$OUT_DIR"/*.pb.go
mkdir -p "$OUT_DIR"
# shellcheck disable=SC2086
protoc \
-I "$UPSTREAM_DIR" \
--go_out="$OUT_DIR" \
--go_opt=paths=source_relative \
$M_OPTS \
$(printf '%s ' $PROTO_FILES)
# Flatten meshtastic/ subdirectory into pb/ root
if [ -d "$OUT_DIR/meshtastic" ]; then
mv "$OUT_DIR/meshtastic"/*.pb.go "$OUT_DIR/"
rmdir "$OUT_DIR/meshtastic"
fi
# Fix package name from 'generated' to 'pb'
for pbfile in "$OUT_DIR"/*.pb.go; do
if [ -f "$pbfile" ]; then
sed -i.bak 's/^package generated$/package pb/' "$pbfile"
rm -f "$pbfile.bak"
fi
done
# Convert JSON tags from snake_case to camelCase
# This converts json:"field_name,omitempty" to json:"fieldName,omitempty"
for pbfile in "$OUT_DIR"/*.pb.go; do
if [ -f "$pbfile" ]; then
perl -i -pe 's/json:"([a-z0-9_]+)((?:,[^"]*)?)"/my ($f, $r) = ($1, $2); $f =~ s#_([a-z0-9])#uc($1)#ge; "json:\"$f$r\""/gex' "$pbfile"
fi
done

173
protocol/meshtastic/node.go Normal file
View 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
}

View File

@@ -0,0 +1,159 @@
package meshtastic
import (
"encoding/binary"
"encoding/json"
"errors"
meshtasticpb "git.maze.io/go/ham/protocol/meshtastic/pb"
"google.golang.org/protobuf/proto"
)
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 `json:"destination"`
Source NodeID `json:"source"`
ID uint32 `json:"id"`
Flags uint8 `json:"flags"`
ChannelHash uint8 `json:"channelHash"`
NextHop uint8 `json:"nextHop"`
RelayNode uint8 `json:"relayNode"`
PayloadLength int `json:"-"`
Payload [maxPayloadSize]byte `json:"-"`
Data *meshtasticpb.Data `json:"data,omitempty"`
DecodedPayload proto.Message `json:"decodedPayload,omitempty"`
TextPayload string `json:"textPayload,omitempty"`
}
func (packet *Packet) Decode(data []byte) error {
if len(data) < minPacketSize {
return ErrInvalidPacket
}
if len(data[16:]) > maxPayloadSize {
return ErrInvalidPacket
}
packet.Source = parseNodeID(data[0:])
packet.Destination = 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:])
packet.Data = nil
packet.DecodedPayload = nil
packet.TextPayload = ""
packet.decodePayloadProtobufs()
return nil
}
func (packet *Packet) PayloadBytes() []byte {
return packet.Payload[:packet.PayloadLength]
}
func (packet *Packet) HopLimit() int {
return 7
}
func parseNodeID(data []byte) NodeID {
return NodeID(binary.LittleEndian.Uint32(data))
}
func (packet *Packet) decodePayloadProtobufs() {
if packet.PayloadLength == 0 {
return
}
decodedData := &meshtasticpb.Data{}
if err := proto.Unmarshal(packet.PayloadBytes(), decodedData); err != nil {
return
}
packet.Data = decodedData
decodedPayload, textPayload := decodeDataPayload(decodedData.GetPortnum(), decodedData.GetPayload())
packet.DecodedPayload = decodedPayload
packet.TextPayload = textPayload
}
func decodeDataPayload(portNum meshtasticpb.PortNum, payload []byte) (proto.Message, string) {
if len(payload) == 0 {
return nil, ""
}
textPortNum := map[meshtasticpb.PortNum]struct{}{
meshtasticpb.PortNum_TEXT_MESSAGE_APP: {},
meshtasticpb.PortNum_ALERT_APP: {},
meshtasticpb.PortNum_DETECTION_SENSOR_APP: {},
meshtasticpb.PortNum_REPLY_APP: {},
meshtasticpb.PortNum_RANGE_TEST_APP: {},
meshtasticpb.PortNum_TEXT_MESSAGE_COMPRESSED_APP: {},
}
if _, ok := textPortNum[portNum]; ok {
return nil, string(payload)
}
var message proto.Message
switch portNum {
case meshtasticpb.PortNum_REMOTE_HARDWARE_APP:
message = &meshtasticpb.HardwareMessage{}
case meshtasticpb.PortNum_POSITION_APP:
message = &meshtasticpb.Position{}
case meshtasticpb.PortNum_NODEINFO_APP:
message = &meshtasticpb.User{}
case meshtasticpb.PortNum_ROUTING_APP:
message = &meshtasticpb.Routing{}
case meshtasticpb.PortNum_ADMIN_APP:
message = &meshtasticpb.AdminMessage{}
case meshtasticpb.PortNum_WAYPOINT_APP:
message = &meshtasticpb.Waypoint{}
case meshtasticpb.PortNum_KEY_VERIFICATION_APP:
message = &meshtasticpb.KeyVerification{}
case meshtasticpb.PortNum_PAXCOUNTER_APP:
message = &meshtasticpb.Paxcount{}
case meshtasticpb.PortNum_STORE_FORWARD_PLUSPLUS_APP:
message = &meshtasticpb.StoreForwardPlusPlus{}
case meshtasticpb.PortNum_STORE_FORWARD_APP:
message = &meshtasticpb.StoreAndForward{}
case meshtasticpb.PortNum_TELEMETRY_APP:
message = &meshtasticpb.Telemetry{}
case meshtasticpb.PortNum_TRACEROUTE_APP:
message = &meshtasticpb.RouteDiscovery{}
case meshtasticpb.PortNum_NEIGHBORINFO_APP:
message = &meshtasticpb.NeighborInfo{}
case meshtasticpb.PortNum_NODE_STATUS_APP:
message = &meshtasticpb.StatusMessage{}
default:
return nil, ""
}
if err := proto.Unmarshal(payload, message); err != nil {
return nil, ""
}
return message, ""
}
// MarshalJSON implements custom JSON marshaling for Packet.
func (packet *Packet) MarshalJSON() ([]byte, error) {
type Alias Packet
return json.Marshal(&struct {
*Alias
Raw []byte `json:"raw"`
}{
Alias: (*Alias)(packet),
Raw: packet.PayloadBytes(),
})
}

View File

@@ -0,0 +1,591 @@
package meshtastic
import (
"encoding/binary"
"encoding/json"
"testing"
meshtasticpb "git.maze.io/go/ham/protocol/meshtastic/pb"
"google.golang.org/protobuf/proto"
)
func TestPacketDecodeTextPayload(t *testing.T) {
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_TEXT_MESSAGE_APP,
Payload: []byte("hello mesh"),
}
encodedData, err := proto.Marshal(dataMsg)
if err != nil {
t.Fatalf("marshaling data protobuf: %v", err)
}
rawPacket := make([]byte, 16+len(encodedData))
binary.LittleEndian.PutUint32(rawPacket[0:], 0x01020304)
binary.LittleEndian.PutUint32(rawPacket[4:], 0x05060708)
binary.LittleEndian.PutUint32(rawPacket[8:], 0x090a0b0c)
rawPacket[12] = 0x01
rawPacket[13] = 0x02
rawPacket[14] = 0x03
rawPacket[15] = 0x04
copy(rawPacket[16:], encodedData)
var packet Packet
if err := packet.Decode(rawPacket); err != nil {
t.Fatalf("decoding packet: %v", err)
}
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_TEXT_MESSAGE_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
if packet.TextPayload != "hello mesh" {
t.Fatalf("unexpected text payload: got %q", packet.TextPayload)
}
if packet.DecodedPayload != nil {
t.Fatalf("expected no decoded protobuf payload for text app, got %T", packet.DecodedPayload)
}
}
func TestPacketDecodeRejectsOversizedPayload(t *testing.T) {
rawPacket := make([]byte, 16+maxPayloadSize+1)
var packet Packet
err := packet.Decode(rawPacket)
if err != ErrInvalidPacket {
t.Fatalf("expected ErrInvalidPacket, got %v", err)
}
}
func TestPacketJSONSerialization(t *testing.T) {
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_TEXT_MESSAGE_APP,
Payload: []byte("test message"),
}
encodedData, err := proto.Marshal(dataMsg)
if err != nil {
t.Fatalf("marshaling data protobuf: %v", err)
}
rawPacket := make([]byte, 16+len(encodedData))
binary.LittleEndian.PutUint32(rawPacket[0:], 0x12345678) // source
binary.LittleEndian.PutUint32(rawPacket[4:], 0xabcdef01) // destination
binary.LittleEndian.PutUint32(rawPacket[8:], 0x99887766) // id
rawPacket[12] = 0x11 // flags
rawPacket[13] = 0x22 // channelHash
rawPacket[14] = 0x33 // nextHop
rawPacket[15] = 0x44 // relayNode
copy(rawPacket[16:], encodedData)
var packet Packet
if err := packet.Decode(rawPacket); err != nil {
t.Fatalf("decoding packet: %v", err)
}
jsonBytes, err := json.Marshal(&packet)
if err != nil {
t.Fatalf("marshaling to JSON: %v", err)
}
var decoded map[string]interface{}
if err := json.Unmarshal(jsonBytes, &decoded); err != nil {
t.Fatalf("unmarshaling JSON: %v", err)
}
// Check camelCase field names
if _, ok := decoded["destination"]; !ok {
t.Error("missing 'destination' field")
}
if _, ok := decoded["source"]; !ok {
t.Error("missing 'source' field")
}
if _, ok := decoded["channelHash"]; !ok {
t.Error("missing 'channelHash' field")
}
if _, ok := decoded["nextHop"]; !ok {
t.Error("missing 'nextHop' field")
}
if _, ok := decoded["relayNode"]; !ok {
t.Error("missing 'relayNode' field")
}
// Check raw payload field
if _, ok := decoded["raw"]; !ok {
t.Error("missing 'raw' field")
}
// Check text payload
if textPayload, ok := decoded["textPayload"].(string); !ok || textPayload != "test message" {
t.Errorf("expected textPayload='test message', got %v", decoded["textPayload"])
}
// Verify PayloadLength and Payload are not in JSON
if _, ok := decoded["PayloadLength"]; ok {
t.Error("PayloadLength should not be in JSON output")
}
if _, ok := decoded["Payload"]; ok {
t.Error("Payload should not be in JSON output")
}
}
func TestProtobufMessagesCamelCaseJSON(t *testing.T) {
// Test that protobuf messages serialize with camelCase JSON tags
pos := &meshtasticpb.Position{
LatitudeI: proto.Int32(379208045),
LongitudeI: proto.Int32(-1223928905),
Altitude: proto.Int32(120),
Time: 1234567890,
}
jsonBytes, err := json.Marshal(pos)
if err != nil {
t.Fatalf("marshaling Position to JSON: %v", err)
}
var decoded map[string]interface{}
if err := json.Unmarshal(jsonBytes, &decoded); err != nil {
t.Fatalf("unmarshaling JSON: %v", err)
}
// Check camelCase field names (not snake_case)
if _, ok := decoded["latitudeI"]; !ok {
t.Error("missing camelCase 'latitudeI' field")
}
if _, ok := decoded["longitudeI"]; !ok {
t.Error("missing camelCase 'longitudeI' field")
}
if _, ok := decoded["altitude"]; !ok {
t.Error("missing 'altitude' field")
}
// Check that snake_case fields are NOT present
if _, ok := decoded["latitude_i"]; ok {
t.Error("found snake_case 'latitude_i' field, expected camelCase 'latitudeI'")
}
if _, ok := decoded["longitude_i"]; ok {
t.Error("found snake_case 'longitude_i' field, expected camelCase 'longitudeI'")
}
// Test User message with underscored fields
user := &meshtasticpb.User{
LongName: "Test User",
ShortName: "TU",
HwModel: meshtasticpb.HardwareModel_TBEAM,
}
jsonBytes, err = json.Marshal(user)
if err != nil {
t.Fatalf("marshaling User to JSON: %v", err)
}
decoded = make(map[string]interface{})
if err := json.Unmarshal(jsonBytes, &decoded); err != nil {
t.Fatalf("unmarshaling JSON: %v", err)
}
// Check camelCase field names
if _, ok := decoded["longName"]; !ok {
t.Error("missing camelCase 'longName' field")
}
if _, ok := decoded["shortName"]; !ok {
t.Error("missing camelCase 'shortName' field")
}
if _, ok := decoded["hwModel"]; !ok {
t.Error("missing camelCase 'hwModel' field")
}
// Check that snake_case fields are NOT present
if _, ok := decoded["long_name"]; ok {
t.Error("found snake_case 'long_name' field, expected camelCase 'longName'")
}
if _, ok := decoded["short_name"]; ok {
t.Error("found snake_case 'short_name' field, expected camelCase 'shortName'")
}
if _, ok := decoded["hw_model"]; ok {
t.Error("found snake_case 'hw_model' field, expected camelCase 'hwModel'")
}
}
// Test POSITION_APP portnum decoding
func TestPacketDecodePositionApp(t *testing.T) {
pos := &meshtasticpb.Position{
LatitudeI: proto.Int32(379208045),
LongitudeI: proto.Int32(-1223928905),
Altitude: proto.Int32(120),
Time: 1234567890,
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_POSITION_APP,
Payload: encodeProto(t, pos),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_POSITION_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedPos, ok := packet.DecodedPayload.(*meshtasticpb.Position)
if !ok {
t.Fatalf("expected *Position, got %T", packet.DecodedPayload)
}
if decodedPos.GetLatitudeI() != 379208045 {
t.Errorf("unexpected latitude: got %d", decodedPos.GetLatitudeI())
}
if decodedPos.GetLongitudeI() != -1223928905 {
t.Errorf("unexpected longitude: got %d", decodedPos.GetLongitudeI())
}
if decodedPos.GetAltitude() != 120 {
t.Errorf("unexpected altitude: got %d", decodedPos.GetAltitude())
}
}
// Test NODEINFO_APP portnum decoding
func TestPacketDecodeNodeInfoApp(t *testing.T) {
user := &meshtasticpb.User{
LongName: "Mesh Node",
ShortName: "MN",
HwModel: meshtasticpb.HardwareModel_TBEAM,
IsLicensed: true,
Macaddr: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_NODEINFO_APP,
Payload: encodeProto(t, user),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_NODEINFO_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedUser, ok := packet.DecodedPayload.(*meshtasticpb.User)
if !ok {
t.Fatalf("expected *User, got %T", packet.DecodedPayload)
}
if decodedUser.GetLongName() != "Mesh Node" {
t.Errorf("unexpected long name: got %s", decodedUser.GetLongName())
}
if decodedUser.GetShortName() != "MN" {
t.Errorf("unexpected short name: got %s", decodedUser.GetShortName())
}
if decodedUser.GetHwModel() != meshtasticpb.HardwareModel_TBEAM {
t.Errorf("unexpected hardware model: got %v", decodedUser.GetHwModel())
}
}
// Test TELEMETRY_APP portnum decoding
func TestPacketDecodeTelemetryApp(t *testing.T) {
batteryLevel := uint32(85)
voltage := float32(12.5)
channelUtil := float32(45.3)
airUtilTx := float32(15.2)
telemetry := &meshtasticpb.Telemetry{
Time: 1234567890,
Variant: &meshtasticpb.Telemetry_DeviceMetrics_{
DeviceMetrics: &meshtasticpb.DeviceMetrics{
BatteryLevel: &batteryLevel,
Voltage: &voltage,
ChannelUtilization: &channelUtil,
AirUtilTx: &airUtilTx,
},
},
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_TELEMETRY_APP,
Payload: encodeProto(t, telemetry),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_TELEMETRY_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedTelem, ok := packet.DecodedPayload.(*meshtasticpb.Telemetry)
if !ok {
t.Fatalf("expected *Telemetry, got %T", packet.DecodedPayload)
}
if decodedTelem.GetTime() != 1234567890 {
t.Errorf("unexpected time: got %d", decodedTelem.GetTime())
}
deviceMetrics := decodedTelem.GetDeviceMetrics()
if deviceMetrics == nil {
t.Fatal("expected DeviceMetrics to be set")
}
if deviceMetrics.GetBatteryLevel() != 85 {
t.Errorf("unexpected battery level: got %d", deviceMetrics.GetBatteryLevel())
}
}
// Test ROUTING_APP portnum decoding
func TestPacketDecodeRoutingApp(t *testing.T) {
routing := &meshtasticpb.Routing{
Variant: &meshtasticpb.Routing_RouteReply_{
RouteReply: &meshtasticpb.Routing_RouteReply{},
},
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_ROUTING_APP,
Payload: encodeProto(t, routing),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_ROUTING_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedRouting, ok := packet.DecodedPayload.(*meshtasticpb.Routing)
if !ok {
t.Fatalf("expected *Routing, got %T", packet.DecodedPayload)
}
if decodedRouting.GetRouteReply() == nil {
t.Fatal("expected RouteReply to be set")
}
}
// Test ADMIN_APP portnum decoding
func TestPacketDecodeAdminApp(t *testing.T) {
// AdminMessage has a sessionPasskey field
admin := &meshtasticpb.AdminMessage{
SessionPasskey: []byte{0x01, 0x02, 0x03, 0x04},
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_ADMIN_APP,
Payload: encodeProto(t, admin),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_ADMIN_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedAdmin, ok := packet.DecodedPayload.(*meshtasticpb.AdminMessage)
if !ok {
t.Fatalf("expected *AdminMessage, got %T", packet.DecodedPayload)
}
if len(decodedAdmin.GetSessionPasskey()) != 4 {
t.Errorf("unexpected session passkey length: got %d", len(decodedAdmin.GetSessionPasskey()))
}
}
// Test WAYPOINT_APP portnum decoding
func TestPacketDecodeWaypointApp(t *testing.T) {
latI := int32(379208045)
lonI := int32(-1223928905)
waypoint := &meshtasticpb.Waypoint{
Id: 1001,
Name: "Home Sweet Home",
Description: "My house",
LatitudeI: &latI,
LongitudeI: &lonI,
Expire: 1234567890,
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_WAYPOINT_APP,
Payload: encodeProto(t, waypoint),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_WAYPOINT_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedWaypoint, ok := packet.DecodedPayload.(*meshtasticpb.Waypoint)
if !ok {
t.Fatalf("expected *Waypoint, got %T", packet.DecodedPayload)
}
if decodedWaypoint.GetName() != "Home Sweet Home" {
t.Errorf("unexpected waypoint name: got %s", decodedWaypoint.GetName())
}
if decodedWaypoint.GetId() != 1001 {
t.Errorf("unexpected waypoint id: got %d", decodedWaypoint.GetId())
}
}
// Test NEIGHBORINFO_APP portnum decoding
func TestPacketDecodeNeighborInfoApp(t *testing.T) {
neighborInfo := &meshtasticpb.NeighborInfo{
NodeId: 12345,
NodeBroadcastIntervalSecs: 60,
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_NEIGHBORINFO_APP,
Payload: encodeProto(t, neighborInfo),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_NEIGHBORINFO_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedNeighbor, ok := packet.DecodedPayload.(*meshtasticpb.NeighborInfo)
if !ok {
t.Fatalf("expected *NeighborInfo, got %T", packet.DecodedPayload)
}
if decodedNeighbor.GetNodeId() != 12345 {
t.Errorf("unexpected node id: got %d", decodedNeighbor.GetNodeId())
}
}
// Test NODE_STATUS_APP portnum decoding
func TestPacketDecodeNodeStatusApp(t *testing.T) {
statusMsg := &meshtasticpb.StatusMessage{
Status: "Device online",
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_NODE_STATUS_APP,
Payload: encodeProto(t, statusMsg),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_NODE_STATUS_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedStatus, ok := packet.DecodedPayload.(*meshtasticpb.StatusMessage)
if !ok {
t.Fatalf("expected *StatusMessage, got %T", packet.DecodedPayload)
}
if decodedStatus.GetStatus() != "Device online" {
t.Errorf("unexpected status: got %s", decodedStatus.GetStatus())
}
}
// Test REMOTE_HARDWARE_APP portnum decoding
func TestPacketDecodeRemoteHardwareApp(t *testing.T) {
hwMsg := &meshtasticpb.HardwareMessage{
Type: meshtasticpb.HardwareMessage_READ_GPIOS,
GpioMask: 0xFF,
}
dataMsg := &meshtasticpb.Data{
Portnum: meshtasticpb.PortNum_REMOTE_HARDWARE_APP,
Payload: encodeProto(t, hwMsg),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != meshtasticpb.PortNum_REMOTE_HARDWARE_APP {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
decodedHw, ok := packet.DecodedPayload.(*meshtasticpb.HardwareMessage)
if !ok {
t.Fatalf("expected *HardwareMessage, got %T", packet.DecodedPayload)
}
if decodedHw.GetType() != meshtasticpb.HardwareMessage_READ_GPIOS {
t.Errorf("unexpected hardware message type: got %v", decodedHw.GetType())
}
}
// Test text payload portnums
func TestPacketDecodeTextPayloads(t *testing.T) {
textPortNums := map[meshtasticpb.PortNum]string{
meshtasticpb.PortNum_TEXT_MESSAGE_APP: "text message app",
meshtasticpb.PortNum_ALERT_APP: "alert message",
meshtasticpb.PortNum_DETECTION_SENSOR_APP: "detection sensor",
meshtasticpb.PortNum_REPLY_APP: "reply message",
meshtasticpb.PortNum_RANGE_TEST_APP: "range test message",
}
for portNum, expectedText := range textPortNums {
t.Run(portNum.String(), func(t *testing.T) {
dataMsg := &meshtasticpb.Data{
Portnum: portNum,
Payload: []byte(expectedText),
}
packet := decodeTestPacket(t, dataMsg)
if packet.Data == nil {
t.Fatal("expected Data protobuf to be decoded")
}
if packet.Data.GetPortnum() != portNum {
t.Fatalf("unexpected portnum: got %v", packet.Data.GetPortnum())
}
if packet.TextPayload != expectedText {
t.Errorf("unexpected text payload: got %q, want %q", packet.TextPayload, expectedText)
}
if packet.DecodedPayload != nil {
t.Errorf("expected no decoded payload for text portnum, got %T", packet.DecodedPayload)
}
})
}
}
// Helper function to encode a proto.Message
func encodeProto(t *testing.T, msg proto.Message) []byte {
encoded, err := proto.Marshal(msg)
if err != nil {
t.Fatalf("marshaling protobuf: %v", err)
}
return encoded
}
// Helper function to create and decode a test packet
func decodeTestPacket(t *testing.T, dataMsg *meshtasticpb.Data) *Packet {
encodedData := encodeProto(t, dataMsg)
rawPacket := make([]byte, 16+len(encodedData))
binary.LittleEndian.PutUint32(rawPacket[0:], 0x12345678) // source
binary.LittleEndian.PutUint32(rawPacket[4:], 0xabcdef01) // destination
binary.LittleEndian.PutUint32(rawPacket[8:], 0x99887766) // id
rawPacket[12] = 0x11 // flags
rawPacket[13] = 0x22 // channelHash
rawPacket[14] = 0x33 // nextHop
rawPacket[15] = 0x44 // relayNode
copy(rawPacket[16:], encodedData)
var packet Packet
if err := packet.Decode(rawPacket); err != nil {
t.Fatalf("decoding packet: %v", err)
}
return &packet
}

File diff suppressed because it is too large Load Diff

View File

View File

@@ -0,0 +1,148 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/apponly.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is the most compact possible representation for a set of channels.
// It includes only one PRIMARY channel (which must be first) and
// any SECONDARY channels.
// No DISABLED channels are included.
// This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL
type ChannelSet struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Channel list with settings
Settings []*ChannelSettings `protobuf:"bytes,1,rep,name=settings,proto3" json:"settings,omitempty"`
// LoRa config
LoraConfig *Config_LoRaConfig `protobuf:"bytes,2,opt,name=lora_config,json=loraConfig,proto3" json:"loraConfig,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ChannelSet) Reset() {
*x = ChannelSet{}
mi := &file_meshtastic_apponly_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ChannelSet) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelSet) ProtoMessage() {}
func (x *ChannelSet) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_apponly_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelSet.ProtoReflect.Descriptor instead.
func (*ChannelSet) Descriptor() ([]byte, []int) {
return file_meshtastic_apponly_proto_rawDescGZIP(), []int{0}
}
func (x *ChannelSet) GetSettings() []*ChannelSettings {
if x != nil {
return x.Settings
}
return nil
}
func (x *ChannelSet) GetLoraConfig() *Config_LoRaConfig {
if x != nil {
return x.LoraConfig
}
return nil
}
var File_meshtastic_apponly_proto protoreflect.FileDescriptor
const file_meshtastic_apponly_proto_rawDesc = "" +
"\n" +
"\x18meshtastic/apponly.proto\x12\n" +
"meshtastic\x1a\x18meshtastic/channel.proto\x1a\x17meshtastic/config.proto\"\x85\x01\n" +
"\n" +
"ChannelSet\x127\n" +
"\bsettings\x18\x01 \x03(\v2\x1b.meshtastic.ChannelSettingsR\bsettings\x12>\n" +
"\vlora_config\x18\x02 \x01(\v2\x1d.meshtastic.Config.LoRaConfigR\n" +
"loraConfigBc\n" +
"\x14org.meshtastic.protoB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_apponly_proto_rawDescOnce sync.Once
file_meshtastic_apponly_proto_rawDescData []byte
)
func file_meshtastic_apponly_proto_rawDescGZIP() []byte {
file_meshtastic_apponly_proto_rawDescOnce.Do(func() {
file_meshtastic_apponly_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_apponly_proto_rawDesc), len(file_meshtastic_apponly_proto_rawDesc)))
})
return file_meshtastic_apponly_proto_rawDescData
}
var file_meshtastic_apponly_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_meshtastic_apponly_proto_goTypes = []any{
(*ChannelSet)(nil), // 0: meshtastic.ChannelSet
(*ChannelSettings)(nil), // 1: meshtastic.ChannelSettings
(*Config_LoRaConfig)(nil), // 2: meshtastic.Config.LoRaConfig
}
var file_meshtastic_apponly_proto_depIdxs = []int32{
1, // 0: meshtastic.ChannelSet.settings:type_name -> meshtastic.ChannelSettings
2, // 1: meshtastic.ChannelSet.lora_config:type_name -> meshtastic.Config.LoRaConfig
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_meshtastic_apponly_proto_init() }
func file_meshtastic_apponly_proto_init() {
if File_meshtastic_apponly_proto != nil {
return
}
file_meshtastic_channel_proto_init()
file_meshtastic_config_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_apponly_proto_rawDesc), len(file_meshtastic_apponly_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_apponly_proto_goTypes,
DependencyIndexes: file_meshtastic_apponly_proto_depIdxs,
MessageInfos: file_meshtastic_apponly_proto_msgTypes,
}.Build()
File_meshtastic_apponly_proto = out.File
file_meshtastic_apponly_proto_goTypes = nil
file_meshtastic_apponly_proto_depIdxs = nil
}

View File

@@ -0,0 +1,797 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/atak.proto
// trunk-ignore(buf-lint/PACKAGE_DIRECTORY_MATCH)
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Team int32
const (
// Unspecifed
Team_Unspecifed_Color Team = 0
// White
Team_White Team = 1
// Yellow
Team_Yellow Team = 2
// Orange
Team_Orange Team = 3
// Magenta
Team_Magenta Team = 4
// Red
Team_Red Team = 5
// Maroon
Team_Maroon Team = 6
// Purple
Team_Purple Team = 7
// Dark Blue
Team_Dark_Blue Team = 8
// Blue
Team_Blue Team = 9
// Cyan
Team_Cyan Team = 10
// Teal
Team_Teal Team = 11
// Green
Team_Green Team = 12
// Dark Green
Team_Dark_Green Team = 13
// Brown
Team_Brown Team = 14
)
// Enum value maps for Team.
var (
Team_name = map[int32]string{
0: "Unspecifed_Color",
1: "White",
2: "Yellow",
3: "Orange",
4: "Magenta",
5: "Red",
6: "Maroon",
7: "Purple",
8: "Dark_Blue",
9: "Blue",
10: "Cyan",
11: "Teal",
12: "Green",
13: "Dark_Green",
14: "Brown",
}
Team_value = map[string]int32{
"Unspecifed_Color": 0,
"White": 1,
"Yellow": 2,
"Orange": 3,
"Magenta": 4,
"Red": 5,
"Maroon": 6,
"Purple": 7,
"Dark_Blue": 8,
"Blue": 9,
"Cyan": 10,
"Teal": 11,
"Green": 12,
"Dark_Green": 13,
"Brown": 14,
}
)
func (x Team) Enum() *Team {
p := new(Team)
*p = x
return p
}
func (x Team) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Team) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_atak_proto_enumTypes[0].Descriptor()
}
func (Team) Type() protoreflect.EnumType {
return &file_meshtastic_atak_proto_enumTypes[0]
}
func (x Team) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Team.Descriptor instead.
func (Team) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_atak_proto_rawDescGZIP(), []int{0}
}
// Role of the group member
type MemberRole int32
const (
// Unspecifed
MemberRole_Unspecifed MemberRole = 0
// Team Member
MemberRole_TeamMember MemberRole = 1
// Team Lead
MemberRole_TeamLead MemberRole = 2
// Headquarters
MemberRole_HQ MemberRole = 3
// Airsoft enthusiast
MemberRole_Sniper MemberRole = 4
// Medic
MemberRole_Medic MemberRole = 5
// ForwardObserver
MemberRole_ForwardObserver MemberRole = 6
// Radio Telephone Operator
MemberRole_RTO MemberRole = 7
// Doggo
MemberRole_K9 MemberRole = 8
)
// Enum value maps for MemberRole.
var (
MemberRole_name = map[int32]string{
0: "Unspecifed",
1: "TeamMember",
2: "TeamLead",
3: "HQ",
4: "Sniper",
5: "Medic",
6: "ForwardObserver",
7: "RTO",
8: "K9",
}
MemberRole_value = map[string]int32{
"Unspecifed": 0,
"TeamMember": 1,
"TeamLead": 2,
"HQ": 3,
"Sniper": 4,
"Medic": 5,
"ForwardObserver": 6,
"RTO": 7,
"K9": 8,
}
)
func (x MemberRole) Enum() *MemberRole {
p := new(MemberRole)
*p = x
return p
}
func (x MemberRole) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MemberRole) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_atak_proto_enumTypes[1].Descriptor()
}
func (MemberRole) Type() protoreflect.EnumType {
return &file_meshtastic_atak_proto_enumTypes[1]
}
func (x MemberRole) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MemberRole.Descriptor instead.
func (MemberRole) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_atak_proto_rawDescGZIP(), []int{1}
}
// Packets for the official ATAK Plugin
type TAKPacket struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Are the payloads strings compressed for LoRA transport?
IsCompressed bool `protobuf:"varint,1,opt,name=is_compressed,json=isCompressed,proto3" json:"isCompressed,omitempty"`
// The contact / callsign for ATAK user
Contact *Contact `protobuf:"bytes,2,opt,name=contact,proto3" json:"contact,omitempty"`
// The group for ATAK user
Group *Group `protobuf:"bytes,3,opt,name=group,proto3" json:"group,omitempty"`
// The status of the ATAK EUD
Status *Status `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
// The payload of the packet
//
// Types that are valid to be assigned to PayloadVariant:
//
// *TAKPacket_Pli
// *TAKPacket_Chat
// *TAKPacket_Detail
PayloadVariant isTAKPacket_PayloadVariant `protobuf_oneof:"payload_variant"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TAKPacket) Reset() {
*x = TAKPacket{}
mi := &file_meshtastic_atak_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TAKPacket) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TAKPacket) ProtoMessage() {}
func (x *TAKPacket) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_atak_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TAKPacket.ProtoReflect.Descriptor instead.
func (*TAKPacket) Descriptor() ([]byte, []int) {
return file_meshtastic_atak_proto_rawDescGZIP(), []int{0}
}
func (x *TAKPacket) GetIsCompressed() bool {
if x != nil {
return x.IsCompressed
}
return false
}
func (x *TAKPacket) GetContact() *Contact {
if x != nil {
return x.Contact
}
return nil
}
func (x *TAKPacket) GetGroup() *Group {
if x != nil {
return x.Group
}
return nil
}
func (x *TAKPacket) GetStatus() *Status {
if x != nil {
return x.Status
}
return nil
}
func (x *TAKPacket) GetPayloadVariant() isTAKPacket_PayloadVariant {
if x != nil {
return x.PayloadVariant
}
return nil
}
func (x *TAKPacket) GetPli() *PLI {
if x != nil {
if x, ok := x.PayloadVariant.(*TAKPacket_Pli); ok {
return x.Pli
}
}
return nil
}
func (x *TAKPacket) GetChat() *GeoChat {
if x != nil {
if x, ok := x.PayloadVariant.(*TAKPacket_Chat); ok {
return x.Chat
}
}
return nil
}
func (x *TAKPacket) GetDetail() []byte {
if x != nil {
if x, ok := x.PayloadVariant.(*TAKPacket_Detail); ok {
return x.Detail
}
}
return nil
}
type isTAKPacket_PayloadVariant interface {
isTAKPacket_PayloadVariant()
}
type TAKPacket_Pli struct {
// TAK position report
Pli *PLI `protobuf:"bytes,5,opt,name=pli,proto3,oneof"`
}
type TAKPacket_Chat struct {
// ATAK GeoChat message
Chat *GeoChat `protobuf:"bytes,6,opt,name=chat,proto3,oneof"`
}
type TAKPacket_Detail struct {
// Generic CoT detail XML
// May be compressed / truncated by the sender (EUD)
Detail []byte `protobuf:"bytes,7,opt,name=detail,proto3,oneof"`
}
func (*TAKPacket_Pli) isTAKPacket_PayloadVariant() {}
func (*TAKPacket_Chat) isTAKPacket_PayloadVariant() {}
func (*TAKPacket_Detail) isTAKPacket_PayloadVariant() {}
// ATAK GeoChat message
type GeoChat struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The text message
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
// Uid recipient of the message
To *string `protobuf:"bytes,2,opt,name=to,proto3,oneof" json:"to,omitempty"`
// Callsign of the recipient for the message
ToCallsign *string `protobuf:"bytes,3,opt,name=to_callsign,json=toCallsign,proto3,oneof" json:"toCallsign,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GeoChat) Reset() {
*x = GeoChat{}
mi := &file_meshtastic_atak_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GeoChat) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GeoChat) ProtoMessage() {}
func (x *GeoChat) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_atak_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GeoChat.ProtoReflect.Descriptor instead.
func (*GeoChat) Descriptor() ([]byte, []int) {
return file_meshtastic_atak_proto_rawDescGZIP(), []int{1}
}
func (x *GeoChat) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *GeoChat) GetTo() string {
if x != nil && x.To != nil {
return *x.To
}
return ""
}
func (x *GeoChat) GetToCallsign() string {
if x != nil && x.ToCallsign != nil {
return *x.ToCallsign
}
return ""
}
// ATAK Group
// <__group role='Team Member' name='Cyan'/>
type Group struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Role of the group member
Role MemberRole `protobuf:"varint,1,opt,name=role,proto3,enum=meshtastic.MemberRole" json:"role,omitempty"`
// Team (color)
// Default Cyan
Team Team `protobuf:"varint,2,opt,name=team,proto3,enum=meshtastic.Team" json:"team,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Group) Reset() {
*x = Group{}
mi := &file_meshtastic_atak_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Group) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Group) ProtoMessage() {}
func (x *Group) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_atak_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Group.ProtoReflect.Descriptor instead.
func (*Group) Descriptor() ([]byte, []int) {
return file_meshtastic_atak_proto_rawDescGZIP(), []int{2}
}
func (x *Group) GetRole() MemberRole {
if x != nil {
return x.Role
}
return MemberRole_Unspecifed
}
func (x *Group) GetTeam() Team {
if x != nil {
return x.Team
}
return Team_Unspecifed_Color
}
// ATAK EUD Status
// <status battery='100' />
type Status struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Battery level
Battery uint32 `protobuf:"varint,1,opt,name=battery,proto3" json:"battery,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Status) Reset() {
*x = Status{}
mi := &file_meshtastic_atak_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Status) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Status) ProtoMessage() {}
func (x *Status) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_atak_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Status.ProtoReflect.Descriptor instead.
func (*Status) Descriptor() ([]byte, []int) {
return file_meshtastic_atak_proto_rawDescGZIP(), []int{3}
}
func (x *Status) GetBattery() uint32 {
if x != nil {
return x.Battery
}
return 0
}
// ATAK Contact
// <contact endpoint='0.0.0.0:4242:tcp' phone='+12345678' callsign='FALKE'/>
type Contact struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Callsign
Callsign string `protobuf:"bytes,1,opt,name=callsign,proto3" json:"callsign,omitempty"`
// Device callsign
DeviceCallsign string `protobuf:"bytes,2,opt,name=device_callsign,json=deviceCallsign,proto3" json:"deviceCallsign,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Contact) Reset() {
*x = Contact{}
mi := &file_meshtastic_atak_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Contact) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Contact) ProtoMessage() {}
func (x *Contact) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_atak_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Contact.ProtoReflect.Descriptor instead.
func (*Contact) Descriptor() ([]byte, []int) {
return file_meshtastic_atak_proto_rawDescGZIP(), []int{4}
}
func (x *Contact) GetCallsign() string {
if x != nil {
return x.Callsign
}
return ""
}
func (x *Contact) GetDeviceCallsign() string {
if x != nil {
return x.DeviceCallsign
}
return ""
}
// Position Location Information from ATAK
type PLI struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The new preferred location encoding, multiply by 1e-7 to get degrees
// in floating point
LatitudeI int32 `protobuf:"fixed32,1,opt,name=latitude_i,json=latitudeI,proto3" json:"latitudeI,omitempty"`
// The new preferred location encoding, multiply by 1e-7 to get degrees
// in floating point
LongitudeI int32 `protobuf:"fixed32,2,opt,name=longitude_i,json=longitudeI,proto3" json:"longitudeI,omitempty"`
// Altitude (ATAK prefers HAE)
Altitude int32 `protobuf:"varint,3,opt,name=altitude,proto3" json:"altitude,omitempty"`
// Speed
Speed uint32 `protobuf:"varint,4,opt,name=speed,proto3" json:"speed,omitempty"`
// Course in degrees
Course uint32 `protobuf:"varint,5,opt,name=course,proto3" json:"course,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PLI) Reset() {
*x = PLI{}
mi := &file_meshtastic_atak_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PLI) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PLI) ProtoMessage() {}
func (x *PLI) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_atak_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PLI.ProtoReflect.Descriptor instead.
func (*PLI) Descriptor() ([]byte, []int) {
return file_meshtastic_atak_proto_rawDescGZIP(), []int{5}
}
func (x *PLI) GetLatitudeI() int32 {
if x != nil {
return x.LatitudeI
}
return 0
}
func (x *PLI) GetLongitudeI() int32 {
if x != nil {
return x.LongitudeI
}
return 0
}
func (x *PLI) GetAltitude() int32 {
if x != nil {
return x.Altitude
}
return 0
}
func (x *PLI) GetSpeed() uint32 {
if x != nil {
return x.Speed
}
return 0
}
func (x *PLI) GetCourse() uint32 {
if x != nil {
return x.Course
}
return 0
}
var File_meshtastic_atak_proto protoreflect.FileDescriptor
const file_meshtastic_atak_proto_rawDesc = "" +
"\n" +
"\x15meshtastic/atak.proto\x12\n" +
"meshtastic\"\xb1\x02\n" +
"\tTAKPacket\x12#\n" +
"\ris_compressed\x18\x01 \x01(\bR\fisCompressed\x12-\n" +
"\acontact\x18\x02 \x01(\v2\x13.meshtastic.ContactR\acontact\x12'\n" +
"\x05group\x18\x03 \x01(\v2\x11.meshtastic.GroupR\x05group\x12*\n" +
"\x06status\x18\x04 \x01(\v2\x12.meshtastic.StatusR\x06status\x12#\n" +
"\x03pli\x18\x05 \x01(\v2\x0f.meshtastic.PLIH\x00R\x03pli\x12)\n" +
"\x04chat\x18\x06 \x01(\v2\x13.meshtastic.GeoChatH\x00R\x04chat\x12\x18\n" +
"\x06detail\x18\a \x01(\fH\x00R\x06detailB\x11\n" +
"\x0fpayload_variant\"u\n" +
"\aGeoChat\x12\x18\n" +
"\amessage\x18\x01 \x01(\tR\amessage\x12\x13\n" +
"\x02to\x18\x02 \x01(\tH\x00R\x02to\x88\x01\x01\x12$\n" +
"\vto_callsign\x18\x03 \x01(\tH\x01R\n" +
"toCallsign\x88\x01\x01B\x05\n" +
"\x03_toB\x0e\n" +
"\f_to_callsign\"Y\n" +
"\x05Group\x12*\n" +
"\x04role\x18\x01 \x01(\x0e2\x16.meshtastic.MemberRoleR\x04role\x12$\n" +
"\x04team\x18\x02 \x01(\x0e2\x10.meshtastic.TeamR\x04team\"\"\n" +
"\x06Status\x12\x18\n" +
"\abattery\x18\x01 \x01(\rR\abattery\"N\n" +
"\aContact\x12\x1a\n" +
"\bcallsign\x18\x01 \x01(\tR\bcallsign\x12'\n" +
"\x0fdevice_callsign\x18\x02 \x01(\tR\x0edeviceCallsign\"\x8f\x01\n" +
"\x03PLI\x12\x1d\n" +
"\n" +
"latitude_i\x18\x01 \x01(\x0fR\tlatitudeI\x12\x1f\n" +
"\vlongitude_i\x18\x02 \x01(\x0fR\n" +
"longitudeI\x12\x1a\n" +
"\baltitude\x18\x03 \x01(\x05R\baltitude\x12\x14\n" +
"\x05speed\x18\x04 \x01(\rR\x05speed\x12\x16\n" +
"\x06course\x18\x05 \x01(\rR\x06course*\xc0\x01\n" +
"\x04Team\x12\x14\n" +
"\x10Unspecifed_Color\x10\x00\x12\t\n" +
"\x05White\x10\x01\x12\n" +
"\n" +
"\x06Yellow\x10\x02\x12\n" +
"\n" +
"\x06Orange\x10\x03\x12\v\n" +
"\aMagenta\x10\x04\x12\a\n" +
"\x03Red\x10\x05\x12\n" +
"\n" +
"\x06Maroon\x10\x06\x12\n" +
"\n" +
"\x06Purple\x10\a\x12\r\n" +
"\tDark_Blue\x10\b\x12\b\n" +
"\x04Blue\x10\t\x12\b\n" +
"\x04Cyan\x10\n" +
"\x12\b\n" +
"\x04Teal\x10\v\x12\t\n" +
"\x05Green\x10\f\x12\x0e\n" +
"\n" +
"Dark_Green\x10\r\x12\t\n" +
"\x05Brown\x10\x0e*\x7f\n" +
"\n" +
"MemberRole\x12\x0e\n" +
"\n" +
"Unspecifed\x10\x00\x12\x0e\n" +
"\n" +
"TeamMember\x10\x01\x12\f\n" +
"\bTeamLead\x10\x02\x12\x06\n" +
"\x02HQ\x10\x03\x12\n" +
"\n" +
"\x06Sniper\x10\x04\x12\t\n" +
"\x05Medic\x10\x05\x12\x13\n" +
"\x0fForwardObserver\x10\x06\x12\a\n" +
"\x03RTO\x10\a\x12\x06\n" +
"\x02K9\x10\bB`\n" +
"\x14org.meshtastic.protoB\n" +
"ATAKProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_atak_proto_rawDescOnce sync.Once
file_meshtastic_atak_proto_rawDescData []byte
)
func file_meshtastic_atak_proto_rawDescGZIP() []byte {
file_meshtastic_atak_proto_rawDescOnce.Do(func() {
file_meshtastic_atak_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_atak_proto_rawDesc), len(file_meshtastic_atak_proto_rawDesc)))
})
return file_meshtastic_atak_proto_rawDescData
}
var file_meshtastic_atak_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_meshtastic_atak_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_meshtastic_atak_proto_goTypes = []any{
(Team)(0), // 0: meshtastic.Team
(MemberRole)(0), // 1: meshtastic.MemberRole
(*TAKPacket)(nil), // 2: meshtastic.TAKPacket
(*GeoChat)(nil), // 3: meshtastic.GeoChat
(*Group)(nil), // 4: meshtastic.Group
(*Status)(nil), // 5: meshtastic.Status
(*Contact)(nil), // 6: meshtastic.Contact
(*PLI)(nil), // 7: meshtastic.PLI
}
var file_meshtastic_atak_proto_depIdxs = []int32{
6, // 0: meshtastic.TAKPacket.contact:type_name -> meshtastic.Contact
4, // 1: meshtastic.TAKPacket.group:type_name -> meshtastic.Group
5, // 2: meshtastic.TAKPacket.status:type_name -> meshtastic.Status
7, // 3: meshtastic.TAKPacket.pli:type_name -> meshtastic.PLI
3, // 4: meshtastic.TAKPacket.chat:type_name -> meshtastic.GeoChat
1, // 5: meshtastic.Group.role:type_name -> meshtastic.MemberRole
0, // 6: meshtastic.Group.team:type_name -> meshtastic.Team
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_meshtastic_atak_proto_init() }
func file_meshtastic_atak_proto_init() {
if File_meshtastic_atak_proto != nil {
return
}
file_meshtastic_atak_proto_msgTypes[0].OneofWrappers = []any{
(*TAKPacket_Pli)(nil),
(*TAKPacket_Chat)(nil),
(*TAKPacket_Detail)(nil),
}
file_meshtastic_atak_proto_msgTypes[1].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_atak_proto_rawDesc), len(file_meshtastic_atak_proto_rawDesc)),
NumEnums: 2,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_atak_proto_goTypes,
DependencyIndexes: file_meshtastic_atak_proto_depIdxs,
EnumInfos: file_meshtastic_atak_proto_enumTypes,
MessageInfos: file_meshtastic_atak_proto_msgTypes,
}.Build()
File_meshtastic_atak_proto = out.File
file_meshtastic_atak_proto_goTypes = nil
file_meshtastic_atak_proto_depIdxs = nil
}

View File

@@ -0,0 +1,126 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/cannedmessages.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Canned message module configuration.
type CannedMessageModuleConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Predefined messages for canned message module separated by '|' characters.
Messages string `protobuf:"bytes,1,opt,name=messages,proto3" json:"messages,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CannedMessageModuleConfig) Reset() {
*x = CannedMessageModuleConfig{}
mi := &file_meshtastic_cannedmessages_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CannedMessageModuleConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CannedMessageModuleConfig) ProtoMessage() {}
func (x *CannedMessageModuleConfig) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_cannedmessages_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CannedMessageModuleConfig.ProtoReflect.Descriptor instead.
func (*CannedMessageModuleConfig) Descriptor() ([]byte, []int) {
return file_meshtastic_cannedmessages_proto_rawDescGZIP(), []int{0}
}
func (x *CannedMessageModuleConfig) GetMessages() string {
if x != nil {
return x.Messages
}
return ""
}
var File_meshtastic_cannedmessages_proto protoreflect.FileDescriptor
const file_meshtastic_cannedmessages_proto_rawDesc = "" +
"\n" +
"\x1fmeshtastic/cannedmessages.proto\x12\n" +
"meshtastic\"7\n" +
"\x19CannedMessageModuleConfig\x12\x1a\n" +
"\bmessages\x18\x01 \x01(\tR\bmessagesBo\n" +
"\x14org.meshtastic.protoB\x19CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_cannedmessages_proto_rawDescOnce sync.Once
file_meshtastic_cannedmessages_proto_rawDescData []byte
)
func file_meshtastic_cannedmessages_proto_rawDescGZIP() []byte {
file_meshtastic_cannedmessages_proto_rawDescOnce.Do(func() {
file_meshtastic_cannedmessages_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_cannedmessages_proto_rawDesc), len(file_meshtastic_cannedmessages_proto_rawDesc)))
})
return file_meshtastic_cannedmessages_proto_rawDescData
}
var file_meshtastic_cannedmessages_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_meshtastic_cannedmessages_proto_goTypes = []any{
(*CannedMessageModuleConfig)(nil), // 0: meshtastic.CannedMessageModuleConfig
}
var file_meshtastic_cannedmessages_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_meshtastic_cannedmessages_proto_init() }
func file_meshtastic_cannedmessages_proto_init() {
if File_meshtastic_cannedmessages_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_cannedmessages_proto_rawDesc), len(file_meshtastic_cannedmessages_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_cannedmessages_proto_goTypes,
DependencyIndexes: file_meshtastic_cannedmessages_proto_depIdxs,
MessageInfos: file_meshtastic_cannedmessages_proto_msgTypes,
}.Build()
File_meshtastic_cannedmessages_proto = out.File
file_meshtastic_cannedmessages_proto_goTypes = nil
file_meshtastic_cannedmessages_proto_depIdxs = nil
}

View File

@@ -0,0 +1,435 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/channel.proto
// trunk-ignore(buf-lint/PACKAGE_DIRECTORY_MATCH)
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// How this channel is being used (or not).
// Note: this field is an enum to give us options for the future.
// In particular, someday we might make a 'SCANNING' option.
// SCANNING channels could have different frequencies and the radio would
// occasionally check that freq to see if anything is being transmitted.
// For devices that have multiple physical radios attached, we could keep multiple PRIMARY/SCANNING channels active at once to allow
// cross band routing as needed.
// If a device has only a single radio (the common case) only one channel can be PRIMARY at a time
// (but any number of SECONDARY channels can't be sent received on that common frequency)
type Channel_Role int32
const (
// This channel is not in use right now
Channel_DISABLED Channel_Role = 0
// This channel is used to set the frequency for the radio - all other enabled channels must be SECONDARY
Channel_PRIMARY Channel_Role = 1
// Secondary channels are only used for encryption/decryption/authentication purposes.
// Their radio settings (freq etc) are ignored, only psk is used.
Channel_SECONDARY Channel_Role = 2
)
// Enum value maps for Channel_Role.
var (
Channel_Role_name = map[int32]string{
0: "DISABLED",
1: "PRIMARY",
2: "SECONDARY",
}
Channel_Role_value = map[string]int32{
"DISABLED": 0,
"PRIMARY": 1,
"SECONDARY": 2,
}
)
func (x Channel_Role) Enum() *Channel_Role {
p := new(Channel_Role)
*p = x
return p
}
func (x Channel_Role) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Channel_Role) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_channel_proto_enumTypes[0].Descriptor()
}
func (Channel_Role) Type() protoreflect.EnumType {
return &file_meshtastic_channel_proto_enumTypes[0]
}
func (x Channel_Role) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Channel_Role.Descriptor instead.
func (Channel_Role) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_channel_proto_rawDescGZIP(), []int{2, 0}
}
// This information can be encoded as a QRcode/url so that other users can configure
// their radio to join the same channel.
// A note about how channel names are shown to users: channelname-X
// poundsymbol is a prefix used to indicate this is a channel name (idea from @professr).
// Where X is a letter from A-Z (base 26) representing a hash of the PSK for this
// channel - so that if the user changes anything about the channel (which does
// force a new PSK) this letter will also change. Thus preventing user confusion if
// two friends try to type in a channel name of "BobsChan" and then can't talk
// because their PSKs will be different.
// The PSK is hashed into this letter by "0x41 + [xor all bytes of the psk ] modulo 26"
// This also allows the option of someday if people have the PSK off (zero), the
// users COULD type in a channel name and be able to talk.
// FIXME: Add description of multi-channel support and how primary vs secondary channels are used.
// FIXME: explain how apps use channels for security.
// explain how remote settings and remote gpio are managed as an example
type ChannelSettings struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Deprecated in favor of LoraConfig.channel_num
//
// Deprecated: Marked as deprecated in meshtastic/channel.proto.
ChannelNum uint32 `protobuf:"varint,1,opt,name=channel_num,json=channelNum,proto3" json:"channelNum,omitempty"`
// A simple pre-shared key for now for crypto.
// Must be either 0 bytes (no crypto), 16 bytes (AES128), or 32 bytes (AES256).
// A special shorthand is used for 1 byte long psks.
// These psks should be treated as only minimally secure,
// because they are listed in this source code.
// Those bytes are mapped using the following scheme:
// `0` = No crypto
// `1` = The special "default" channel key: {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01}
// `2` through 10 = The default channel key, except with 1 through 9 added to the last byte.
// Shown to user as simple1 through 10
Psk []byte `protobuf:"bytes,2,opt,name=psk,proto3" json:"psk,omitempty"`
// A SHORT name that will be packed into the URL.
// Less than 12 bytes.
// Something for end users to call the channel
// If this is the empty string it is assumed that this channel
// is the special (minimally secure) "Default"channel.
// In user interfaces it should be rendered as a local language translation of "X".
// For channel_num hashing empty string will be treated as "X".
// Where "X" is selected based on the English words listed above for ModemPreset
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
// Used to construct a globally unique channel ID.
// The full globally unique ID will be: "name.id" where ID is shown as base36.
// Assuming that the number of meshtastic users is below 20K (true for a long time)
// the chance of this 64 bit random number colliding with anyone else is super low.
// And the penalty for collision is low as well, it just means that anyone trying to decrypt channel messages might need to
// try multiple candidate channels.
// Any time a non wire compatible change is made to a channel, this field should be regenerated.
// There are a small number of 'special' globally known (and fairly) insecure standard channels.
// Those channels do not have a numeric id included in the settings, but instead it is pulled from
// a table of well known IDs.
// (see Well Known Channels FIXME)
Id uint32 `protobuf:"fixed32,4,opt,name=id,proto3" json:"id,omitempty"`
// If true, messages on the mesh will be sent to the *public* internet by any gateway ndoe
UplinkEnabled bool `protobuf:"varint,5,opt,name=uplink_enabled,json=uplinkEnabled,proto3" json:"uplinkEnabled,omitempty"`
// If true, messages seen on the internet will be forwarded to the local mesh.
DownlinkEnabled bool `protobuf:"varint,6,opt,name=downlink_enabled,json=downlinkEnabled,proto3" json:"downlinkEnabled,omitempty"`
// Per-channel module settings.
ModuleSettings *ModuleSettings `protobuf:"bytes,7,opt,name=module_settings,json=moduleSettings,proto3" json:"moduleSettings,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ChannelSettings) Reset() {
*x = ChannelSettings{}
mi := &file_meshtastic_channel_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ChannelSettings) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelSettings) ProtoMessage() {}
func (x *ChannelSettings) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_channel_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelSettings.ProtoReflect.Descriptor instead.
func (*ChannelSettings) Descriptor() ([]byte, []int) {
return file_meshtastic_channel_proto_rawDescGZIP(), []int{0}
}
// Deprecated: Marked as deprecated in meshtastic/channel.proto.
func (x *ChannelSettings) GetChannelNum() uint32 {
if x != nil {
return x.ChannelNum
}
return 0
}
func (x *ChannelSettings) GetPsk() []byte {
if x != nil {
return x.Psk
}
return nil
}
func (x *ChannelSettings) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *ChannelSettings) GetId() uint32 {
if x != nil {
return x.Id
}
return 0
}
func (x *ChannelSettings) GetUplinkEnabled() bool {
if x != nil {
return x.UplinkEnabled
}
return false
}
func (x *ChannelSettings) GetDownlinkEnabled() bool {
if x != nil {
return x.DownlinkEnabled
}
return false
}
func (x *ChannelSettings) GetModuleSettings() *ModuleSettings {
if x != nil {
return x.ModuleSettings
}
return nil
}
// This message is specifically for modules to store per-channel configuration data.
type ModuleSettings struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Bits of precision for the location sent in position packets.
PositionPrecision uint32 `protobuf:"varint,1,opt,name=position_precision,json=positionPrecision,proto3" json:"positionPrecision,omitempty"`
// Controls whether or not the client / device should mute the current channel
// Useful for noisy public channels you don't necessarily want to disable
IsMuted bool `protobuf:"varint,2,opt,name=is_muted,json=isMuted,proto3" json:"isMuted,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ModuleSettings) Reset() {
*x = ModuleSettings{}
mi := &file_meshtastic_channel_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ModuleSettings) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ModuleSettings) ProtoMessage() {}
func (x *ModuleSettings) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_channel_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ModuleSettings.ProtoReflect.Descriptor instead.
func (*ModuleSettings) Descriptor() ([]byte, []int) {
return file_meshtastic_channel_proto_rawDescGZIP(), []int{1}
}
func (x *ModuleSettings) GetPositionPrecision() uint32 {
if x != nil {
return x.PositionPrecision
}
return 0
}
func (x *ModuleSettings) GetIsMuted() bool {
if x != nil {
return x.IsMuted
}
return false
}
// A pair of a channel number, mode and the (sharable) settings for that channel
type Channel struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The index of this channel in the channel table (from 0 to MAX_NUM_CHANNELS-1)
// (Someday - not currently implemented) An index of -1 could be used to mean "set by name",
// in which case the target node will find and set the channel by settings.name.
Index int32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"`
// The new settings, or NULL to disable that channel
Settings *ChannelSettings `protobuf:"bytes,2,opt,name=settings,proto3" json:"settings,omitempty"`
// TODO: REPLACE
Role Channel_Role `protobuf:"varint,3,opt,name=role,proto3,enum=meshtastic.Channel_Role" json:"role,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Channel) Reset() {
*x = Channel{}
mi := &file_meshtastic_channel_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Channel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Channel) ProtoMessage() {}
func (x *Channel) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_channel_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Channel.ProtoReflect.Descriptor instead.
func (*Channel) Descriptor() ([]byte, []int) {
return file_meshtastic_channel_proto_rawDescGZIP(), []int{2}
}
func (x *Channel) GetIndex() int32 {
if x != nil {
return x.Index
}
return 0
}
func (x *Channel) GetSettings() *ChannelSettings {
if x != nil {
return x.Settings
}
return nil
}
func (x *Channel) GetRole() Channel_Role {
if x != nil {
return x.Role
}
return Channel_DISABLED
}
var File_meshtastic_channel_proto protoreflect.FileDescriptor
const file_meshtastic_channel_proto_rawDesc = "" +
"\n" +
"\x18meshtastic/channel.proto\x12\n" +
"meshtastic\"\x83\x02\n" +
"\x0fChannelSettings\x12#\n" +
"\vchannel_num\x18\x01 \x01(\rB\x02\x18\x01R\n" +
"channelNum\x12\x10\n" +
"\x03psk\x18\x02 \x01(\fR\x03psk\x12\x12\n" +
"\x04name\x18\x03 \x01(\tR\x04name\x12\x0e\n" +
"\x02id\x18\x04 \x01(\aR\x02id\x12%\n" +
"\x0euplink_enabled\x18\x05 \x01(\bR\ruplinkEnabled\x12)\n" +
"\x10downlink_enabled\x18\x06 \x01(\bR\x0fdownlinkEnabled\x12C\n" +
"\x0fmodule_settings\x18\a \x01(\v2\x1a.meshtastic.ModuleSettingsR\x0emoduleSettings\"Z\n" +
"\x0eModuleSettings\x12-\n" +
"\x12position_precision\x18\x01 \x01(\rR\x11positionPrecision\x12\x19\n" +
"\bis_muted\x18\x02 \x01(\bR\aisMuted\"\xb8\x01\n" +
"\aChannel\x12\x14\n" +
"\x05index\x18\x01 \x01(\x05R\x05index\x127\n" +
"\bsettings\x18\x02 \x01(\v2\x1b.meshtastic.ChannelSettingsR\bsettings\x12,\n" +
"\x04role\x18\x03 \x01(\x0e2\x18.meshtastic.Channel.RoleR\x04role\"0\n" +
"\x04Role\x12\f\n" +
"\bDISABLED\x10\x00\x12\v\n" +
"\aPRIMARY\x10\x01\x12\r\n" +
"\tSECONDARY\x10\x02Bc\n" +
"\x14org.meshtastic.protoB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_channel_proto_rawDescOnce sync.Once
file_meshtastic_channel_proto_rawDescData []byte
)
func file_meshtastic_channel_proto_rawDescGZIP() []byte {
file_meshtastic_channel_proto_rawDescOnce.Do(func() {
file_meshtastic_channel_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_channel_proto_rawDesc), len(file_meshtastic_channel_proto_rawDesc)))
})
return file_meshtastic_channel_proto_rawDescData
}
var file_meshtastic_channel_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_meshtastic_channel_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_meshtastic_channel_proto_goTypes = []any{
(Channel_Role)(0), // 0: meshtastic.Channel.Role
(*ChannelSettings)(nil), // 1: meshtastic.ChannelSettings
(*ModuleSettings)(nil), // 2: meshtastic.ModuleSettings
(*Channel)(nil), // 3: meshtastic.Channel
}
var file_meshtastic_channel_proto_depIdxs = []int32{
2, // 0: meshtastic.ChannelSettings.module_settings:type_name -> meshtastic.ModuleSettings
1, // 1: meshtastic.Channel.settings:type_name -> meshtastic.ChannelSettings
0, // 2: meshtastic.Channel.role:type_name -> meshtastic.Channel.Role
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_meshtastic_channel_proto_init() }
func file_meshtastic_channel_proto_init() {
if File_meshtastic_channel_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_channel_proto_rawDesc), len(file_meshtastic_channel_proto_rawDesc)),
NumEnums: 1,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_channel_proto_goTypes,
DependencyIndexes: file_meshtastic_channel_proto_depIdxs,
EnumInfos: file_meshtastic_channel_proto_enumTypes,
MessageInfos: file_meshtastic_channel_proto_msgTypes,
}.Build()
File_meshtastic_channel_proto = out.File
file_meshtastic_channel_proto_goTypes = nil
file_meshtastic_channel_proto_depIdxs = nil
}

View File

@@ -0,0 +1,217 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/clientonly.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This abstraction is used to contain any configuration for provisioning a node on any client.
// It is useful for importing and exporting configurations.
type DeviceProfile struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Long name for the node
LongName *string `protobuf:"bytes,1,opt,name=long_name,json=longName,proto3,oneof" json:"longName,omitempty"`
// Short name of the node
ShortName *string `protobuf:"bytes,2,opt,name=short_name,json=shortName,proto3,oneof" json:"shortName,omitempty"`
// The url of the channels from our node
ChannelUrl *string `protobuf:"bytes,3,opt,name=channel_url,json=channelUrl,proto3,oneof" json:"channelUrl,omitempty"`
// The Config of the node
Config *LocalConfig `protobuf:"bytes,4,opt,name=config,proto3,oneof" json:"config,omitempty"`
// The ModuleConfig of the node
ModuleConfig *LocalModuleConfig `protobuf:"bytes,5,opt,name=module_config,json=moduleConfig,proto3,oneof" json:"moduleConfig,omitempty"`
// Fixed position data
FixedPosition *Position `protobuf:"bytes,6,opt,name=fixed_position,json=fixedPosition,proto3,oneof" json:"fixedPosition,omitempty"`
// Ringtone for ExternalNotification
Ringtone *string `protobuf:"bytes,7,opt,name=ringtone,proto3,oneof" json:"ringtone,omitempty"`
// Predefined messages for CannedMessage
CannedMessages *string `protobuf:"bytes,8,opt,name=canned_messages,json=cannedMessages,proto3,oneof" json:"cannedMessages,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceProfile) Reset() {
*x = DeviceProfile{}
mi := &file_meshtastic_clientonly_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeviceProfile) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeviceProfile) ProtoMessage() {}
func (x *DeviceProfile) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_clientonly_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeviceProfile.ProtoReflect.Descriptor instead.
func (*DeviceProfile) Descriptor() ([]byte, []int) {
return file_meshtastic_clientonly_proto_rawDescGZIP(), []int{0}
}
func (x *DeviceProfile) GetLongName() string {
if x != nil && x.LongName != nil {
return *x.LongName
}
return ""
}
func (x *DeviceProfile) GetShortName() string {
if x != nil && x.ShortName != nil {
return *x.ShortName
}
return ""
}
func (x *DeviceProfile) GetChannelUrl() string {
if x != nil && x.ChannelUrl != nil {
return *x.ChannelUrl
}
return ""
}
func (x *DeviceProfile) GetConfig() *LocalConfig {
if x != nil {
return x.Config
}
return nil
}
func (x *DeviceProfile) GetModuleConfig() *LocalModuleConfig {
if x != nil {
return x.ModuleConfig
}
return nil
}
func (x *DeviceProfile) GetFixedPosition() *Position {
if x != nil {
return x.FixedPosition
}
return nil
}
func (x *DeviceProfile) GetRingtone() string {
if x != nil && x.Ringtone != nil {
return *x.Ringtone
}
return ""
}
func (x *DeviceProfile) GetCannedMessages() string {
if x != nil && x.CannedMessages != nil {
return *x.CannedMessages
}
return ""
}
var File_meshtastic_clientonly_proto protoreflect.FileDescriptor
const file_meshtastic_clientonly_proto_rawDesc = "" +
"\n" +
"\x1bmeshtastic/clientonly.proto\x12\n" +
"meshtastic\x1a\x1ameshtastic/localonly.proto\x1a\x15meshtastic/mesh.proto\"\x89\x04\n" +
"\rDeviceProfile\x12 \n" +
"\tlong_name\x18\x01 \x01(\tH\x00R\blongName\x88\x01\x01\x12\"\n" +
"\n" +
"short_name\x18\x02 \x01(\tH\x01R\tshortName\x88\x01\x01\x12$\n" +
"\vchannel_url\x18\x03 \x01(\tH\x02R\n" +
"channelUrl\x88\x01\x01\x124\n" +
"\x06config\x18\x04 \x01(\v2\x17.meshtastic.LocalConfigH\x03R\x06config\x88\x01\x01\x12G\n" +
"\rmodule_config\x18\x05 \x01(\v2\x1d.meshtastic.LocalModuleConfigH\x04R\fmoduleConfig\x88\x01\x01\x12@\n" +
"\x0efixed_position\x18\x06 \x01(\v2\x14.meshtastic.PositionH\x05R\rfixedPosition\x88\x01\x01\x12\x1f\n" +
"\bringtone\x18\a \x01(\tH\x06R\bringtone\x88\x01\x01\x12,\n" +
"\x0fcanned_messages\x18\b \x01(\tH\aR\x0ecannedMessages\x88\x01\x01B\f\n" +
"\n" +
"_long_nameB\r\n" +
"\v_short_nameB\x0e\n" +
"\f_channel_urlB\t\n" +
"\a_configB\x10\n" +
"\x0e_module_configB\x11\n" +
"\x0f_fixed_positionB\v\n" +
"\t_ringtoneB\x12\n" +
"\x10_canned_messagesBf\n" +
"\x14org.meshtastic.protoB\x10ClientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_clientonly_proto_rawDescOnce sync.Once
file_meshtastic_clientonly_proto_rawDescData []byte
)
func file_meshtastic_clientonly_proto_rawDescGZIP() []byte {
file_meshtastic_clientonly_proto_rawDescOnce.Do(func() {
file_meshtastic_clientonly_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_clientonly_proto_rawDesc), len(file_meshtastic_clientonly_proto_rawDesc)))
})
return file_meshtastic_clientonly_proto_rawDescData
}
var file_meshtastic_clientonly_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_meshtastic_clientonly_proto_goTypes = []any{
(*DeviceProfile)(nil), // 0: meshtastic.DeviceProfile
(*LocalConfig)(nil), // 1: meshtastic.LocalConfig
(*LocalModuleConfig)(nil), // 2: meshtastic.LocalModuleConfig
(*Position)(nil), // 3: meshtastic.Position
}
var file_meshtastic_clientonly_proto_depIdxs = []int32{
1, // 0: meshtastic.DeviceProfile.config:type_name -> meshtastic.LocalConfig
2, // 1: meshtastic.DeviceProfile.module_config:type_name -> meshtastic.LocalModuleConfig
3, // 2: meshtastic.DeviceProfile.fixed_position:type_name -> meshtastic.Position
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_meshtastic_clientonly_proto_init() }
func file_meshtastic_clientonly_proto_init() {
if File_meshtastic_clientonly_proto != nil {
return
}
file_meshtastic_localonly_proto_init()
file_meshtastic_mesh_proto_init()
file_meshtastic_clientonly_proto_msgTypes[0].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_clientonly_proto_rawDesc), len(file_meshtastic_clientonly_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_clientonly_proto_goTypes,
DependencyIndexes: file_meshtastic_clientonly_proto_depIdxs,
MessageInfos: file_meshtastic_clientonly_proto_msgTypes,
}.Build()
File_meshtastic_clientonly_proto = out.File
file_meshtastic_clientonly_proto_goTypes = nil
file_meshtastic_clientonly_proto_depIdxs = nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,493 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/connection_status.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type DeviceConnectionStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
// WiFi Status
Wifi *WifiConnectionStatus `protobuf:"bytes,1,opt,name=wifi,proto3,oneof" json:"wifi,omitempty"`
// WiFi Status
Ethernet *EthernetConnectionStatus `protobuf:"bytes,2,opt,name=ethernet,proto3,oneof" json:"ethernet,omitempty"`
// Bluetooth Status
Bluetooth *BluetoothConnectionStatus `protobuf:"bytes,3,opt,name=bluetooth,proto3,oneof" json:"bluetooth,omitempty"`
// Serial Status
Serial *SerialConnectionStatus `protobuf:"bytes,4,opt,name=serial,proto3,oneof" json:"serial,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceConnectionStatus) Reset() {
*x = DeviceConnectionStatus{}
mi := &file_meshtastic_connection_status_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeviceConnectionStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeviceConnectionStatus) ProtoMessage() {}
func (x *DeviceConnectionStatus) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_connection_status_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeviceConnectionStatus.ProtoReflect.Descriptor instead.
func (*DeviceConnectionStatus) Descriptor() ([]byte, []int) {
return file_meshtastic_connection_status_proto_rawDescGZIP(), []int{0}
}
func (x *DeviceConnectionStatus) GetWifi() *WifiConnectionStatus {
if x != nil {
return x.Wifi
}
return nil
}
func (x *DeviceConnectionStatus) GetEthernet() *EthernetConnectionStatus {
if x != nil {
return x.Ethernet
}
return nil
}
func (x *DeviceConnectionStatus) GetBluetooth() *BluetoothConnectionStatus {
if x != nil {
return x.Bluetooth
}
return nil
}
func (x *DeviceConnectionStatus) GetSerial() *SerialConnectionStatus {
if x != nil {
return x.Serial
}
return nil
}
// WiFi connection status
type WifiConnectionStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Connection status
Status *NetworkConnectionStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
// WiFi access point SSID
Ssid string `protobuf:"bytes,2,opt,name=ssid,proto3" json:"ssid,omitempty"`
// RSSI of wireless connection
Rssi int32 `protobuf:"varint,3,opt,name=rssi,proto3" json:"rssi,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WifiConnectionStatus) Reset() {
*x = WifiConnectionStatus{}
mi := &file_meshtastic_connection_status_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WifiConnectionStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WifiConnectionStatus) ProtoMessage() {}
func (x *WifiConnectionStatus) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_connection_status_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WifiConnectionStatus.ProtoReflect.Descriptor instead.
func (*WifiConnectionStatus) Descriptor() ([]byte, []int) {
return file_meshtastic_connection_status_proto_rawDescGZIP(), []int{1}
}
func (x *WifiConnectionStatus) GetStatus() *NetworkConnectionStatus {
if x != nil {
return x.Status
}
return nil
}
func (x *WifiConnectionStatus) GetSsid() string {
if x != nil {
return x.Ssid
}
return ""
}
func (x *WifiConnectionStatus) GetRssi() int32 {
if x != nil {
return x.Rssi
}
return 0
}
// Ethernet connection status
type EthernetConnectionStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Connection status
Status *NetworkConnectionStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EthernetConnectionStatus) Reset() {
*x = EthernetConnectionStatus{}
mi := &file_meshtastic_connection_status_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EthernetConnectionStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EthernetConnectionStatus) ProtoMessage() {}
func (x *EthernetConnectionStatus) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_connection_status_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EthernetConnectionStatus.ProtoReflect.Descriptor instead.
func (*EthernetConnectionStatus) Descriptor() ([]byte, []int) {
return file_meshtastic_connection_status_proto_rawDescGZIP(), []int{2}
}
func (x *EthernetConnectionStatus) GetStatus() *NetworkConnectionStatus {
if x != nil {
return x.Status
}
return nil
}
// Ethernet or WiFi connection status
type NetworkConnectionStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
// IP address of device
IpAddress uint32 `protobuf:"fixed32,1,opt,name=ip_address,json=ipAddress,proto3" json:"ipAddress,omitempty"`
// Whether the device has an active connection or not
IsConnected bool `protobuf:"varint,2,opt,name=is_connected,json=isConnected,proto3" json:"isConnected,omitempty"`
// Whether the device has an active connection to an MQTT broker or not
IsMqttConnected bool `protobuf:"varint,3,opt,name=is_mqtt_connected,json=isMqttConnected,proto3" json:"isMqttConnected,omitempty"`
// Whether the device is actively remote syslogging or not
IsSyslogConnected bool `protobuf:"varint,4,opt,name=is_syslog_connected,json=isSyslogConnected,proto3" json:"isSyslogConnected,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *NetworkConnectionStatus) Reset() {
*x = NetworkConnectionStatus{}
mi := &file_meshtastic_connection_status_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *NetworkConnectionStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NetworkConnectionStatus) ProtoMessage() {}
func (x *NetworkConnectionStatus) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_connection_status_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NetworkConnectionStatus.ProtoReflect.Descriptor instead.
func (*NetworkConnectionStatus) Descriptor() ([]byte, []int) {
return file_meshtastic_connection_status_proto_rawDescGZIP(), []int{3}
}
func (x *NetworkConnectionStatus) GetIpAddress() uint32 {
if x != nil {
return x.IpAddress
}
return 0
}
func (x *NetworkConnectionStatus) GetIsConnected() bool {
if x != nil {
return x.IsConnected
}
return false
}
func (x *NetworkConnectionStatus) GetIsMqttConnected() bool {
if x != nil {
return x.IsMqttConnected
}
return false
}
func (x *NetworkConnectionStatus) GetIsSyslogConnected() bool {
if x != nil {
return x.IsSyslogConnected
}
return false
}
// Bluetooth connection status
type BluetoothConnectionStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The pairing PIN for bluetooth
Pin uint32 `protobuf:"varint,1,opt,name=pin,proto3" json:"pin,omitempty"`
// RSSI of bluetooth connection
Rssi int32 `protobuf:"varint,2,opt,name=rssi,proto3" json:"rssi,omitempty"`
// Whether the device has an active connection or not
IsConnected bool `protobuf:"varint,3,opt,name=is_connected,json=isConnected,proto3" json:"isConnected,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BluetoothConnectionStatus) Reset() {
*x = BluetoothConnectionStatus{}
mi := &file_meshtastic_connection_status_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BluetoothConnectionStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BluetoothConnectionStatus) ProtoMessage() {}
func (x *BluetoothConnectionStatus) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_connection_status_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BluetoothConnectionStatus.ProtoReflect.Descriptor instead.
func (*BluetoothConnectionStatus) Descriptor() ([]byte, []int) {
return file_meshtastic_connection_status_proto_rawDescGZIP(), []int{4}
}
func (x *BluetoothConnectionStatus) GetPin() uint32 {
if x != nil {
return x.Pin
}
return 0
}
func (x *BluetoothConnectionStatus) GetRssi() int32 {
if x != nil {
return x.Rssi
}
return 0
}
func (x *BluetoothConnectionStatus) GetIsConnected() bool {
if x != nil {
return x.IsConnected
}
return false
}
// Serial connection status
type SerialConnectionStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Serial baud rate
Baud uint32 `protobuf:"varint,1,opt,name=baud,proto3" json:"baud,omitempty"`
// Whether the device has an active connection or not
IsConnected bool `protobuf:"varint,2,opt,name=is_connected,json=isConnected,proto3" json:"isConnected,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SerialConnectionStatus) Reset() {
*x = SerialConnectionStatus{}
mi := &file_meshtastic_connection_status_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SerialConnectionStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SerialConnectionStatus) ProtoMessage() {}
func (x *SerialConnectionStatus) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_connection_status_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SerialConnectionStatus.ProtoReflect.Descriptor instead.
func (*SerialConnectionStatus) Descriptor() ([]byte, []int) {
return file_meshtastic_connection_status_proto_rawDescGZIP(), []int{5}
}
func (x *SerialConnectionStatus) GetBaud() uint32 {
if x != nil {
return x.Baud
}
return 0
}
func (x *SerialConnectionStatus) GetIsConnected() bool {
if x != nil {
return x.IsConnected
}
return false
}
var File_meshtastic_connection_status_proto protoreflect.FileDescriptor
const file_meshtastic_connection_status_proto_rawDesc = "" +
"\n" +
"\"meshtastic/connection_status.proto\x12\n" +
"meshtastic\"\xd4\x02\n" +
"\x16DeviceConnectionStatus\x129\n" +
"\x04wifi\x18\x01 \x01(\v2 .meshtastic.WifiConnectionStatusH\x00R\x04wifi\x88\x01\x01\x12E\n" +
"\bethernet\x18\x02 \x01(\v2$.meshtastic.EthernetConnectionStatusH\x01R\bethernet\x88\x01\x01\x12H\n" +
"\tbluetooth\x18\x03 \x01(\v2%.meshtastic.BluetoothConnectionStatusH\x02R\tbluetooth\x88\x01\x01\x12?\n" +
"\x06serial\x18\x04 \x01(\v2\".meshtastic.SerialConnectionStatusH\x03R\x06serial\x88\x01\x01B\a\n" +
"\x05_wifiB\v\n" +
"\t_ethernetB\f\n" +
"\n" +
"_bluetoothB\t\n" +
"\a_serial\"{\n" +
"\x14WifiConnectionStatus\x12;\n" +
"\x06status\x18\x01 \x01(\v2#.meshtastic.NetworkConnectionStatusR\x06status\x12\x12\n" +
"\x04ssid\x18\x02 \x01(\tR\x04ssid\x12\x12\n" +
"\x04rssi\x18\x03 \x01(\x05R\x04rssi\"W\n" +
"\x18EthernetConnectionStatus\x12;\n" +
"\x06status\x18\x01 \x01(\v2#.meshtastic.NetworkConnectionStatusR\x06status\"\xb7\x01\n" +
"\x17NetworkConnectionStatus\x12\x1d\n" +
"\n" +
"ip_address\x18\x01 \x01(\aR\tipAddress\x12!\n" +
"\fis_connected\x18\x02 \x01(\bR\visConnected\x12*\n" +
"\x11is_mqtt_connected\x18\x03 \x01(\bR\x0fisMqttConnected\x12.\n" +
"\x13is_syslog_connected\x18\x04 \x01(\bR\x11isSyslogConnected\"d\n" +
"\x19BluetoothConnectionStatus\x12\x10\n" +
"\x03pin\x18\x01 \x01(\rR\x03pin\x12\x12\n" +
"\x04rssi\x18\x02 \x01(\x05R\x04rssi\x12!\n" +
"\fis_connected\x18\x03 \x01(\bR\visConnected\"O\n" +
"\x16SerialConnectionStatus\x12\x12\n" +
"\x04baud\x18\x01 \x01(\rR\x04baud\x12!\n" +
"\fis_connected\x18\x02 \x01(\bR\visConnectedBf\n" +
"\x14org.meshtastic.protoB\x10ConnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_connection_status_proto_rawDescOnce sync.Once
file_meshtastic_connection_status_proto_rawDescData []byte
)
func file_meshtastic_connection_status_proto_rawDescGZIP() []byte {
file_meshtastic_connection_status_proto_rawDescOnce.Do(func() {
file_meshtastic_connection_status_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_connection_status_proto_rawDesc), len(file_meshtastic_connection_status_proto_rawDesc)))
})
return file_meshtastic_connection_status_proto_rawDescData
}
var file_meshtastic_connection_status_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_meshtastic_connection_status_proto_goTypes = []any{
(*DeviceConnectionStatus)(nil), // 0: meshtastic.DeviceConnectionStatus
(*WifiConnectionStatus)(nil), // 1: meshtastic.WifiConnectionStatus
(*EthernetConnectionStatus)(nil), // 2: meshtastic.EthernetConnectionStatus
(*NetworkConnectionStatus)(nil), // 3: meshtastic.NetworkConnectionStatus
(*BluetoothConnectionStatus)(nil), // 4: meshtastic.BluetoothConnectionStatus
(*SerialConnectionStatus)(nil), // 5: meshtastic.SerialConnectionStatus
}
var file_meshtastic_connection_status_proto_depIdxs = []int32{
1, // 0: meshtastic.DeviceConnectionStatus.wifi:type_name -> meshtastic.WifiConnectionStatus
2, // 1: meshtastic.DeviceConnectionStatus.ethernet:type_name -> meshtastic.EthernetConnectionStatus
4, // 2: meshtastic.DeviceConnectionStatus.bluetooth:type_name -> meshtastic.BluetoothConnectionStatus
5, // 3: meshtastic.DeviceConnectionStatus.serial:type_name -> meshtastic.SerialConnectionStatus
3, // 4: meshtastic.WifiConnectionStatus.status:type_name -> meshtastic.NetworkConnectionStatus
3, // 5: meshtastic.EthernetConnectionStatus.status:type_name -> meshtastic.NetworkConnectionStatus
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_meshtastic_connection_status_proto_init() }
func file_meshtastic_connection_status_proto_init() {
if File_meshtastic_connection_status_proto != nil {
return
}
file_meshtastic_connection_status_proto_msgTypes[0].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_connection_status_proto_rawDesc), len(file_meshtastic_connection_status_proto_rawDesc)),
NumEnums: 0,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_connection_status_proto_goTypes,
DependencyIndexes: file_meshtastic_connection_status_proto_depIdxs,
MessageInfos: file_meshtastic_connection_status_proto_msgTypes,
}.Build()
File_meshtastic_connection_status_proto = out.File
file_meshtastic_connection_status_proto_goTypes = nil
file_meshtastic_connection_status_proto_depIdxs = nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,892 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/deviceonly.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Position with static location information only for NodeDBLite
type PositionLite struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The new preferred location encoding, multiply by 1e-7 to get degrees
// in floating point
LatitudeI int32 `protobuf:"fixed32,1,opt,name=latitude_i,json=latitudeI,proto3" json:"latitudeI,omitempty"`
// TODO: REPLACE
LongitudeI int32 `protobuf:"fixed32,2,opt,name=longitude_i,json=longitudeI,proto3" json:"longitudeI,omitempty"`
// In meters above MSL (but see issue #359)
Altitude int32 `protobuf:"varint,3,opt,name=altitude,proto3" json:"altitude,omitempty"`
// This is usually not sent over the mesh (to save space), but it is sent
// from the phone so that the local device can set its RTC If it is sent over
// the mesh (because there are devices on the mesh without GPS), it will only
// be sent by devices which has a hardware GPS clock.
// seconds since 1970
Time uint32 `protobuf:"fixed32,4,opt,name=time,proto3" json:"time,omitempty"`
// TODO: REPLACE
LocationSource Position_LocSource `protobuf:"varint,5,opt,name=location_source,json=locationSource,proto3,enum=meshtastic.Position_LocSource" json:"locationSource,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PositionLite) Reset() {
*x = PositionLite{}
mi := &file_meshtastic_deviceonly_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PositionLite) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PositionLite) ProtoMessage() {}
func (x *PositionLite) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_deviceonly_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PositionLite.ProtoReflect.Descriptor instead.
func (*PositionLite) Descriptor() ([]byte, []int) {
return file_meshtastic_deviceonly_proto_rawDescGZIP(), []int{0}
}
func (x *PositionLite) GetLatitudeI() int32 {
if x != nil {
return x.LatitudeI
}
return 0
}
func (x *PositionLite) GetLongitudeI() int32 {
if x != nil {
return x.LongitudeI
}
return 0
}
func (x *PositionLite) GetAltitude() int32 {
if x != nil {
return x.Altitude
}
return 0
}
func (x *PositionLite) GetTime() uint32 {
if x != nil {
return x.Time
}
return 0
}
func (x *PositionLite) GetLocationSource() Position_LocSource {
if x != nil {
return x.LocationSource
}
return Position_LOC_UNSET
}
type UserLite struct {
state protoimpl.MessageState `protogen:"open.v1"`
// This is the addr of the radio.
//
// Deprecated: Marked as deprecated in meshtastic/deviceonly.proto.
Macaddr []byte `protobuf:"bytes,1,opt,name=macaddr,proto3" json:"macaddr,omitempty"`
// A full name for this user, i.e. "Kevin Hester"
LongName string `protobuf:"bytes,2,opt,name=long_name,json=longName,proto3" json:"longName,omitempty"`
// A VERY short name, ideally two characters.
// Suitable for a tiny OLED screen
ShortName string `protobuf:"bytes,3,opt,name=short_name,json=shortName,proto3" json:"shortName,omitempty"`
// TBEAM, HELTEC, etc...
// Starting in 1.2.11 moved to hw_model enum in the NodeInfo object.
// Apps will still need the string here for older builds
// (so OTA update can find the right image), but if the enum is available it will be used instead.
HwModel HardwareModel `protobuf:"varint,4,opt,name=hw_model,json=hwModel,proto3,enum=meshtastic.HardwareModel" json:"hwModel,omitempty"`
// In some regions Ham radio operators have different bandwidth limitations than others.
// If this user is a licensed operator, set this flag.
// Also, "long_name" should be their licence number.
IsLicensed bool `protobuf:"varint,5,opt,name=is_licensed,json=isLicensed,proto3" json:"isLicensed,omitempty"`
// Indicates that the user's role in the mesh
Role Config_DeviceConfig_Role `protobuf:"varint,6,opt,name=role,proto3,enum=meshtastic.Config_DeviceConfig_Role" json:"role,omitempty"`
// The public key of the user's device.
// This is sent out to other nodes on the mesh to allow them to compute a shared secret key.
PublicKey []byte `protobuf:"bytes,7,opt,name=public_key,json=publicKey,proto3" json:"publicKey,omitempty"`
// Whether or not the node can be messaged
IsUnmessagable *bool `protobuf:"varint,9,opt,name=is_unmessagable,json=isUnmessagable,proto3,oneof" json:"isUnmessagable,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UserLite) Reset() {
*x = UserLite{}
mi := &file_meshtastic_deviceonly_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UserLite) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserLite) ProtoMessage() {}
func (x *UserLite) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_deviceonly_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserLite.ProtoReflect.Descriptor instead.
func (*UserLite) Descriptor() ([]byte, []int) {
return file_meshtastic_deviceonly_proto_rawDescGZIP(), []int{1}
}
// Deprecated: Marked as deprecated in meshtastic/deviceonly.proto.
func (x *UserLite) GetMacaddr() []byte {
if x != nil {
return x.Macaddr
}
return nil
}
func (x *UserLite) GetLongName() string {
if x != nil {
return x.LongName
}
return ""
}
func (x *UserLite) GetShortName() string {
if x != nil {
return x.ShortName
}
return ""
}
func (x *UserLite) GetHwModel() HardwareModel {
if x != nil {
return x.HwModel
}
return HardwareModel_UNSET
}
func (x *UserLite) GetIsLicensed() bool {
if x != nil {
return x.IsLicensed
}
return false
}
func (x *UserLite) GetRole() Config_DeviceConfig_Role {
if x != nil {
return x.Role
}
return Config_DeviceConfig_CLIENT
}
func (x *UserLite) GetPublicKey() []byte {
if x != nil {
return x.PublicKey
}
return nil
}
func (x *UserLite) GetIsUnmessagable() bool {
if x != nil && x.IsUnmessagable != nil {
return *x.IsUnmessagable
}
return false
}
type NodeInfoLite struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The node number
Num uint32 `protobuf:"varint,1,opt,name=num,proto3" json:"num,omitempty"`
// The user info for this node
User *UserLite `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"`
// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true.
// Position.time now indicates the last time we received a POSITION from that node.
Position *PositionLite `protobuf:"bytes,3,opt,name=position,proto3" json:"position,omitempty"`
// Returns the Signal-to-noise ratio (SNR) of the last received message,
// as measured by the receiver. Return SNR of the last received message in dB
Snr float32 `protobuf:"fixed32,4,opt,name=snr,proto3" json:"snr,omitempty"`
// Set to indicate the last time we received a packet from this node
LastHeard uint32 `protobuf:"fixed32,5,opt,name=last_heard,json=lastHeard,proto3" json:"lastHeard,omitempty"`
// The latest device metrics for the node.
DeviceMetrics *DeviceMetrics `protobuf:"bytes,6,opt,name=device_metrics,json=deviceMetrics,proto3" json:"deviceMetrics,omitempty"`
// local channel index we heard that node on. Only populated if its not the default channel.
Channel uint32 `protobuf:"varint,7,opt,name=channel,proto3" json:"channel,omitempty"`
// True if we witnessed the node over MQTT instead of LoRA transport
ViaMqtt bool `protobuf:"varint,8,opt,name=via_mqtt,json=viaMqtt,proto3" json:"viaMqtt,omitempty"`
// Number of hops away from us this node is (0 if direct neighbor)
HopsAway *uint32 `protobuf:"varint,9,opt,name=hops_away,json=hopsAway,proto3,oneof" json:"hopsAway,omitempty"`
// True if node is in our favorites list
// Persists between NodeDB internal clean ups
IsFavorite bool `protobuf:"varint,10,opt,name=is_favorite,json=isFavorite,proto3" json:"isFavorite,omitempty"`
// True if node is in our ignored list
// Persists between NodeDB internal clean ups
IsIgnored bool `protobuf:"varint,11,opt,name=is_ignored,json=isIgnored,proto3" json:"isIgnored,omitempty"`
// Last byte of the node number of the node that should be used as the next hop to reach this node.
NextHop uint32 `protobuf:"varint,12,opt,name=next_hop,json=nextHop,proto3" json:"nextHop,omitempty"`
// Bitfield for storing booleans.
// LSB 0 is_key_manually_verified
// LSB 1 is_muted
Bitfield uint32 `protobuf:"varint,13,opt,name=bitfield,proto3" json:"bitfield,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *NodeInfoLite) Reset() {
*x = NodeInfoLite{}
mi := &file_meshtastic_deviceonly_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *NodeInfoLite) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeInfoLite) ProtoMessage() {}
func (x *NodeInfoLite) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_deviceonly_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeInfoLite.ProtoReflect.Descriptor instead.
func (*NodeInfoLite) Descriptor() ([]byte, []int) {
return file_meshtastic_deviceonly_proto_rawDescGZIP(), []int{2}
}
func (x *NodeInfoLite) GetNum() uint32 {
if x != nil {
return x.Num
}
return 0
}
func (x *NodeInfoLite) GetUser() *UserLite {
if x != nil {
return x.User
}
return nil
}
func (x *NodeInfoLite) GetPosition() *PositionLite {
if x != nil {
return x.Position
}
return nil
}
func (x *NodeInfoLite) GetSnr() float32 {
if x != nil {
return x.Snr
}
return 0
}
func (x *NodeInfoLite) GetLastHeard() uint32 {
if x != nil {
return x.LastHeard
}
return 0
}
func (x *NodeInfoLite) GetDeviceMetrics() *DeviceMetrics {
if x != nil {
return x.DeviceMetrics
}
return nil
}
func (x *NodeInfoLite) GetChannel() uint32 {
if x != nil {
return x.Channel
}
return 0
}
func (x *NodeInfoLite) GetViaMqtt() bool {
if x != nil {
return x.ViaMqtt
}
return false
}
func (x *NodeInfoLite) GetHopsAway() uint32 {
if x != nil && x.HopsAway != nil {
return *x.HopsAway
}
return 0
}
func (x *NodeInfoLite) GetIsFavorite() bool {
if x != nil {
return x.IsFavorite
}
return false
}
func (x *NodeInfoLite) GetIsIgnored() bool {
if x != nil {
return x.IsIgnored
}
return false
}
func (x *NodeInfoLite) GetNextHop() uint32 {
if x != nil {
return x.NextHop
}
return 0
}
func (x *NodeInfoLite) GetBitfield() uint32 {
if x != nil {
return x.Bitfield
}
return 0
}
// This message is never sent over the wire, but it is used for serializing DB
// state to flash in the device code
// FIXME, since we write this each time we enter deep sleep (and have infinite
// flash) it would be better to use some sort of append only data structure for
// the receive queue and use the preferences store for the other stuff
type DeviceState struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Read only settings/info about this node
MyNode *MyNodeInfo `protobuf:"bytes,2,opt,name=my_node,json=myNode,proto3" json:"myNode,omitempty"`
// My owner info
Owner *User `protobuf:"bytes,3,opt,name=owner,proto3" json:"owner,omitempty"`
// Received packets saved for delivery to the phone
ReceiveQueue []*MeshPacket `protobuf:"bytes,5,rep,name=receive_queue,json=receiveQueue,proto3" json:"receiveQueue,omitempty"`
// A version integer used to invalidate old save files when we make
// incompatible changes This integer is set at build time and is private to
// NodeDB.cpp in the device code.
Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty"`
// We keep the last received text message (only) stored in the device flash,
// so we can show it on the screen.
// Might be null
RxTextMessage *MeshPacket `protobuf:"bytes,7,opt,name=rx_text_message,json=rxTextMessage,proto3" json:"rxTextMessage,omitempty"`
// Used only during development.
// Indicates developer is testing and changes should never be saved to flash.
// Deprecated in 2.3.1
//
// Deprecated: Marked as deprecated in meshtastic/deviceonly.proto.
NoSave bool `protobuf:"varint,9,opt,name=no_save,json=noSave,proto3" json:"noSave,omitempty"`
// Previously used to manage GPS factory resets.
// Deprecated in 2.5.23
//
// Deprecated: Marked as deprecated in meshtastic/deviceonly.proto.
DidGpsReset bool `protobuf:"varint,11,opt,name=did_gps_reset,json=didGpsReset,proto3" json:"didGpsReset,omitempty"`
// We keep the last received waypoint stored in the device flash,
// so we can show it on the screen.
// Might be null
RxWaypoint *MeshPacket `protobuf:"bytes,12,opt,name=rx_waypoint,json=rxWaypoint,proto3" json:"rxWaypoint,omitempty"`
// The mesh's nodes with their available gpio pins for RemoteHardware module
NodeRemoteHardwarePins []*NodeRemoteHardwarePin `protobuf:"bytes,13,rep,name=node_remote_hardware_pins,json=nodeRemoteHardwarePins,proto3" json:"nodeRemoteHardwarePins,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceState) Reset() {
*x = DeviceState{}
mi := &file_meshtastic_deviceonly_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeviceState) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeviceState) ProtoMessage() {}
func (x *DeviceState) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_deviceonly_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeviceState.ProtoReflect.Descriptor instead.
func (*DeviceState) Descriptor() ([]byte, []int) {
return file_meshtastic_deviceonly_proto_rawDescGZIP(), []int{3}
}
func (x *DeviceState) GetMyNode() *MyNodeInfo {
if x != nil {
return x.MyNode
}
return nil
}
func (x *DeviceState) GetOwner() *User {
if x != nil {
return x.Owner
}
return nil
}
func (x *DeviceState) GetReceiveQueue() []*MeshPacket {
if x != nil {
return x.ReceiveQueue
}
return nil
}
func (x *DeviceState) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *DeviceState) GetRxTextMessage() *MeshPacket {
if x != nil {
return x.RxTextMessage
}
return nil
}
// Deprecated: Marked as deprecated in meshtastic/deviceonly.proto.
func (x *DeviceState) GetNoSave() bool {
if x != nil {
return x.NoSave
}
return false
}
// Deprecated: Marked as deprecated in meshtastic/deviceonly.proto.
func (x *DeviceState) GetDidGpsReset() bool {
if x != nil {
return x.DidGpsReset
}
return false
}
func (x *DeviceState) GetRxWaypoint() *MeshPacket {
if x != nil {
return x.RxWaypoint
}
return nil
}
func (x *DeviceState) GetNodeRemoteHardwarePins() []*NodeRemoteHardwarePin {
if x != nil {
return x.NodeRemoteHardwarePins
}
return nil
}
type NodeDatabase struct {
state protoimpl.MessageState `protogen:"open.v1"`
// A version integer used to invalidate old save files when we make
// incompatible changes This integer is set at build time and is private to
// NodeDB.cpp in the device code.
Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
// New lite version of NodeDB to decrease memory footprint
Nodes []*NodeInfoLite `protobuf:"bytes,2,rep,name=nodes,proto3" json:"nodes,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *NodeDatabase) Reset() {
*x = NodeDatabase{}
mi := &file_meshtastic_deviceonly_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *NodeDatabase) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeDatabase) ProtoMessage() {}
func (x *NodeDatabase) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_deviceonly_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeDatabase.ProtoReflect.Descriptor instead.
func (*NodeDatabase) Descriptor() ([]byte, []int) {
return file_meshtastic_deviceonly_proto_rawDescGZIP(), []int{4}
}
func (x *NodeDatabase) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *NodeDatabase) GetNodes() []*NodeInfoLite {
if x != nil {
return x.Nodes
}
return nil
}
// The on-disk saved channels
type ChannelFile struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The channels our node knows about
Channels []*Channel `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"`
// A version integer used to invalidate old save files when we make
// incompatible changes This integer is set at build time and is private to
// NodeDB.cpp in the device code.
Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ChannelFile) Reset() {
*x = ChannelFile{}
mi := &file_meshtastic_deviceonly_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ChannelFile) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelFile) ProtoMessage() {}
func (x *ChannelFile) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_deviceonly_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelFile.ProtoReflect.Descriptor instead.
func (*ChannelFile) Descriptor() ([]byte, []int) {
return file_meshtastic_deviceonly_proto_rawDescGZIP(), []int{5}
}
func (x *ChannelFile) GetChannels() []*Channel {
if x != nil {
return x.Channels
}
return nil
}
func (x *ChannelFile) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
// The on-disk backup of the node's preferences
type BackupPreferences struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The version of the backup
Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
// The timestamp of the backup (if node has time)
Timestamp uint32 `protobuf:"fixed32,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// The node's configuration
Config *LocalConfig `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"`
// The node's module configuration
ModuleConfig *LocalModuleConfig `protobuf:"bytes,4,opt,name=module_config,json=moduleConfig,proto3" json:"moduleConfig,omitempty"`
// The node's channels
Channels *ChannelFile `protobuf:"bytes,5,opt,name=channels,proto3" json:"channels,omitempty"`
// The node's user (owner) information
Owner *User `protobuf:"bytes,6,opt,name=owner,proto3" json:"owner,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BackupPreferences) Reset() {
*x = BackupPreferences{}
mi := &file_meshtastic_deviceonly_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BackupPreferences) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BackupPreferences) ProtoMessage() {}
func (x *BackupPreferences) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_deviceonly_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BackupPreferences.ProtoReflect.Descriptor instead.
func (*BackupPreferences) Descriptor() ([]byte, []int) {
return file_meshtastic_deviceonly_proto_rawDescGZIP(), []int{6}
}
func (x *BackupPreferences) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *BackupPreferences) GetTimestamp() uint32 {
if x != nil {
return x.Timestamp
}
return 0
}
func (x *BackupPreferences) GetConfig() *LocalConfig {
if x != nil {
return x.Config
}
return nil
}
func (x *BackupPreferences) GetModuleConfig() *LocalModuleConfig {
if x != nil {
return x.ModuleConfig
}
return nil
}
func (x *BackupPreferences) GetChannels() *ChannelFile {
if x != nil {
return x.Channels
}
return nil
}
func (x *BackupPreferences) GetOwner() *User {
if x != nil {
return x.Owner
}
return nil
}
var File_meshtastic_deviceonly_proto protoreflect.FileDescriptor
const file_meshtastic_deviceonly_proto_rawDesc = "" +
"\n" +
"\x1bmeshtastic/deviceonly.proto\x12\n" +
"meshtastic\x1a\x18meshtastic/channel.proto\x1a\x17meshtastic/config.proto\x1a\x1ameshtastic/localonly.proto\x1a\x15meshtastic/mesh.proto\x1a\x1ameshtastic/telemetry.proto\x1a\fnanopb.proto\"\xc7\x01\n" +
"\fPositionLite\x12\x1d\n" +
"\n" +
"latitude_i\x18\x01 \x01(\x0fR\tlatitudeI\x12\x1f\n" +
"\vlongitude_i\x18\x02 \x01(\x0fR\n" +
"longitudeI\x12\x1a\n" +
"\baltitude\x18\x03 \x01(\x05R\baltitude\x12\x12\n" +
"\x04time\x18\x04 \x01(\aR\x04time\x12G\n" +
"\x0flocation_source\x18\x05 \x01(\x0e2\x1e.meshtastic.Position.LocSourceR\x0elocationSource\"\xd6\x02\n" +
"\bUserLite\x12\x1c\n" +
"\amacaddr\x18\x01 \x01(\fB\x02\x18\x01R\amacaddr\x12\x1b\n" +
"\tlong_name\x18\x02 \x01(\tR\blongName\x12\x1d\n" +
"\n" +
"short_name\x18\x03 \x01(\tR\tshortName\x124\n" +
"\bhw_model\x18\x04 \x01(\x0e2\x19.meshtastic.HardwareModelR\ahwModel\x12\x1f\n" +
"\vis_licensed\x18\x05 \x01(\bR\n" +
"isLicensed\x128\n" +
"\x04role\x18\x06 \x01(\x0e2$.meshtastic.Config.DeviceConfig.RoleR\x04role\x12\x1d\n" +
"\n" +
"public_key\x18\a \x01(\fR\tpublicKey\x12,\n" +
"\x0fis_unmessagable\x18\t \x01(\bH\x00R\x0eisUnmessagable\x88\x01\x01B\x12\n" +
"\x10_is_unmessagable\"\xcf\x03\n" +
"\fNodeInfoLite\x12\x10\n" +
"\x03num\x18\x01 \x01(\rR\x03num\x12(\n" +
"\x04user\x18\x02 \x01(\v2\x14.meshtastic.UserLiteR\x04user\x124\n" +
"\bposition\x18\x03 \x01(\v2\x18.meshtastic.PositionLiteR\bposition\x12\x10\n" +
"\x03snr\x18\x04 \x01(\x02R\x03snr\x12\x1d\n" +
"\n" +
"last_heard\x18\x05 \x01(\aR\tlastHeard\x12@\n" +
"\x0edevice_metrics\x18\x06 \x01(\v2\x19.meshtastic.DeviceMetricsR\rdeviceMetrics\x12\x18\n" +
"\achannel\x18\a \x01(\rR\achannel\x12\x19\n" +
"\bvia_mqtt\x18\b \x01(\bR\aviaMqtt\x12 \n" +
"\thops_away\x18\t \x01(\rH\x00R\bhopsAway\x88\x01\x01\x12\x1f\n" +
"\vis_favorite\x18\n" +
" \x01(\bR\n" +
"isFavorite\x12\x1d\n" +
"\n" +
"is_ignored\x18\v \x01(\bR\tisIgnored\x12\x19\n" +
"\bnext_hop\x18\f \x01(\rR\anextHop\x12\x1a\n" +
"\bbitfield\x18\r \x01(\rR\bbitfieldB\f\n" +
"\n" +
"_hops_away\"\xd9\x03\n" +
"\vDeviceState\x12/\n" +
"\amy_node\x18\x02 \x01(\v2\x16.meshtastic.MyNodeInfoR\x06myNode\x12&\n" +
"\x05owner\x18\x03 \x01(\v2\x10.meshtastic.UserR\x05owner\x12;\n" +
"\rreceive_queue\x18\x05 \x03(\v2\x16.meshtastic.MeshPacketR\freceiveQueue\x12\x18\n" +
"\aversion\x18\b \x01(\rR\aversion\x12>\n" +
"\x0frx_text_message\x18\a \x01(\v2\x16.meshtastic.MeshPacketR\rrxTextMessage\x12\x1b\n" +
"\ano_save\x18\t \x01(\bB\x02\x18\x01R\x06noSave\x12&\n" +
"\rdid_gps_reset\x18\v \x01(\bB\x02\x18\x01R\vdidGpsReset\x127\n" +
"\vrx_waypoint\x18\f \x01(\v2\x16.meshtastic.MeshPacketR\n" +
"rxWaypoint\x12\\\n" +
"\x19node_remote_hardware_pins\x18\r \x03(\v2!.meshtastic.NodeRemoteHardwarePinR\x16nodeRemoteHardwarePins\"\x84\x01\n" +
"\fNodeDatabase\x12\x18\n" +
"\aversion\x18\x01 \x01(\rR\aversion\x12Z\n" +
"\x05nodes\x18\x02 \x03(\v2\x18.meshtastic.NodeInfoLiteB*\x92?'\x92\x01$std::vector<meshtastic_NodeInfoLite>R\x05nodes\"X\n" +
"\vChannelFile\x12/\n" +
"\bchannels\x18\x01 \x03(\v2\x13.meshtastic.ChannelR\bchannels\x12\x18\n" +
"\aversion\x18\x02 \x01(\rR\aversion\"\x9d\x02\n" +
"\x11BackupPreferences\x12\x18\n" +
"\aversion\x18\x01 \x01(\rR\aversion\x12\x1c\n" +
"\ttimestamp\x18\x02 \x01(\aR\ttimestamp\x12/\n" +
"\x06config\x18\x03 \x01(\v2\x17.meshtastic.LocalConfigR\x06config\x12B\n" +
"\rmodule_config\x18\x04 \x01(\v2\x1d.meshtastic.LocalModuleConfigR\fmoduleConfig\x123\n" +
"\bchannels\x18\x05 \x01(\v2\x17.meshtastic.ChannelFileR\bchannels\x12&\n" +
"\x05owner\x18\x06 \x01(\v2\x10.meshtastic.UserR\x05ownerBn\x92?\v\xc2\x01\b<vector>\n" +
"\x14org.meshtastic.protoB\n" +
"DeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_deviceonly_proto_rawDescOnce sync.Once
file_meshtastic_deviceonly_proto_rawDescData []byte
)
func file_meshtastic_deviceonly_proto_rawDescGZIP() []byte {
file_meshtastic_deviceonly_proto_rawDescOnce.Do(func() {
file_meshtastic_deviceonly_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_deviceonly_proto_rawDesc), len(file_meshtastic_deviceonly_proto_rawDesc)))
})
return file_meshtastic_deviceonly_proto_rawDescData
}
var file_meshtastic_deviceonly_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_meshtastic_deviceonly_proto_goTypes = []any{
(*PositionLite)(nil), // 0: meshtastic.PositionLite
(*UserLite)(nil), // 1: meshtastic.UserLite
(*NodeInfoLite)(nil), // 2: meshtastic.NodeInfoLite
(*DeviceState)(nil), // 3: meshtastic.DeviceState
(*NodeDatabase)(nil), // 4: meshtastic.NodeDatabase
(*ChannelFile)(nil), // 5: meshtastic.ChannelFile
(*BackupPreferences)(nil), // 6: meshtastic.BackupPreferences
(Position_LocSource)(0), // 7: meshtastic.Position.LocSource
(HardwareModel)(0), // 8: meshtastic.HardwareModel
(Config_DeviceConfig_Role)(0), // 9: meshtastic.Config.DeviceConfig.Role
(*DeviceMetrics)(nil), // 10: meshtastic.DeviceMetrics
(*MyNodeInfo)(nil), // 11: meshtastic.MyNodeInfo
(*User)(nil), // 12: meshtastic.User
(*MeshPacket)(nil), // 13: meshtastic.MeshPacket
(*NodeRemoteHardwarePin)(nil), // 14: meshtastic.NodeRemoteHardwarePin
(*Channel)(nil), // 15: meshtastic.Channel
(*LocalConfig)(nil), // 16: meshtastic.LocalConfig
(*LocalModuleConfig)(nil), // 17: meshtastic.LocalModuleConfig
}
var file_meshtastic_deviceonly_proto_depIdxs = []int32{
7, // 0: meshtastic.PositionLite.location_source:type_name -> meshtastic.Position.LocSource
8, // 1: meshtastic.UserLite.hw_model:type_name -> meshtastic.HardwareModel
9, // 2: meshtastic.UserLite.role:type_name -> meshtastic.Config.DeviceConfig.Role
1, // 3: meshtastic.NodeInfoLite.user:type_name -> meshtastic.UserLite
0, // 4: meshtastic.NodeInfoLite.position:type_name -> meshtastic.PositionLite
10, // 5: meshtastic.NodeInfoLite.device_metrics:type_name -> meshtastic.DeviceMetrics
11, // 6: meshtastic.DeviceState.my_node:type_name -> meshtastic.MyNodeInfo
12, // 7: meshtastic.DeviceState.owner:type_name -> meshtastic.User
13, // 8: meshtastic.DeviceState.receive_queue:type_name -> meshtastic.MeshPacket
13, // 9: meshtastic.DeviceState.rx_text_message:type_name -> meshtastic.MeshPacket
13, // 10: meshtastic.DeviceState.rx_waypoint:type_name -> meshtastic.MeshPacket
14, // 11: meshtastic.DeviceState.node_remote_hardware_pins:type_name -> meshtastic.NodeRemoteHardwarePin
2, // 12: meshtastic.NodeDatabase.nodes:type_name -> meshtastic.NodeInfoLite
15, // 13: meshtastic.ChannelFile.channels:type_name -> meshtastic.Channel
16, // 14: meshtastic.BackupPreferences.config:type_name -> meshtastic.LocalConfig
17, // 15: meshtastic.BackupPreferences.module_config:type_name -> meshtastic.LocalModuleConfig
5, // 16: meshtastic.BackupPreferences.channels:type_name -> meshtastic.ChannelFile
12, // 17: meshtastic.BackupPreferences.owner:type_name -> meshtastic.User
18, // [18:18] is the sub-list for method output_type
18, // [18:18] is the sub-list for method input_type
18, // [18:18] is the sub-list for extension type_name
18, // [18:18] is the sub-list for extension extendee
0, // [0:18] is the sub-list for field type_name
}
func init() { file_meshtastic_deviceonly_proto_init() }
func file_meshtastic_deviceonly_proto_init() {
if File_meshtastic_deviceonly_proto != nil {
return
}
file_meshtastic_channel_proto_init()
file_meshtastic_config_proto_init()
file_meshtastic_localonly_proto_init()
file_meshtastic_mesh_proto_init()
file_meshtastic_telemetry_proto_init()
file_nanopb_proto_init()
file_meshtastic_deviceonly_proto_msgTypes[1].OneofWrappers = []any{}
file_meshtastic_deviceonly_proto_msgTypes[2].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_deviceonly_proto_rawDesc), len(file_meshtastic_deviceonly_proto_rawDesc)),
NumEnums: 0,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_deviceonly_proto_goTypes,
DependencyIndexes: file_meshtastic_deviceonly_proto_depIdxs,
MessageInfos: file_meshtastic_deviceonly_proto_msgTypes,
}.Build()
File_meshtastic_deviceonly_proto = out.File
file_meshtastic_deviceonly_proto_goTypes = nil
file_meshtastic_deviceonly_proto_depIdxs = nil
}

View File

@@ -0,0 +1,372 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/interdevice.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type MessageType int32
const (
MessageType_ACK MessageType = 0
MessageType_COLLECT_INTERVAL MessageType = 160 // in ms
MessageType_BEEP_ON MessageType = 161 // duration ms
MessageType_BEEP_OFF MessageType = 162 // cancel prematurely
MessageType_SHUTDOWN MessageType = 163
MessageType_POWER_ON MessageType = 164
MessageType_SCD41_TEMP MessageType = 176
MessageType_SCD41_HUMIDITY MessageType = 177
MessageType_SCD41_CO2 MessageType = 178
MessageType_AHT20_TEMP MessageType = 179
MessageType_AHT20_HUMIDITY MessageType = 180
MessageType_TVOC_INDEX MessageType = 181
)
// Enum value maps for MessageType.
var (
MessageType_name = map[int32]string{
0: "ACK",
160: "COLLECT_INTERVAL",
161: "BEEP_ON",
162: "BEEP_OFF",
163: "SHUTDOWN",
164: "POWER_ON",
176: "SCD41_TEMP",
177: "SCD41_HUMIDITY",
178: "SCD41_CO2",
179: "AHT20_TEMP",
180: "AHT20_HUMIDITY",
181: "TVOC_INDEX",
}
MessageType_value = map[string]int32{
"ACK": 0,
"COLLECT_INTERVAL": 160,
"BEEP_ON": 161,
"BEEP_OFF": 162,
"SHUTDOWN": 163,
"POWER_ON": 164,
"SCD41_TEMP": 176,
"SCD41_HUMIDITY": 177,
"SCD41_CO2": 178,
"AHT20_TEMP": 179,
"AHT20_HUMIDITY": 180,
"TVOC_INDEX": 181,
}
)
func (x MessageType) Enum() *MessageType {
p := new(MessageType)
*p = x
return p
}
func (x MessageType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MessageType) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_interdevice_proto_enumTypes[0].Descriptor()
}
func (MessageType) Type() protoreflect.EnumType {
return &file_meshtastic_interdevice_proto_enumTypes[0]
}
func (x MessageType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MessageType.Descriptor instead.
func (MessageType) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_interdevice_proto_rawDescGZIP(), []int{0}
}
type SensorData struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The message type
Type MessageType `protobuf:"varint,1,opt,name=type,proto3,enum=meshtastic.MessageType" json:"type,omitempty"`
// The sensor data, either as a float or an uint32
//
// Types that are valid to be assigned to Data:
//
// *SensorData_FloatValue
// *SensorData_Uint32Value
Data isSensorData_Data `protobuf_oneof:"data"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SensorData) Reset() {
*x = SensorData{}
mi := &file_meshtastic_interdevice_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SensorData) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SensorData) ProtoMessage() {}
func (x *SensorData) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_interdevice_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SensorData.ProtoReflect.Descriptor instead.
func (*SensorData) Descriptor() ([]byte, []int) {
return file_meshtastic_interdevice_proto_rawDescGZIP(), []int{0}
}
func (x *SensorData) GetType() MessageType {
if x != nil {
return x.Type
}
return MessageType_ACK
}
func (x *SensorData) GetData() isSensorData_Data {
if x != nil {
return x.Data
}
return nil
}
func (x *SensorData) GetFloatValue() float32 {
if x != nil {
if x, ok := x.Data.(*SensorData_FloatValue); ok {
return x.FloatValue
}
}
return 0
}
func (x *SensorData) GetUint32Value() uint32 {
if x != nil {
if x, ok := x.Data.(*SensorData_Uint32Value); ok {
return x.Uint32Value
}
}
return 0
}
type isSensorData_Data interface {
isSensorData_Data()
}
type SensorData_FloatValue struct {
FloatValue float32 `protobuf:"fixed32,2,opt,name=float_value,json=floatValue,proto3,oneof"`
}
type SensorData_Uint32Value struct {
Uint32Value uint32 `protobuf:"varint,3,opt,name=uint32_value,json=uint32Value,proto3,oneof"`
}
func (*SensorData_FloatValue) isSensorData_Data() {}
func (*SensorData_Uint32Value) isSensorData_Data() {}
type InterdeviceMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The message data
//
// Types that are valid to be assigned to Data:
//
// *InterdeviceMessage_Nmea
// *InterdeviceMessage_Sensor
Data isInterdeviceMessage_Data `protobuf_oneof:"data"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *InterdeviceMessage) Reset() {
*x = InterdeviceMessage{}
mi := &file_meshtastic_interdevice_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *InterdeviceMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InterdeviceMessage) ProtoMessage() {}
func (x *InterdeviceMessage) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_interdevice_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InterdeviceMessage.ProtoReflect.Descriptor instead.
func (*InterdeviceMessage) Descriptor() ([]byte, []int) {
return file_meshtastic_interdevice_proto_rawDescGZIP(), []int{1}
}
func (x *InterdeviceMessage) GetData() isInterdeviceMessage_Data {
if x != nil {
return x.Data
}
return nil
}
func (x *InterdeviceMessage) GetNmea() string {
if x != nil {
if x, ok := x.Data.(*InterdeviceMessage_Nmea); ok {
return x.Nmea
}
}
return ""
}
func (x *InterdeviceMessage) GetSensor() *SensorData {
if x != nil {
if x, ok := x.Data.(*InterdeviceMessage_Sensor); ok {
return x.Sensor
}
}
return nil
}
type isInterdeviceMessage_Data interface {
isInterdeviceMessage_Data()
}
type InterdeviceMessage_Nmea struct {
Nmea string `protobuf:"bytes,1,opt,name=nmea,proto3,oneof"`
}
type InterdeviceMessage_Sensor struct {
Sensor *SensorData `protobuf:"bytes,2,opt,name=sensor,proto3,oneof"`
}
func (*InterdeviceMessage_Nmea) isInterdeviceMessage_Data() {}
func (*InterdeviceMessage_Sensor) isInterdeviceMessage_Data() {}
var File_meshtastic_interdevice_proto protoreflect.FileDescriptor
const file_meshtastic_interdevice_proto_rawDesc = "" +
"\n" +
"\x1cmeshtastic/interdevice.proto\x12\n" +
"meshtastic\"\x89\x01\n" +
"\n" +
"SensorData\x12+\n" +
"\x04type\x18\x01 \x01(\x0e2\x17.meshtastic.MessageTypeR\x04type\x12!\n" +
"\vfloat_value\x18\x02 \x01(\x02H\x00R\n" +
"floatValue\x12#\n" +
"\fuint32_value\x18\x03 \x01(\rH\x00R\vuint32ValueB\x06\n" +
"\x04data\"d\n" +
"\x12InterdeviceMessage\x12\x14\n" +
"\x04nmea\x18\x01 \x01(\tH\x00R\x04nmea\x120\n" +
"\x06sensor\x18\x02 \x01(\v2\x16.meshtastic.SensorDataH\x00R\x06sensorB\x06\n" +
"\x04data*\xd5\x01\n" +
"\vMessageType\x12\a\n" +
"\x03ACK\x10\x00\x12\x15\n" +
"\x10COLLECT_INTERVAL\x10\xa0\x01\x12\f\n" +
"\aBEEP_ON\x10\xa1\x01\x12\r\n" +
"\bBEEP_OFF\x10\xa2\x01\x12\r\n" +
"\bSHUTDOWN\x10\xa3\x01\x12\r\n" +
"\bPOWER_ON\x10\xa4\x01\x12\x0f\n" +
"\n" +
"SCD41_TEMP\x10\xb0\x01\x12\x13\n" +
"\x0eSCD41_HUMIDITY\x10\xb1\x01\x12\x0e\n" +
"\tSCD41_CO2\x10\xb2\x01\x12\x0f\n" +
"\n" +
"AHT20_TEMP\x10\xb3\x01\x12\x13\n" +
"\x0eAHT20_HUMIDITY\x10\xb4\x01\x12\x0f\n" +
"\n" +
"TVOC_INDEX\x10\xb5\x01Bg\n" +
"\x14org.meshtastic.protoB\x11InterdeviceProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_interdevice_proto_rawDescOnce sync.Once
file_meshtastic_interdevice_proto_rawDescData []byte
)
func file_meshtastic_interdevice_proto_rawDescGZIP() []byte {
file_meshtastic_interdevice_proto_rawDescOnce.Do(func() {
file_meshtastic_interdevice_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_interdevice_proto_rawDesc), len(file_meshtastic_interdevice_proto_rawDesc)))
})
return file_meshtastic_interdevice_proto_rawDescData
}
var file_meshtastic_interdevice_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_meshtastic_interdevice_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_meshtastic_interdevice_proto_goTypes = []any{
(MessageType)(0), // 0: meshtastic.MessageType
(*SensorData)(nil), // 1: meshtastic.SensorData
(*InterdeviceMessage)(nil), // 2: meshtastic.InterdeviceMessage
}
var file_meshtastic_interdevice_proto_depIdxs = []int32{
0, // 0: meshtastic.SensorData.type:type_name -> meshtastic.MessageType
1, // 1: meshtastic.InterdeviceMessage.sensor:type_name -> meshtastic.SensorData
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_meshtastic_interdevice_proto_init() }
func file_meshtastic_interdevice_proto_init() {
if File_meshtastic_interdevice_proto != nil {
return
}
file_meshtastic_interdevice_proto_msgTypes[0].OneofWrappers = []any{
(*SensorData_FloatValue)(nil),
(*SensorData_Uint32Value)(nil),
}
file_meshtastic_interdevice_proto_msgTypes[1].OneofWrappers = []any{
(*InterdeviceMessage_Nmea)(nil),
(*InterdeviceMessage_Sensor)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_interdevice_proto_rawDesc), len(file_meshtastic_interdevice_proto_rawDesc)),
NumEnums: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_interdevice_proto_goTypes,
DependencyIndexes: file_meshtastic_interdevice_proto_depIdxs,
EnumInfos: file_meshtastic_interdevice_proto_enumTypes,
MessageInfos: file_meshtastic_interdevice_proto_msgTypes,
}.Build()
File_meshtastic_interdevice_proto = out.File
file_meshtastic_interdevice_proto_goTypes = nil
file_meshtastic_interdevice_proto_depIdxs = nil
}

View File

@@ -0,0 +1,472 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/localonly.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type LocalConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The part of the config that is specific to the Device
Device *Config_DeviceConfig `protobuf:"bytes,1,opt,name=device,proto3" json:"device,omitempty"`
// The part of the config that is specific to the GPS Position
Position *Config_PositionConfig `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"`
// The part of the config that is specific to the Power settings
Power *Config_PowerConfig `protobuf:"bytes,3,opt,name=power,proto3" json:"power,omitempty"`
// The part of the config that is specific to the Wifi Settings
Network *Config_NetworkConfig `protobuf:"bytes,4,opt,name=network,proto3" json:"network,omitempty"`
// The part of the config that is specific to the Display
Display *Config_DisplayConfig `protobuf:"bytes,5,opt,name=display,proto3" json:"display,omitempty"`
// The part of the config that is specific to the Lora Radio
Lora *Config_LoRaConfig `protobuf:"bytes,6,opt,name=lora,proto3" json:"lora,omitempty"`
// The part of the config that is specific to the Bluetooth settings
Bluetooth *Config_BluetoothConfig `protobuf:"bytes,7,opt,name=bluetooth,proto3" json:"bluetooth,omitempty"`
// A version integer used to invalidate old save files when we make
// incompatible changes This integer is set at build time and is private to
// NodeDB.cpp in the device code.
Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty"`
// The part of the config that is specific to Security settings
Security *Config_SecurityConfig `protobuf:"bytes,9,opt,name=security,proto3" json:"security,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *LocalConfig) Reset() {
*x = LocalConfig{}
mi := &file_meshtastic_localonly_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *LocalConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LocalConfig) ProtoMessage() {}
func (x *LocalConfig) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_localonly_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LocalConfig.ProtoReflect.Descriptor instead.
func (*LocalConfig) Descriptor() ([]byte, []int) {
return file_meshtastic_localonly_proto_rawDescGZIP(), []int{0}
}
func (x *LocalConfig) GetDevice() *Config_DeviceConfig {
if x != nil {
return x.Device
}
return nil
}
func (x *LocalConfig) GetPosition() *Config_PositionConfig {
if x != nil {
return x.Position
}
return nil
}
func (x *LocalConfig) GetPower() *Config_PowerConfig {
if x != nil {
return x.Power
}
return nil
}
func (x *LocalConfig) GetNetwork() *Config_NetworkConfig {
if x != nil {
return x.Network
}
return nil
}
func (x *LocalConfig) GetDisplay() *Config_DisplayConfig {
if x != nil {
return x.Display
}
return nil
}
func (x *LocalConfig) GetLora() *Config_LoRaConfig {
if x != nil {
return x.Lora
}
return nil
}
func (x *LocalConfig) GetBluetooth() *Config_BluetoothConfig {
if x != nil {
return x.Bluetooth
}
return nil
}
func (x *LocalConfig) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
func (x *LocalConfig) GetSecurity() *Config_SecurityConfig {
if x != nil {
return x.Security
}
return nil
}
type LocalModuleConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The part of the config that is specific to the MQTT module
Mqtt *ModuleConfig_MQTTConfig `protobuf:"bytes,1,opt,name=mqtt,proto3" json:"mqtt,omitempty"`
// The part of the config that is specific to the Serial module
Serial *ModuleConfig_SerialConfig `protobuf:"bytes,2,opt,name=serial,proto3" json:"serial,omitempty"`
// The part of the config that is specific to the ExternalNotification module
ExternalNotification *ModuleConfig_ExternalNotificationConfig `protobuf:"bytes,3,opt,name=external_notification,json=externalNotification,proto3" json:"externalNotification,omitempty"`
// The part of the config that is specific to the Store & Forward module
StoreForward *ModuleConfig_StoreForwardConfig `protobuf:"bytes,4,opt,name=store_forward,json=storeForward,proto3" json:"storeForward,omitempty"`
// The part of the config that is specific to the RangeTest module
RangeTest *ModuleConfig_RangeTestConfig `protobuf:"bytes,5,opt,name=range_test,json=rangeTest,proto3" json:"rangeTest,omitempty"`
// The part of the config that is specific to the Telemetry module
Telemetry *ModuleConfig_TelemetryConfig `protobuf:"bytes,6,opt,name=telemetry,proto3" json:"telemetry,omitempty"`
// The part of the config that is specific to the Canned Message module
CannedMessage *ModuleConfig_CannedMessageConfig `protobuf:"bytes,7,opt,name=canned_message,json=cannedMessage,proto3" json:"cannedMessage,omitempty"`
// The part of the config that is specific to the Audio module
Audio *ModuleConfig_AudioConfig `protobuf:"bytes,9,opt,name=audio,proto3" json:"audio,omitempty"`
// The part of the config that is specific to the Remote Hardware module
RemoteHardware *ModuleConfig_RemoteHardwareConfig `protobuf:"bytes,10,opt,name=remote_hardware,json=remoteHardware,proto3" json:"remoteHardware,omitempty"`
// The part of the config that is specific to the Neighbor Info module
NeighborInfo *ModuleConfig_NeighborInfoConfig `protobuf:"bytes,11,opt,name=neighbor_info,json=neighborInfo,proto3" json:"neighborInfo,omitempty"`
// The part of the config that is specific to the Ambient Lighting module
AmbientLighting *ModuleConfig_AmbientLightingConfig `protobuf:"bytes,12,opt,name=ambient_lighting,json=ambientLighting,proto3" json:"ambientLighting,omitempty"`
// The part of the config that is specific to the Detection Sensor module
DetectionSensor *ModuleConfig_DetectionSensorConfig `protobuf:"bytes,13,opt,name=detection_sensor,json=detectionSensor,proto3" json:"detectionSensor,omitempty"`
// Paxcounter Config
Paxcounter *ModuleConfig_PaxcounterConfig `protobuf:"bytes,14,opt,name=paxcounter,proto3" json:"paxcounter,omitempty"`
// StatusMessage Config
Statusmessage *ModuleConfig_StatusMessageConfig `protobuf:"bytes,15,opt,name=statusmessage,proto3" json:"statusmessage,omitempty"`
// The part of the config that is specific to the Traffic Management module
TrafficManagement *ModuleConfig_TrafficManagementConfig `protobuf:"bytes,16,opt,name=traffic_management,json=trafficManagement,proto3" json:"trafficManagement,omitempty"`
// TAK Config
Tak *ModuleConfig_TAKConfig `protobuf:"bytes,17,opt,name=tak,proto3" json:"tak,omitempty"`
// A version integer used to invalidate old save files when we make
// incompatible changes This integer is set at build time and is private to
// NodeDB.cpp in the device code.
Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *LocalModuleConfig) Reset() {
*x = LocalModuleConfig{}
mi := &file_meshtastic_localonly_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *LocalModuleConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LocalModuleConfig) ProtoMessage() {}
func (x *LocalModuleConfig) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_localonly_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LocalModuleConfig.ProtoReflect.Descriptor instead.
func (*LocalModuleConfig) Descriptor() ([]byte, []int) {
return file_meshtastic_localonly_proto_rawDescGZIP(), []int{1}
}
func (x *LocalModuleConfig) GetMqtt() *ModuleConfig_MQTTConfig {
if x != nil {
return x.Mqtt
}
return nil
}
func (x *LocalModuleConfig) GetSerial() *ModuleConfig_SerialConfig {
if x != nil {
return x.Serial
}
return nil
}
func (x *LocalModuleConfig) GetExternalNotification() *ModuleConfig_ExternalNotificationConfig {
if x != nil {
return x.ExternalNotification
}
return nil
}
func (x *LocalModuleConfig) GetStoreForward() *ModuleConfig_StoreForwardConfig {
if x != nil {
return x.StoreForward
}
return nil
}
func (x *LocalModuleConfig) GetRangeTest() *ModuleConfig_RangeTestConfig {
if x != nil {
return x.RangeTest
}
return nil
}
func (x *LocalModuleConfig) GetTelemetry() *ModuleConfig_TelemetryConfig {
if x != nil {
return x.Telemetry
}
return nil
}
func (x *LocalModuleConfig) GetCannedMessage() *ModuleConfig_CannedMessageConfig {
if x != nil {
return x.CannedMessage
}
return nil
}
func (x *LocalModuleConfig) GetAudio() *ModuleConfig_AudioConfig {
if x != nil {
return x.Audio
}
return nil
}
func (x *LocalModuleConfig) GetRemoteHardware() *ModuleConfig_RemoteHardwareConfig {
if x != nil {
return x.RemoteHardware
}
return nil
}
func (x *LocalModuleConfig) GetNeighborInfo() *ModuleConfig_NeighborInfoConfig {
if x != nil {
return x.NeighborInfo
}
return nil
}
func (x *LocalModuleConfig) GetAmbientLighting() *ModuleConfig_AmbientLightingConfig {
if x != nil {
return x.AmbientLighting
}
return nil
}
func (x *LocalModuleConfig) GetDetectionSensor() *ModuleConfig_DetectionSensorConfig {
if x != nil {
return x.DetectionSensor
}
return nil
}
func (x *LocalModuleConfig) GetPaxcounter() *ModuleConfig_PaxcounterConfig {
if x != nil {
return x.Paxcounter
}
return nil
}
func (x *LocalModuleConfig) GetStatusmessage() *ModuleConfig_StatusMessageConfig {
if x != nil {
return x.Statusmessage
}
return nil
}
func (x *LocalModuleConfig) GetTrafficManagement() *ModuleConfig_TrafficManagementConfig {
if x != nil {
return x.TrafficManagement
}
return nil
}
func (x *LocalModuleConfig) GetTak() *ModuleConfig_TAKConfig {
if x != nil {
return x.Tak
}
return nil
}
func (x *LocalModuleConfig) GetVersion() uint32 {
if x != nil {
return x.Version
}
return 0
}
var File_meshtastic_localonly_proto protoreflect.FileDescriptor
const file_meshtastic_localonly_proto_rawDesc = "" +
"\n" +
"\x1ameshtastic/localonly.proto\x12\n" +
"meshtastic\x1a\x17meshtastic/config.proto\x1a\x1emeshtastic/module_config.proto\"\x81\x04\n" +
"\vLocalConfig\x127\n" +
"\x06device\x18\x01 \x01(\v2\x1f.meshtastic.Config.DeviceConfigR\x06device\x12=\n" +
"\bposition\x18\x02 \x01(\v2!.meshtastic.Config.PositionConfigR\bposition\x124\n" +
"\x05power\x18\x03 \x01(\v2\x1e.meshtastic.Config.PowerConfigR\x05power\x12:\n" +
"\anetwork\x18\x04 \x01(\v2 .meshtastic.Config.NetworkConfigR\anetwork\x12:\n" +
"\adisplay\x18\x05 \x01(\v2 .meshtastic.Config.DisplayConfigR\adisplay\x121\n" +
"\x04lora\x18\x06 \x01(\v2\x1d.meshtastic.Config.LoRaConfigR\x04lora\x12@\n" +
"\tbluetooth\x18\a \x01(\v2\".meshtastic.Config.BluetoothConfigR\tbluetooth\x12\x18\n" +
"\aversion\x18\b \x01(\rR\aversion\x12=\n" +
"\bsecurity\x18\t \x01(\v2!.meshtastic.Config.SecurityConfigR\bsecurity\"\x99\n" +
"\n" +
"\x11LocalModuleConfig\x127\n" +
"\x04mqtt\x18\x01 \x01(\v2#.meshtastic.ModuleConfig.MQTTConfigR\x04mqtt\x12=\n" +
"\x06serial\x18\x02 \x01(\v2%.meshtastic.ModuleConfig.SerialConfigR\x06serial\x12h\n" +
"\x15external_notification\x18\x03 \x01(\v23.meshtastic.ModuleConfig.ExternalNotificationConfigR\x14externalNotification\x12P\n" +
"\rstore_forward\x18\x04 \x01(\v2+.meshtastic.ModuleConfig.StoreForwardConfigR\fstoreForward\x12G\n" +
"\n" +
"range_test\x18\x05 \x01(\v2(.meshtastic.ModuleConfig.RangeTestConfigR\trangeTest\x12F\n" +
"\ttelemetry\x18\x06 \x01(\v2(.meshtastic.ModuleConfig.TelemetryConfigR\ttelemetry\x12S\n" +
"\x0ecanned_message\x18\a \x01(\v2,.meshtastic.ModuleConfig.CannedMessageConfigR\rcannedMessage\x12:\n" +
"\x05audio\x18\t \x01(\v2$.meshtastic.ModuleConfig.AudioConfigR\x05audio\x12V\n" +
"\x0fremote_hardware\x18\n" +
" \x01(\v2-.meshtastic.ModuleConfig.RemoteHardwareConfigR\x0eremoteHardware\x12P\n" +
"\rneighbor_info\x18\v \x01(\v2+.meshtastic.ModuleConfig.NeighborInfoConfigR\fneighborInfo\x12Y\n" +
"\x10ambient_lighting\x18\f \x01(\v2..meshtastic.ModuleConfig.AmbientLightingConfigR\x0fambientLighting\x12Y\n" +
"\x10detection_sensor\x18\r \x01(\v2..meshtastic.ModuleConfig.DetectionSensorConfigR\x0fdetectionSensor\x12I\n" +
"\n" +
"paxcounter\x18\x0e \x01(\v2).meshtastic.ModuleConfig.PaxcounterConfigR\n" +
"paxcounter\x12R\n" +
"\rstatusmessage\x18\x0f \x01(\v2,.meshtastic.ModuleConfig.StatusMessageConfigR\rstatusmessage\x12_\n" +
"\x12traffic_management\x18\x10 \x01(\v20.meshtastic.ModuleConfig.TrafficManagementConfigR\x11trafficManagement\x124\n" +
"\x03tak\x18\x11 \x01(\v2\".meshtastic.ModuleConfig.TAKConfigR\x03tak\x12\x18\n" +
"\aversion\x18\b \x01(\rR\aversionBe\n" +
"\x14org.meshtastic.protoB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_localonly_proto_rawDescOnce sync.Once
file_meshtastic_localonly_proto_rawDescData []byte
)
func file_meshtastic_localonly_proto_rawDescGZIP() []byte {
file_meshtastic_localonly_proto_rawDescOnce.Do(func() {
file_meshtastic_localonly_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_localonly_proto_rawDesc), len(file_meshtastic_localonly_proto_rawDesc)))
})
return file_meshtastic_localonly_proto_rawDescData
}
var file_meshtastic_localonly_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_meshtastic_localonly_proto_goTypes = []any{
(*LocalConfig)(nil), // 0: meshtastic.LocalConfig
(*LocalModuleConfig)(nil), // 1: meshtastic.LocalModuleConfig
(*Config_DeviceConfig)(nil), // 2: meshtastic.Config.DeviceConfig
(*Config_PositionConfig)(nil), // 3: meshtastic.Config.PositionConfig
(*Config_PowerConfig)(nil), // 4: meshtastic.Config.PowerConfig
(*Config_NetworkConfig)(nil), // 5: meshtastic.Config.NetworkConfig
(*Config_DisplayConfig)(nil), // 6: meshtastic.Config.DisplayConfig
(*Config_LoRaConfig)(nil), // 7: meshtastic.Config.LoRaConfig
(*Config_BluetoothConfig)(nil), // 8: meshtastic.Config.BluetoothConfig
(*Config_SecurityConfig)(nil), // 9: meshtastic.Config.SecurityConfig
(*ModuleConfig_MQTTConfig)(nil), // 10: meshtastic.ModuleConfig.MQTTConfig
(*ModuleConfig_SerialConfig)(nil), // 11: meshtastic.ModuleConfig.SerialConfig
(*ModuleConfig_ExternalNotificationConfig)(nil), // 12: meshtastic.ModuleConfig.ExternalNotificationConfig
(*ModuleConfig_StoreForwardConfig)(nil), // 13: meshtastic.ModuleConfig.StoreForwardConfig
(*ModuleConfig_RangeTestConfig)(nil), // 14: meshtastic.ModuleConfig.RangeTestConfig
(*ModuleConfig_TelemetryConfig)(nil), // 15: meshtastic.ModuleConfig.TelemetryConfig
(*ModuleConfig_CannedMessageConfig)(nil), // 16: meshtastic.ModuleConfig.CannedMessageConfig
(*ModuleConfig_AudioConfig)(nil), // 17: meshtastic.ModuleConfig.AudioConfig
(*ModuleConfig_RemoteHardwareConfig)(nil), // 18: meshtastic.ModuleConfig.RemoteHardwareConfig
(*ModuleConfig_NeighborInfoConfig)(nil), // 19: meshtastic.ModuleConfig.NeighborInfoConfig
(*ModuleConfig_AmbientLightingConfig)(nil), // 20: meshtastic.ModuleConfig.AmbientLightingConfig
(*ModuleConfig_DetectionSensorConfig)(nil), // 21: meshtastic.ModuleConfig.DetectionSensorConfig
(*ModuleConfig_PaxcounterConfig)(nil), // 22: meshtastic.ModuleConfig.PaxcounterConfig
(*ModuleConfig_StatusMessageConfig)(nil), // 23: meshtastic.ModuleConfig.StatusMessageConfig
(*ModuleConfig_TrafficManagementConfig)(nil), // 24: meshtastic.ModuleConfig.TrafficManagementConfig
(*ModuleConfig_TAKConfig)(nil), // 25: meshtastic.ModuleConfig.TAKConfig
}
var file_meshtastic_localonly_proto_depIdxs = []int32{
2, // 0: meshtastic.LocalConfig.device:type_name -> meshtastic.Config.DeviceConfig
3, // 1: meshtastic.LocalConfig.position:type_name -> meshtastic.Config.PositionConfig
4, // 2: meshtastic.LocalConfig.power:type_name -> meshtastic.Config.PowerConfig
5, // 3: meshtastic.LocalConfig.network:type_name -> meshtastic.Config.NetworkConfig
6, // 4: meshtastic.LocalConfig.display:type_name -> meshtastic.Config.DisplayConfig
7, // 5: meshtastic.LocalConfig.lora:type_name -> meshtastic.Config.LoRaConfig
8, // 6: meshtastic.LocalConfig.bluetooth:type_name -> meshtastic.Config.BluetoothConfig
9, // 7: meshtastic.LocalConfig.security:type_name -> meshtastic.Config.SecurityConfig
10, // 8: meshtastic.LocalModuleConfig.mqtt:type_name -> meshtastic.ModuleConfig.MQTTConfig
11, // 9: meshtastic.LocalModuleConfig.serial:type_name -> meshtastic.ModuleConfig.SerialConfig
12, // 10: meshtastic.LocalModuleConfig.external_notification:type_name -> meshtastic.ModuleConfig.ExternalNotificationConfig
13, // 11: meshtastic.LocalModuleConfig.store_forward:type_name -> meshtastic.ModuleConfig.StoreForwardConfig
14, // 12: meshtastic.LocalModuleConfig.range_test:type_name -> meshtastic.ModuleConfig.RangeTestConfig
15, // 13: meshtastic.LocalModuleConfig.telemetry:type_name -> meshtastic.ModuleConfig.TelemetryConfig
16, // 14: meshtastic.LocalModuleConfig.canned_message:type_name -> meshtastic.ModuleConfig.CannedMessageConfig
17, // 15: meshtastic.LocalModuleConfig.audio:type_name -> meshtastic.ModuleConfig.AudioConfig
18, // 16: meshtastic.LocalModuleConfig.remote_hardware:type_name -> meshtastic.ModuleConfig.RemoteHardwareConfig
19, // 17: meshtastic.LocalModuleConfig.neighbor_info:type_name -> meshtastic.ModuleConfig.NeighborInfoConfig
20, // 18: meshtastic.LocalModuleConfig.ambient_lighting:type_name -> meshtastic.ModuleConfig.AmbientLightingConfig
21, // 19: meshtastic.LocalModuleConfig.detection_sensor:type_name -> meshtastic.ModuleConfig.DetectionSensorConfig
22, // 20: meshtastic.LocalModuleConfig.paxcounter:type_name -> meshtastic.ModuleConfig.PaxcounterConfig
23, // 21: meshtastic.LocalModuleConfig.statusmessage:type_name -> meshtastic.ModuleConfig.StatusMessageConfig
24, // 22: meshtastic.LocalModuleConfig.traffic_management:type_name -> meshtastic.ModuleConfig.TrafficManagementConfig
25, // 23: meshtastic.LocalModuleConfig.tak:type_name -> meshtastic.ModuleConfig.TAKConfig
24, // [24:24] is the sub-list for method output_type
24, // [24:24] is the sub-list for method input_type
24, // [24:24] is the sub-list for extension type_name
24, // [24:24] is the sub-list for extension extendee
0, // [0:24] is the sub-list for field type_name
}
func init() { file_meshtastic_localonly_proto_init() }
func file_meshtastic_localonly_proto_init() {
if File_meshtastic_localonly_proto != nil {
return
}
file_meshtastic_config_proto_init()
file_meshtastic_module_config_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_localonly_proto_rawDesc), len(file_meshtastic_localonly_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_localonly_proto_goTypes,
DependencyIndexes: file_meshtastic_localonly_proto_depIdxs,
MessageInfos: file_meshtastic_localonly_proto_msgTypes,
}.Build()
File_meshtastic_localonly_proto = out.File
file_meshtastic_localonly_proto_goTypes = nil
file_meshtastic_localonly_proto_depIdxs = nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,349 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/mqtt.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This message wraps a MeshPacket with extra metadata about the sender and how it arrived.
type ServiceEnvelope struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The (probably encrypted) packet
Packet *MeshPacket `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet,omitempty"`
// The global channel ID it was sent on
ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channelId,omitempty"`
// The sending gateway node ID. Can we use this to authenticate/prevent fake
// nodeid impersonation for senders? - i.e. use gateway/mesh id (which is authenticated) + local node id as
// the globally trusted nodenum
GatewayId string `protobuf:"bytes,3,opt,name=gateway_id,json=gatewayId,proto3" json:"gatewayId,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ServiceEnvelope) Reset() {
*x = ServiceEnvelope{}
mi := &file_meshtastic_mqtt_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ServiceEnvelope) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ServiceEnvelope) ProtoMessage() {}
func (x *ServiceEnvelope) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_mqtt_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ServiceEnvelope.ProtoReflect.Descriptor instead.
func (*ServiceEnvelope) Descriptor() ([]byte, []int) {
return file_meshtastic_mqtt_proto_rawDescGZIP(), []int{0}
}
func (x *ServiceEnvelope) GetPacket() *MeshPacket {
if x != nil {
return x.Packet
}
return nil
}
func (x *ServiceEnvelope) GetChannelId() string {
if x != nil {
return x.ChannelId
}
return ""
}
func (x *ServiceEnvelope) GetGatewayId() string {
if x != nil {
return x.GatewayId
}
return ""
}
// Information about a node intended to be reported unencrypted to a map using MQTT.
type MapReport struct {
state protoimpl.MessageState `protogen:"open.v1"`
// A full name for this user, i.e. "Kevin Hester"
LongName string `protobuf:"bytes,1,opt,name=long_name,json=longName,proto3" json:"longName,omitempty"`
// A VERY short name, ideally two characters.
// Suitable for a tiny OLED screen
ShortName string `protobuf:"bytes,2,opt,name=short_name,json=shortName,proto3" json:"shortName,omitempty"`
// Role of the node that applies specific settings for a particular use-case
Role Config_DeviceConfig_Role `protobuf:"varint,3,opt,name=role,proto3,enum=meshtastic.Config_DeviceConfig_Role" json:"role,omitempty"`
// Hardware model of the node, i.e. T-Beam, Heltec V3, etc...
HwModel HardwareModel `protobuf:"varint,4,opt,name=hw_model,json=hwModel,proto3,enum=meshtastic.HardwareModel" json:"hwModel,omitempty"`
// Device firmware version string
FirmwareVersion string `protobuf:"bytes,5,opt,name=firmware_version,json=firmwareVersion,proto3" json:"firmwareVersion,omitempty"`
// The region code for the radio (US, CN, EU433, etc...)
Region Config_LoRaConfig_RegionCode `protobuf:"varint,6,opt,name=region,proto3,enum=meshtastic.Config_LoRaConfig_RegionCode" json:"region,omitempty"`
// Modem preset used by the radio (LongFast, MediumSlow, etc...)
ModemPreset Config_LoRaConfig_ModemPreset `protobuf:"varint,7,opt,name=modem_preset,json=modemPreset,proto3,enum=meshtastic.Config_LoRaConfig_ModemPreset" json:"modemPreset,omitempty"`
// Whether the node has a channel with default PSK and name (LongFast, MediumSlow, etc...)
// and it uses the default frequency slot given the region and modem preset.
HasDefaultChannel bool `protobuf:"varint,8,opt,name=has_default_channel,json=hasDefaultChannel,proto3" json:"hasDefaultChannel,omitempty"`
// Latitude: multiply by 1e-7 to get degrees in floating point
LatitudeI int32 `protobuf:"fixed32,9,opt,name=latitude_i,json=latitudeI,proto3" json:"latitudeI,omitempty"`
// Longitude: multiply by 1e-7 to get degrees in floating point
LongitudeI int32 `protobuf:"fixed32,10,opt,name=longitude_i,json=longitudeI,proto3" json:"longitudeI,omitempty"`
// Altitude in meters above MSL
Altitude int32 `protobuf:"varint,11,opt,name=altitude,proto3" json:"altitude,omitempty"`
// Indicates the bits of precision for latitude and longitude set by the sending node
PositionPrecision uint32 `protobuf:"varint,12,opt,name=position_precision,json=positionPrecision,proto3" json:"positionPrecision,omitempty"`
// Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT)
NumOnlineLocalNodes uint32 `protobuf:"varint,13,opt,name=num_online_local_nodes,json=numOnlineLocalNodes,proto3" json:"numOnlineLocalNodes,omitempty"`
// User has opted in to share their location (map report) with the mqtt server
// Controlled by map_report.should_report_location
HasOptedReportLocation bool `protobuf:"varint,14,opt,name=has_opted_report_location,json=hasOptedReportLocation,proto3" json:"hasOptedReportLocation,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *MapReport) Reset() {
*x = MapReport{}
mi := &file_meshtastic_mqtt_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *MapReport) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MapReport) ProtoMessage() {}
func (x *MapReport) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_mqtt_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MapReport.ProtoReflect.Descriptor instead.
func (*MapReport) Descriptor() ([]byte, []int) {
return file_meshtastic_mqtt_proto_rawDescGZIP(), []int{1}
}
func (x *MapReport) GetLongName() string {
if x != nil {
return x.LongName
}
return ""
}
func (x *MapReport) GetShortName() string {
if x != nil {
return x.ShortName
}
return ""
}
func (x *MapReport) GetRole() Config_DeviceConfig_Role {
if x != nil {
return x.Role
}
return Config_DeviceConfig_CLIENT
}
func (x *MapReport) GetHwModel() HardwareModel {
if x != nil {
return x.HwModel
}
return HardwareModel_UNSET
}
func (x *MapReport) GetFirmwareVersion() string {
if x != nil {
return x.FirmwareVersion
}
return ""
}
func (x *MapReport) GetRegion() Config_LoRaConfig_RegionCode {
if x != nil {
return x.Region
}
return Config_LoRaConfig_UNSET
}
func (x *MapReport) GetModemPreset() Config_LoRaConfig_ModemPreset {
if x != nil {
return x.ModemPreset
}
return Config_LoRaConfig_LONG_FAST
}
func (x *MapReport) GetHasDefaultChannel() bool {
if x != nil {
return x.HasDefaultChannel
}
return false
}
func (x *MapReport) GetLatitudeI() int32 {
if x != nil {
return x.LatitudeI
}
return 0
}
func (x *MapReport) GetLongitudeI() int32 {
if x != nil {
return x.LongitudeI
}
return 0
}
func (x *MapReport) GetAltitude() int32 {
if x != nil {
return x.Altitude
}
return 0
}
func (x *MapReport) GetPositionPrecision() uint32 {
if x != nil {
return x.PositionPrecision
}
return 0
}
func (x *MapReport) GetNumOnlineLocalNodes() uint32 {
if x != nil {
return x.NumOnlineLocalNodes
}
return 0
}
func (x *MapReport) GetHasOptedReportLocation() bool {
if x != nil {
return x.HasOptedReportLocation
}
return false
}
var File_meshtastic_mqtt_proto protoreflect.FileDescriptor
const file_meshtastic_mqtt_proto_rawDesc = "" +
"\n" +
"\x15meshtastic/mqtt.proto\x12\n" +
"meshtastic\x1a\x17meshtastic/config.proto\x1a\x15meshtastic/mesh.proto\"\x7f\n" +
"\x0fServiceEnvelope\x12.\n" +
"\x06packet\x18\x01 \x01(\v2\x16.meshtastic.MeshPacketR\x06packet\x12\x1d\n" +
"\n" +
"channel_id\x18\x02 \x01(\tR\tchannelId\x12\x1d\n" +
"\n" +
"gateway_id\x18\x03 \x01(\tR\tgatewayId\"\x9d\x05\n" +
"\tMapReport\x12\x1b\n" +
"\tlong_name\x18\x01 \x01(\tR\blongName\x12\x1d\n" +
"\n" +
"short_name\x18\x02 \x01(\tR\tshortName\x128\n" +
"\x04role\x18\x03 \x01(\x0e2$.meshtastic.Config.DeviceConfig.RoleR\x04role\x124\n" +
"\bhw_model\x18\x04 \x01(\x0e2\x19.meshtastic.HardwareModelR\ahwModel\x12)\n" +
"\x10firmware_version\x18\x05 \x01(\tR\x0ffirmwareVersion\x12@\n" +
"\x06region\x18\x06 \x01(\x0e2(.meshtastic.Config.LoRaConfig.RegionCodeR\x06region\x12L\n" +
"\fmodem_preset\x18\a \x01(\x0e2).meshtastic.Config.LoRaConfig.ModemPresetR\vmodemPreset\x12.\n" +
"\x13has_default_channel\x18\b \x01(\bR\x11hasDefaultChannel\x12\x1d\n" +
"\n" +
"latitude_i\x18\t \x01(\x0fR\tlatitudeI\x12\x1f\n" +
"\vlongitude_i\x18\n" +
" \x01(\x0fR\n" +
"longitudeI\x12\x1a\n" +
"\baltitude\x18\v \x01(\x05R\baltitude\x12-\n" +
"\x12position_precision\x18\f \x01(\rR\x11positionPrecision\x123\n" +
"\x16num_online_local_nodes\x18\r \x01(\rR\x13numOnlineLocalNodes\x129\n" +
"\x19has_opted_report_location\x18\x0e \x01(\bR\x16hasOptedReportLocationB`\n" +
"\x14org.meshtastic.protoB\n" +
"MQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_mqtt_proto_rawDescOnce sync.Once
file_meshtastic_mqtt_proto_rawDescData []byte
)
func file_meshtastic_mqtt_proto_rawDescGZIP() []byte {
file_meshtastic_mqtt_proto_rawDescOnce.Do(func() {
file_meshtastic_mqtt_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_mqtt_proto_rawDesc), len(file_meshtastic_mqtt_proto_rawDesc)))
})
return file_meshtastic_mqtt_proto_rawDescData
}
var file_meshtastic_mqtt_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_meshtastic_mqtt_proto_goTypes = []any{
(*ServiceEnvelope)(nil), // 0: meshtastic.ServiceEnvelope
(*MapReport)(nil), // 1: meshtastic.MapReport
(*MeshPacket)(nil), // 2: meshtastic.MeshPacket
(Config_DeviceConfig_Role)(0), // 3: meshtastic.Config.DeviceConfig.Role
(HardwareModel)(0), // 4: meshtastic.HardwareModel
(Config_LoRaConfig_RegionCode)(0), // 5: meshtastic.Config.LoRaConfig.RegionCode
(Config_LoRaConfig_ModemPreset)(0), // 6: meshtastic.Config.LoRaConfig.ModemPreset
}
var file_meshtastic_mqtt_proto_depIdxs = []int32{
2, // 0: meshtastic.ServiceEnvelope.packet:type_name -> meshtastic.MeshPacket
3, // 1: meshtastic.MapReport.role:type_name -> meshtastic.Config.DeviceConfig.Role
4, // 2: meshtastic.MapReport.hw_model:type_name -> meshtastic.HardwareModel
5, // 3: meshtastic.MapReport.region:type_name -> meshtastic.Config.LoRaConfig.RegionCode
6, // 4: meshtastic.MapReport.modem_preset:type_name -> meshtastic.Config.LoRaConfig.ModemPreset
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_meshtastic_mqtt_proto_init() }
func file_meshtastic_mqtt_proto_init() {
if File_meshtastic_mqtt_proto != nil {
return
}
file_meshtastic_config_proto_init()
file_meshtastic_mesh_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_mqtt_proto_rawDesc), len(file_meshtastic_mqtt_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_mqtt_proto_goTypes,
DependencyIndexes: file_meshtastic_mqtt_proto_depIdxs,
MessageInfos: file_meshtastic_mqtt_proto_msgTypes,
}.Build()
File_meshtastic_mqtt_proto = out.File
file_meshtastic_mqtt_proto_goTypes = nil
file_meshtastic_mqtt_proto_depIdxs = nil
}

View File

@@ -0,0 +1,849 @@
// Custom options for defining:
// - Maximum size of string/bytes
// - Maximum number of elements in array
//
// These are used by nanopb to generate statically allocable structures
// for memory-limited environments.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: nanopb.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type FieldType int32
const (
FieldType_FT_DEFAULT FieldType = 0 // Automatically decide field type, generate static field if possible.
FieldType_FT_CALLBACK FieldType = 1 // Always generate a callback field.
FieldType_FT_POINTER FieldType = 4 // Always generate a dynamically allocated field.
FieldType_FT_STATIC FieldType = 2 // Generate a static field or raise an exception if not possible.
FieldType_FT_IGNORE FieldType = 3 // Ignore the field completely.
FieldType_FT_INLINE FieldType = 5 // Legacy option, use the separate 'fixed_length' option instead
)
// Enum value maps for FieldType.
var (
FieldType_name = map[int32]string{
0: "FT_DEFAULT",
1: "FT_CALLBACK",
4: "FT_POINTER",
2: "FT_STATIC",
3: "FT_IGNORE",
5: "FT_INLINE",
}
FieldType_value = map[string]int32{
"FT_DEFAULT": 0,
"FT_CALLBACK": 1,
"FT_POINTER": 4,
"FT_STATIC": 2,
"FT_IGNORE": 3,
"FT_INLINE": 5,
}
)
func (x FieldType) Enum() *FieldType {
p := new(FieldType)
*p = x
return p
}
func (x FieldType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (FieldType) Descriptor() protoreflect.EnumDescriptor {
return file_nanopb_proto_enumTypes[0].Descriptor()
}
func (FieldType) Type() protoreflect.EnumType {
return &file_nanopb_proto_enumTypes[0]
}
func (x FieldType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Do not use.
func (x *FieldType) UnmarshalJSON(b []byte) error {
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
if err != nil {
return err
}
*x = FieldType(num)
return nil
}
// Deprecated: Use FieldType.Descriptor instead.
func (FieldType) EnumDescriptor() ([]byte, []int) {
return file_nanopb_proto_rawDescGZIP(), []int{0}
}
type IntSize int32
const (
IntSize_IS_DEFAULT IntSize = 0 // Default, 32/64bit based on type in .proto
IntSize_IS_8 IntSize = 8
IntSize_IS_16 IntSize = 16
IntSize_IS_32 IntSize = 32
IntSize_IS_64 IntSize = 64
)
// Enum value maps for IntSize.
var (
IntSize_name = map[int32]string{
0: "IS_DEFAULT",
8: "IS_8",
16: "IS_16",
32: "IS_32",
64: "IS_64",
}
IntSize_value = map[string]int32{
"IS_DEFAULT": 0,
"IS_8": 8,
"IS_16": 16,
"IS_32": 32,
"IS_64": 64,
}
)
func (x IntSize) Enum() *IntSize {
p := new(IntSize)
*p = x
return p
}
func (x IntSize) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (IntSize) Descriptor() protoreflect.EnumDescriptor {
return file_nanopb_proto_enumTypes[1].Descriptor()
}
func (IntSize) Type() protoreflect.EnumType {
return &file_nanopb_proto_enumTypes[1]
}
func (x IntSize) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Do not use.
func (x *IntSize) UnmarshalJSON(b []byte) error {
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
if err != nil {
return err
}
*x = IntSize(num)
return nil
}
// Deprecated: Use IntSize.Descriptor instead.
func (IntSize) EnumDescriptor() ([]byte, []int) {
return file_nanopb_proto_rawDescGZIP(), []int{1}
}
type TypenameMangling int32
const (
TypenameMangling_M_NONE TypenameMangling = 0 // Default, no typename mangling
TypenameMangling_M_STRIP_PACKAGE TypenameMangling = 1 // Strip current package name
TypenameMangling_M_FLATTEN TypenameMangling = 2 // Only use last path component
TypenameMangling_M_PACKAGE_INITIALS TypenameMangling = 3 // Replace the package name by the initials
)
// Enum value maps for TypenameMangling.
var (
TypenameMangling_name = map[int32]string{
0: "M_NONE",
1: "M_STRIP_PACKAGE",
2: "M_FLATTEN",
3: "M_PACKAGE_INITIALS",
}
TypenameMangling_value = map[string]int32{
"M_NONE": 0,
"M_STRIP_PACKAGE": 1,
"M_FLATTEN": 2,
"M_PACKAGE_INITIALS": 3,
}
)
func (x TypenameMangling) Enum() *TypenameMangling {
p := new(TypenameMangling)
*p = x
return p
}
func (x TypenameMangling) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (TypenameMangling) Descriptor() protoreflect.EnumDescriptor {
return file_nanopb_proto_enumTypes[2].Descriptor()
}
func (TypenameMangling) Type() protoreflect.EnumType {
return &file_nanopb_proto_enumTypes[2]
}
func (x TypenameMangling) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Do not use.
func (x *TypenameMangling) UnmarshalJSON(b []byte) error {
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
if err != nil {
return err
}
*x = TypenameMangling(num)
return nil
}
// Deprecated: Use TypenameMangling.Descriptor instead.
func (TypenameMangling) EnumDescriptor() ([]byte, []int) {
return file_nanopb_proto_rawDescGZIP(), []int{2}
}
type DescriptorSize int32
const (
DescriptorSize_DS_AUTO DescriptorSize = 0 // Select minimal size based on field type
DescriptorSize_DS_1 DescriptorSize = 1 // 1 word; up to 15 byte fields, no arrays
DescriptorSize_DS_2 DescriptorSize = 2 // 2 words; up to 4095 byte fields, 4095 entry arrays
DescriptorSize_DS_4 DescriptorSize = 4 // 4 words; up to 2^32-1 byte fields, 2^16-1 entry arrays
DescriptorSize_DS_8 DescriptorSize = 8 // 8 words; up to 2^32-1 entry arrays
)
// Enum value maps for DescriptorSize.
var (
DescriptorSize_name = map[int32]string{
0: "DS_AUTO",
1: "DS_1",
2: "DS_2",
4: "DS_4",
8: "DS_8",
}
DescriptorSize_value = map[string]int32{
"DS_AUTO": 0,
"DS_1": 1,
"DS_2": 2,
"DS_4": 4,
"DS_8": 8,
}
)
func (x DescriptorSize) Enum() *DescriptorSize {
p := new(DescriptorSize)
*p = x
return p
}
func (x DescriptorSize) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (DescriptorSize) Descriptor() protoreflect.EnumDescriptor {
return file_nanopb_proto_enumTypes[3].Descriptor()
}
func (DescriptorSize) Type() protoreflect.EnumType {
return &file_nanopb_proto_enumTypes[3]
}
func (x DescriptorSize) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Do not use.
func (x *DescriptorSize) UnmarshalJSON(b []byte) error {
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
if err != nil {
return err
}
*x = DescriptorSize(num)
return nil
}
// Deprecated: Use DescriptorSize.Descriptor instead.
func (DescriptorSize) EnumDescriptor() ([]byte, []int) {
return file_nanopb_proto_rawDescGZIP(), []int{3}
}
// This is the inner options message, which basically defines options for
// a field. When it is used in message or file scope, it applies to all
// fields.
type NanoPBOptions struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Allocated size for 'bytes' and 'string' fields.
// For string fields, this should include the space for null terminator.
MaxSize *int32 `protobuf:"varint,1,opt,name=max_size,json=maxSize" json:"maxSize,omitempty"`
// Maximum length for 'string' fields. Setting this is equivalent
// to setting max_size to a value of length+1.
MaxLength *int32 `protobuf:"varint,14,opt,name=max_length,json=maxLength" json:"maxLength,omitempty"`
// Allocated number of entries in arrays ('repeated' fields)
MaxCount *int32 `protobuf:"varint,2,opt,name=max_count,json=maxCount" json:"maxCount,omitempty"`
// Size of integer fields. Can save some memory if you don't need
// full 32 bits for the value.
IntSize *IntSize `protobuf:"varint,7,opt,name=int_size,json=intSize,enum=IntSize,def=0" json:"intSize,omitempty"`
// Force type of field (callback or static allocation)
Type *FieldType `protobuf:"varint,3,opt,name=type,enum=FieldType,def=0" json:"type,omitempty"`
// Use long names for enums, i.e. EnumName_EnumValue.
LongNames *bool `protobuf:"varint,4,opt,name=long_names,json=longNames,def=1" json:"longNames,omitempty"`
// Add 'packed' attribute to generated structs.
// Note: this cannot be used on CPUs that break on unaligned
// accesses to variables.
PackedStruct *bool `protobuf:"varint,5,opt,name=packed_struct,json=packedStruct,def=0" json:"packedStruct,omitempty"`
// Add 'packed' attribute to generated enums.
PackedEnum *bool `protobuf:"varint,10,opt,name=packed_enum,json=packedEnum,def=0" json:"packedEnum,omitempty"`
// Skip this message
SkipMessage *bool `protobuf:"varint,6,opt,name=skip_message,json=skipMessage,def=0" json:"skipMessage,omitempty"`
// Generate oneof fields as normal optional fields instead of union.
NoUnions *bool `protobuf:"varint,8,opt,name=no_unions,json=noUnions,def=0" json:"noUnions,omitempty"`
// integer type tag for a message
Msgid *uint32 `protobuf:"varint,9,opt,name=msgid" json:"msgid,omitempty"`
// decode oneof as anonymous union
AnonymousOneof *bool `protobuf:"varint,11,opt,name=anonymous_oneof,json=anonymousOneof,def=0" json:"anonymousOneof,omitempty"`
// Proto3 singular field does not generate a "has_" flag
Proto3 *bool `protobuf:"varint,12,opt,name=proto3,def=0" json:"proto3,omitempty"`
// Force proto3 messages to have no "has_" flag.
// This was default behavior until nanopb-0.4.0.
Proto3SingularMsgs *bool `protobuf:"varint,21,opt,name=proto3_singular_msgs,json=proto3SingularMsgs,def=0" json:"proto3SingularMsgs,omitempty"`
// Generate an enum->string mapping function (can take up lots of space).
EnumToString *bool `protobuf:"varint,13,opt,name=enum_to_string,json=enumToString,def=0" json:"enumToString,omitempty"`
// Generate bytes arrays with fixed length
FixedLength *bool `protobuf:"varint,15,opt,name=fixed_length,json=fixedLength,def=0" json:"fixedLength,omitempty"`
// Generate repeated field with fixed count
FixedCount *bool `protobuf:"varint,16,opt,name=fixed_count,json=fixedCount,def=0" json:"fixedCount,omitempty"`
// Generate message-level callback that is called before decoding submessages.
// This can be used to set callback fields for submsgs inside oneofs.
SubmsgCallback *bool `protobuf:"varint,22,opt,name=submsg_callback,json=submsgCallback,def=0" json:"submsgCallback,omitempty"`
// Shorten or remove package names from type names.
// This option applies only on the file level.
MangleNames *TypenameMangling `protobuf:"varint,17,opt,name=mangle_names,json=mangleNames,enum=TypenameMangling,def=0" json:"mangleNames,omitempty"`
// Data type for storage associated with callback fields.
CallbackDatatype *string `protobuf:"bytes,18,opt,name=callback_datatype,json=callbackDatatype,def=pb_callback_t" json:"callbackDatatype,omitempty"`
// Callback function used for encoding and decoding.
// Prior to nanopb-0.4.0, the callback was specified in per-field pb_callback_t
// structure. This is still supported, but does not work inside e.g. oneof or pointer
// fields. Instead, a new method allows specifying a per-message callback that
// will be called for all callback fields in a message type.
CallbackFunction *string `protobuf:"bytes,19,opt,name=callback_function,json=callbackFunction,def=pb_default_field_callback" json:"callbackFunction,omitempty"`
// Select the size of field descriptors. This option has to be defined
// for the whole message, not per-field. Usually automatic selection is
// ok, but if it results in compilation errors you can increase the field
// size here.
Descriptorsize *DescriptorSize `protobuf:"varint,20,opt,name=descriptorsize,enum=DescriptorSize,def=0" json:"descriptorsize,omitempty"`
// Set default value for has_ fields.
DefaultHas *bool `protobuf:"varint,23,opt,name=default_has,json=defaultHas,def=0" json:"defaultHas,omitempty"`
// Extra files to include in generated `.pb.h`
Include []string `protobuf:"bytes,24,rep,name=include" json:"include,omitempty"`
// Automatic includes to exclude from generated `.pb.h`
// Same as nanopb_generator.py command line flag -x.
Exclude []string `protobuf:"bytes,26,rep,name=exclude" json:"exclude,omitempty"`
// Package name that applies only for nanopb.
Package *string `protobuf:"bytes,25,opt,name=package" json:"package,omitempty"`
// Override type of the field in generated C code. Only to be used with related field types
TypeOverride *descriptorpb.FieldDescriptorProto_Type `protobuf:"varint,27,opt,name=type_override,json=typeOverride,enum=google.protobuf.FieldDescriptorProto_Type" json:"typeOverride,omitempty"`
// Due to historical reasons, nanopb orders fields in structs by their tag number
// instead of the order in .proto. Set this to false to keep the .proto order.
// The default value will probably change to false in nanopb-0.5.0.
SortByTag *bool `protobuf:"varint,28,opt,name=sort_by_tag,json=sortByTag,def=1" json:"sortByTag,omitempty"`
// Set the FT_DEFAULT field conversion strategy.
// A field that can become a static member of a c struct (e.g. int, bool, etc)
// will be a a static field.
// Fields with dynamic length are converted to either a pointer or a callback.
FallbackType *FieldType `protobuf:"varint,29,opt,name=fallback_type,json=fallbackType,enum=FieldType,def=1" json:"fallbackType,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Default values for NanoPBOptions fields.
const (
Default_NanoPBOptions_IntSize = IntSize_IS_DEFAULT
Default_NanoPBOptions_Type = FieldType_FT_DEFAULT
Default_NanoPBOptions_LongNames = bool(true)
Default_NanoPBOptions_PackedStruct = bool(false)
Default_NanoPBOptions_PackedEnum = bool(false)
Default_NanoPBOptions_SkipMessage = bool(false)
Default_NanoPBOptions_NoUnions = bool(false)
Default_NanoPBOptions_AnonymousOneof = bool(false)
Default_NanoPBOptions_Proto3 = bool(false)
Default_NanoPBOptions_Proto3SingularMsgs = bool(false)
Default_NanoPBOptions_EnumToString = bool(false)
Default_NanoPBOptions_FixedLength = bool(false)
Default_NanoPBOptions_FixedCount = bool(false)
Default_NanoPBOptions_SubmsgCallback = bool(false)
Default_NanoPBOptions_MangleNames = TypenameMangling_M_NONE
Default_NanoPBOptions_CallbackDatatype = string("pb_callback_t")
Default_NanoPBOptions_CallbackFunction = string("pb_default_field_callback")
Default_NanoPBOptions_Descriptorsize = DescriptorSize_DS_AUTO
Default_NanoPBOptions_DefaultHas = bool(false)
Default_NanoPBOptions_SortByTag = bool(true)
Default_NanoPBOptions_FallbackType = FieldType_FT_CALLBACK
)
func (x *NanoPBOptions) Reset() {
*x = NanoPBOptions{}
mi := &file_nanopb_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *NanoPBOptions) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NanoPBOptions) ProtoMessage() {}
func (x *NanoPBOptions) ProtoReflect() protoreflect.Message {
mi := &file_nanopb_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NanoPBOptions.ProtoReflect.Descriptor instead.
func (*NanoPBOptions) Descriptor() ([]byte, []int) {
return file_nanopb_proto_rawDescGZIP(), []int{0}
}
func (x *NanoPBOptions) GetMaxSize() int32 {
if x != nil && x.MaxSize != nil {
return *x.MaxSize
}
return 0
}
func (x *NanoPBOptions) GetMaxLength() int32 {
if x != nil && x.MaxLength != nil {
return *x.MaxLength
}
return 0
}
func (x *NanoPBOptions) GetMaxCount() int32 {
if x != nil && x.MaxCount != nil {
return *x.MaxCount
}
return 0
}
func (x *NanoPBOptions) GetIntSize() IntSize {
if x != nil && x.IntSize != nil {
return *x.IntSize
}
return Default_NanoPBOptions_IntSize
}
func (x *NanoPBOptions) GetType() FieldType {
if x != nil && x.Type != nil {
return *x.Type
}
return Default_NanoPBOptions_Type
}
func (x *NanoPBOptions) GetLongNames() bool {
if x != nil && x.LongNames != nil {
return *x.LongNames
}
return Default_NanoPBOptions_LongNames
}
func (x *NanoPBOptions) GetPackedStruct() bool {
if x != nil && x.PackedStruct != nil {
return *x.PackedStruct
}
return Default_NanoPBOptions_PackedStruct
}
func (x *NanoPBOptions) GetPackedEnum() bool {
if x != nil && x.PackedEnum != nil {
return *x.PackedEnum
}
return Default_NanoPBOptions_PackedEnum
}
func (x *NanoPBOptions) GetSkipMessage() bool {
if x != nil && x.SkipMessage != nil {
return *x.SkipMessage
}
return Default_NanoPBOptions_SkipMessage
}
func (x *NanoPBOptions) GetNoUnions() bool {
if x != nil && x.NoUnions != nil {
return *x.NoUnions
}
return Default_NanoPBOptions_NoUnions
}
func (x *NanoPBOptions) GetMsgid() uint32 {
if x != nil && x.Msgid != nil {
return *x.Msgid
}
return 0
}
func (x *NanoPBOptions) GetAnonymousOneof() bool {
if x != nil && x.AnonymousOneof != nil {
return *x.AnonymousOneof
}
return Default_NanoPBOptions_AnonymousOneof
}
func (x *NanoPBOptions) GetProto3() bool {
if x != nil && x.Proto3 != nil {
return *x.Proto3
}
return Default_NanoPBOptions_Proto3
}
func (x *NanoPBOptions) GetProto3SingularMsgs() bool {
if x != nil && x.Proto3SingularMsgs != nil {
return *x.Proto3SingularMsgs
}
return Default_NanoPBOptions_Proto3SingularMsgs
}
func (x *NanoPBOptions) GetEnumToString() bool {
if x != nil && x.EnumToString != nil {
return *x.EnumToString
}
return Default_NanoPBOptions_EnumToString
}
func (x *NanoPBOptions) GetFixedLength() bool {
if x != nil && x.FixedLength != nil {
return *x.FixedLength
}
return Default_NanoPBOptions_FixedLength
}
func (x *NanoPBOptions) GetFixedCount() bool {
if x != nil && x.FixedCount != nil {
return *x.FixedCount
}
return Default_NanoPBOptions_FixedCount
}
func (x *NanoPBOptions) GetSubmsgCallback() bool {
if x != nil && x.SubmsgCallback != nil {
return *x.SubmsgCallback
}
return Default_NanoPBOptions_SubmsgCallback
}
func (x *NanoPBOptions) GetMangleNames() TypenameMangling {
if x != nil && x.MangleNames != nil {
return *x.MangleNames
}
return Default_NanoPBOptions_MangleNames
}
func (x *NanoPBOptions) GetCallbackDatatype() string {
if x != nil && x.CallbackDatatype != nil {
return *x.CallbackDatatype
}
return Default_NanoPBOptions_CallbackDatatype
}
func (x *NanoPBOptions) GetCallbackFunction() string {
if x != nil && x.CallbackFunction != nil {
return *x.CallbackFunction
}
return Default_NanoPBOptions_CallbackFunction
}
func (x *NanoPBOptions) GetDescriptorsize() DescriptorSize {
if x != nil && x.Descriptorsize != nil {
return *x.Descriptorsize
}
return Default_NanoPBOptions_Descriptorsize
}
func (x *NanoPBOptions) GetDefaultHas() bool {
if x != nil && x.DefaultHas != nil {
return *x.DefaultHas
}
return Default_NanoPBOptions_DefaultHas
}
func (x *NanoPBOptions) GetInclude() []string {
if x != nil {
return x.Include
}
return nil
}
func (x *NanoPBOptions) GetExclude() []string {
if x != nil {
return x.Exclude
}
return nil
}
func (x *NanoPBOptions) GetPackage() string {
if x != nil && x.Package != nil {
return *x.Package
}
return ""
}
func (x *NanoPBOptions) GetTypeOverride() descriptorpb.FieldDescriptorProto_Type {
if x != nil && x.TypeOverride != nil {
return *x.TypeOverride
}
return descriptorpb.FieldDescriptorProto_Type(1)
}
func (x *NanoPBOptions) GetSortByTag() bool {
if x != nil && x.SortByTag != nil {
return *x.SortByTag
}
return Default_NanoPBOptions_SortByTag
}
func (x *NanoPBOptions) GetFallbackType() FieldType {
if x != nil && x.FallbackType != nil {
return *x.FallbackType
}
return Default_NanoPBOptions_FallbackType
}
var file_nanopb_proto_extTypes = []protoimpl.ExtensionInfo{
{
ExtendedType: (*descriptorpb.FileOptions)(nil),
ExtensionType: (*NanoPBOptions)(nil),
Field: 1010,
Name: "nanopb_fileopt",
Tag: "bytes,1010,opt,name=nanopb_fileopt",
Filename: "nanopb.proto",
},
{
ExtendedType: (*descriptorpb.MessageOptions)(nil),
ExtensionType: (*NanoPBOptions)(nil),
Field: 1010,
Name: "nanopb_msgopt",
Tag: "bytes,1010,opt,name=nanopb_msgopt",
Filename: "nanopb.proto",
},
{
ExtendedType: (*descriptorpb.EnumOptions)(nil),
ExtensionType: (*NanoPBOptions)(nil),
Field: 1010,
Name: "nanopb_enumopt",
Tag: "bytes,1010,opt,name=nanopb_enumopt",
Filename: "nanopb.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*NanoPBOptions)(nil),
Field: 1010,
Name: "nanopb",
Tag: "bytes,1010,opt,name=nanopb",
Filename: "nanopb.proto",
},
}
// Extension fields to descriptorpb.FileOptions.
var (
// optional NanoPBOptions nanopb_fileopt = 1010;
E_NanopbFileopt = &file_nanopb_proto_extTypes[0]
)
// Extension fields to descriptorpb.MessageOptions.
var (
// optional NanoPBOptions nanopb_msgopt = 1010;
E_NanopbMsgopt = &file_nanopb_proto_extTypes[1]
)
// Extension fields to descriptorpb.EnumOptions.
var (
// optional NanoPBOptions nanopb_enumopt = 1010;
E_NanopbEnumopt = &file_nanopb_proto_extTypes[2]
)
// Extension fields to descriptorpb.FieldOptions.
var (
// optional NanoPBOptions nanopb = 1010;
E_Nanopb = &file_nanopb_proto_extTypes[3]
)
var File_nanopb_proto protoreflect.FileDescriptor
const file_nanopb_proto_rawDesc = "" +
"\n" +
"\fnanopb.proto\x1a google/protobuf/descriptor.proto\"\x86\n" +
"\n" +
"\rNanoPBOptions\x12\x19\n" +
"\bmax_size\x18\x01 \x01(\x05R\amaxSize\x12\x1d\n" +
"\n" +
"max_length\x18\x0e \x01(\x05R\tmaxLength\x12\x1b\n" +
"\tmax_count\x18\x02 \x01(\x05R\bmaxCount\x12/\n" +
"\bint_size\x18\a \x01(\x0e2\b.IntSize:\n" +
"IS_DEFAULTR\aintSize\x12*\n" +
"\x04type\x18\x03 \x01(\x0e2\n" +
".FieldType:\n" +
"FT_DEFAULTR\x04type\x12#\n" +
"\n" +
"long_names\x18\x04 \x01(\b:\x04trueR\tlongNames\x12*\n" +
"\rpacked_struct\x18\x05 \x01(\b:\x05falseR\fpackedStruct\x12&\n" +
"\vpacked_enum\x18\n" +
" \x01(\b:\x05falseR\n" +
"packedEnum\x12(\n" +
"\fskip_message\x18\x06 \x01(\b:\x05falseR\vskipMessage\x12\"\n" +
"\tno_unions\x18\b \x01(\b:\x05falseR\bnoUnions\x12\x14\n" +
"\x05msgid\x18\t \x01(\rR\x05msgid\x12.\n" +
"\x0fanonymous_oneof\x18\v \x01(\b:\x05falseR\x0eanonymousOneof\x12\x1d\n" +
"\x06proto3\x18\f \x01(\b:\x05falseR\x06proto3\x127\n" +
"\x14proto3_singular_msgs\x18\x15 \x01(\b:\x05falseR\x12proto3SingularMsgs\x12+\n" +
"\x0eenum_to_string\x18\r \x01(\b:\x05falseR\fenumToString\x12(\n" +
"\ffixed_length\x18\x0f \x01(\b:\x05falseR\vfixedLength\x12&\n" +
"\vfixed_count\x18\x10 \x01(\b:\x05falseR\n" +
"fixedCount\x12.\n" +
"\x0fsubmsg_callback\x18\x16 \x01(\b:\x05falseR\x0esubmsgCallback\x12<\n" +
"\fmangle_names\x18\x11 \x01(\x0e2\x11.TypenameMangling:\x06M_NONER\vmangleNames\x12:\n" +
"\x11callback_datatype\x18\x12 \x01(\t:\rpb_callback_tR\x10callbackDatatype\x12F\n" +
"\x11callback_function\x18\x13 \x01(\t:\x19pb_default_field_callbackR\x10callbackFunction\x12@\n" +
"\x0edescriptorsize\x18\x14 \x01(\x0e2\x0f.DescriptorSize:\aDS_AUTOR\x0edescriptorsize\x12&\n" +
"\vdefault_has\x18\x17 \x01(\b:\x05falseR\n" +
"defaultHas\x12\x18\n" +
"\ainclude\x18\x18 \x03(\tR\ainclude\x12\x18\n" +
"\aexclude\x18\x1a \x03(\tR\aexclude\x12\x18\n" +
"\apackage\x18\x19 \x01(\tR\apackage\x12O\n" +
"\rtype_override\x18\x1b \x01(\x0e2*.google.protobuf.FieldDescriptorProto.TypeR\ftypeOverride\x12$\n" +
"\vsort_by_tag\x18\x1c \x01(\b:\x04trueR\tsortByTag\x12<\n" +
"\rfallback_type\x18\x1d \x01(\x0e2\n" +
".FieldType:\vFT_CALLBACKR\ffallbackType*i\n" +
"\tFieldType\x12\x0e\n" +
"\n" +
"FT_DEFAULT\x10\x00\x12\x0f\n" +
"\vFT_CALLBACK\x10\x01\x12\x0e\n" +
"\n" +
"FT_POINTER\x10\x04\x12\r\n" +
"\tFT_STATIC\x10\x02\x12\r\n" +
"\tFT_IGNORE\x10\x03\x12\r\n" +
"\tFT_INLINE\x10\x05*D\n" +
"\aIntSize\x12\x0e\n" +
"\n" +
"IS_DEFAULT\x10\x00\x12\b\n" +
"\x04IS_8\x10\b\x12\t\n" +
"\x05IS_16\x10\x10\x12\t\n" +
"\x05IS_32\x10 \x12\t\n" +
"\x05IS_64\x10@*Z\n" +
"\x10TypenameMangling\x12\n" +
"\n" +
"\x06M_NONE\x10\x00\x12\x13\n" +
"\x0fM_STRIP_PACKAGE\x10\x01\x12\r\n" +
"\tM_FLATTEN\x10\x02\x12\x16\n" +
"\x12M_PACKAGE_INITIALS\x10\x03*E\n" +
"\x0eDescriptorSize\x12\v\n" +
"\aDS_AUTO\x10\x00\x12\b\n" +
"\x04DS_1\x10\x01\x12\b\n" +
"\x04DS_2\x10\x02\x12\b\n" +
"\x04DS_4\x10\x04\x12\b\n" +
"\x04DS_8\x10\b:T\n" +
"\x0enanopb_fileopt\x12\x1c.google.protobuf.FileOptions\x18\xf2\a \x01(\v2\x0e.NanoPBOptionsR\rnanopbFileopt:U\n" +
"\rnanopb_msgopt\x12\x1f.google.protobuf.MessageOptions\x18\xf2\a \x01(\v2\x0e.NanoPBOptionsR\fnanopbMsgopt:T\n" +
"\x0enanopb_enumopt\x12\x1c.google.protobuf.EnumOptions\x18\xf2\a \x01(\v2\x0e.NanoPBOptionsR\rnanopbEnumopt:F\n" +
"\x06nanopb\x12\x1d.google.protobuf.FieldOptions\x18\xf2\a \x01(\v2\x0e.NanoPBOptionsR\x06nanopbB>\n" +
"\x18fi.kapsi.koti.jpa.nanopbZ\"github.com/meshtastic/go/generated"
var (
file_nanopb_proto_rawDescOnce sync.Once
file_nanopb_proto_rawDescData []byte
)
func file_nanopb_proto_rawDescGZIP() []byte {
file_nanopb_proto_rawDescOnce.Do(func() {
file_nanopb_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_nanopb_proto_rawDesc), len(file_nanopb_proto_rawDesc)))
})
return file_nanopb_proto_rawDescData
}
var file_nanopb_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
var file_nanopb_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_nanopb_proto_goTypes = []any{
(FieldType)(0), // 0: FieldType
(IntSize)(0), // 1: IntSize
(TypenameMangling)(0), // 2: TypenameMangling
(DescriptorSize)(0), // 3: DescriptorSize
(*NanoPBOptions)(nil), // 4: NanoPBOptions
(descriptorpb.FieldDescriptorProto_Type)(0), // 5: google.protobuf.FieldDescriptorProto.Type
(*descriptorpb.FileOptions)(nil), // 6: google.protobuf.FileOptions
(*descriptorpb.MessageOptions)(nil), // 7: google.protobuf.MessageOptions
(*descriptorpb.EnumOptions)(nil), // 8: google.protobuf.EnumOptions
(*descriptorpb.FieldOptions)(nil), // 9: google.protobuf.FieldOptions
}
var file_nanopb_proto_depIdxs = []int32{
1, // 0: NanoPBOptions.int_size:type_name -> IntSize
0, // 1: NanoPBOptions.type:type_name -> FieldType
2, // 2: NanoPBOptions.mangle_names:type_name -> TypenameMangling
3, // 3: NanoPBOptions.descriptorsize:type_name -> DescriptorSize
5, // 4: NanoPBOptions.type_override:type_name -> google.protobuf.FieldDescriptorProto.Type
0, // 5: NanoPBOptions.fallback_type:type_name -> FieldType
6, // 6: nanopb_fileopt:extendee -> google.protobuf.FileOptions
7, // 7: nanopb_msgopt:extendee -> google.protobuf.MessageOptions
8, // 8: nanopb_enumopt:extendee -> google.protobuf.EnumOptions
9, // 9: nanopb:extendee -> google.protobuf.FieldOptions
4, // 10: nanopb_fileopt:type_name -> NanoPBOptions
4, // 11: nanopb_msgopt:type_name -> NanoPBOptions
4, // 12: nanopb_enumopt:type_name -> NanoPBOptions
4, // 13: nanopb:type_name -> NanoPBOptions
14, // [14:14] is the sub-list for method output_type
14, // [14:14] is the sub-list for method input_type
10, // [10:14] is the sub-list for extension type_name
6, // [6:10] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_nanopb_proto_init() }
func file_nanopb_proto_init() {
if File_nanopb_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_nanopb_proto_rawDesc), len(file_nanopb_proto_rawDesc)),
NumEnums: 4,
NumMessages: 1,
NumExtensions: 4,
NumServices: 0,
},
GoTypes: file_nanopb_proto_goTypes,
DependencyIndexes: file_nanopb_proto_depIdxs,
EnumInfos: file_nanopb_proto_enumTypes,
MessageInfos: file_nanopb_proto_msgTypes,
ExtensionInfos: file_nanopb_proto_extTypes,
}.Build()
File_nanopb_proto = out.File
file_nanopb_proto_goTypes = nil
file_nanopb_proto_depIdxs = nil
}

View File

@@ -0,0 +1,146 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/paxcount.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// TODO: REPLACE
type Paxcount struct {
state protoimpl.MessageState `protogen:"open.v1"`
// seen Wifi devices
Wifi uint32 `protobuf:"varint,1,opt,name=wifi,proto3" json:"wifi,omitempty"`
// Seen BLE devices
Ble uint32 `protobuf:"varint,2,opt,name=ble,proto3" json:"ble,omitempty"`
// Uptime in seconds
Uptime uint32 `protobuf:"varint,3,opt,name=uptime,proto3" json:"uptime,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Paxcount) Reset() {
*x = Paxcount{}
mi := &file_meshtastic_paxcount_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Paxcount) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Paxcount) ProtoMessage() {}
func (x *Paxcount) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_paxcount_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Paxcount.ProtoReflect.Descriptor instead.
func (*Paxcount) Descriptor() ([]byte, []int) {
return file_meshtastic_paxcount_proto_rawDescGZIP(), []int{0}
}
func (x *Paxcount) GetWifi() uint32 {
if x != nil {
return x.Wifi
}
return 0
}
func (x *Paxcount) GetBle() uint32 {
if x != nil {
return x.Ble
}
return 0
}
func (x *Paxcount) GetUptime() uint32 {
if x != nil {
return x.Uptime
}
return 0
}
var File_meshtastic_paxcount_proto protoreflect.FileDescriptor
const file_meshtastic_paxcount_proto_rawDesc = "" +
"\n" +
"\x19meshtastic/paxcount.proto\x12\n" +
"meshtastic\"H\n" +
"\bPaxcount\x12\x12\n" +
"\x04wifi\x18\x01 \x01(\rR\x04wifi\x12\x10\n" +
"\x03ble\x18\x02 \x01(\rR\x03ble\x12\x16\n" +
"\x06uptime\x18\x03 \x01(\rR\x06uptimeBd\n" +
"\x14org.meshtastic.protoB\x0ePaxcountProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_paxcount_proto_rawDescOnce sync.Once
file_meshtastic_paxcount_proto_rawDescData []byte
)
func file_meshtastic_paxcount_proto_rawDescGZIP() []byte {
file_meshtastic_paxcount_proto_rawDescOnce.Do(func() {
file_meshtastic_paxcount_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_paxcount_proto_rawDesc), len(file_meshtastic_paxcount_proto_rawDesc)))
})
return file_meshtastic_paxcount_proto_rawDescData
}
var file_meshtastic_paxcount_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_meshtastic_paxcount_proto_goTypes = []any{
(*Paxcount)(nil), // 0: meshtastic.Paxcount
}
var file_meshtastic_paxcount_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_meshtastic_paxcount_proto_init() }
func file_meshtastic_paxcount_proto_init() {
if File_meshtastic_paxcount_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_paxcount_proto_rawDesc), len(file_meshtastic_paxcount_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_paxcount_proto_goTypes,
DependencyIndexes: file_meshtastic_paxcount_proto_depIdxs,
MessageInfos: file_meshtastic_paxcount_proto_msgTypes,
}.Build()
File_meshtastic_paxcount_proto = out.File
file_meshtastic_paxcount_proto_goTypes = nil
file_meshtastic_paxcount_proto_depIdxs = nil
}

View File

@@ -0,0 +1,374 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/portnums.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// For any new 'apps' that run on the device or via sister apps on phones/PCs they should pick and use a
// unique 'portnum' for their application.
// If you are making a new app using meshtastic, please send in a pull request to add your 'portnum' to this
// master table.
// PortNums should be assigned in the following range:
// 0-63 Core Meshtastic use, do not use for third party apps
// 64-127 Registered 3rd party apps, send in a pull request that adds a new entry to portnums.proto to register your application
// 256-511 Use one of these portnums for your private applications that you don't want to register publically
// All other values are reserved.
// Note: This was formerly a Type enum named 'typ' with the same id #
// We have change to this 'portnum' based scheme for specifying app handlers for particular payloads.
// This change is backwards compatible by treating the legacy OPAQUE/CLEAR_TEXT values identically.
type PortNum int32
const (
// Deprecated: do not use in new code (formerly called OPAQUE)
// A message sent from a device outside of the mesh, in a form the mesh does not understand
// NOTE: This must be 0, because it is documented in IMeshService.aidl to be so
// ENCODING: binary undefined
PortNum_UNKNOWN_APP PortNum = 0
// A simple UTF-8 text message, which even the little micros in the mesh
// can understand and show on their screen eventually in some circumstances
// even signal might send messages in this form (see below)
// ENCODING: UTF-8 Plaintext (?)
PortNum_TEXT_MESSAGE_APP PortNum = 1
// Reserved for built-in GPIO/example app.
// See remote_hardware.proto/HardwareMessage for details on the message sent/received to this port number
// ENCODING: Protobuf
PortNum_REMOTE_HARDWARE_APP PortNum = 2
// The built-in position messaging app.
// Payload is a Position message.
// ENCODING: Protobuf
PortNum_POSITION_APP PortNum = 3
// The built-in user info app.
// Payload is a User message.
// ENCODING: Protobuf
PortNum_NODEINFO_APP PortNum = 4
// Protocol control packets for mesh protocol use.
// Payload is a Routing message.
// ENCODING: Protobuf
PortNum_ROUTING_APP PortNum = 5
// Admin control packets.
// Payload is a AdminMessage message.
// ENCODING: Protobuf
PortNum_ADMIN_APP PortNum = 6
// Compressed TEXT_MESSAGE payloads.
// ENCODING: UTF-8 Plaintext (?) with Unishox2 Compression
// NOTE: The Device Firmware converts a TEXT_MESSAGE_APP to TEXT_MESSAGE_COMPRESSED_APP if the compressed
// payload is shorter. There's no need for app developers to do this themselves. Also the firmware will decompress
// any incoming TEXT_MESSAGE_COMPRESSED_APP payload and convert to TEXT_MESSAGE_APP.
PortNum_TEXT_MESSAGE_COMPRESSED_APP PortNum = 7
// Waypoint payloads.
// Payload is a Waypoint message.
// ENCODING: Protobuf
PortNum_WAYPOINT_APP PortNum = 8
// Audio Payloads.
// Encapsulated codec2 packets. On 2.4 GHZ Bandwidths only for now
// ENCODING: codec2 audio frames
// NOTE: audio frames contain a 3 byte header (0xc0 0xde 0xc2) and a one byte marker for the decompressed bitrate.
// This marker comes from the 'moduleConfig.audio.bitrate' enum minus one.
PortNum_AUDIO_APP PortNum = 9
// Same as Text Message but originating from Detection Sensor Module.
// NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
PortNum_DETECTION_SENSOR_APP PortNum = 10
// Same as Text Message but used for critical alerts.
PortNum_ALERT_APP PortNum = 11
// Module/port for handling key verification requests.
PortNum_KEY_VERIFICATION_APP PortNum = 12
// Provides a 'ping' service that replies to any packet it receives.
// Also serves as a small example module.
// ENCODING: ASCII Plaintext
PortNum_REPLY_APP PortNum = 32
// Used for the python IP tunnel feature
// ENCODING: IP Packet. Handled by the python API, firmware ignores this one and pases on.
PortNum_IP_TUNNEL_APP PortNum = 33
// Paxcounter lib included in the firmware
// ENCODING: protobuf
PortNum_PAXCOUNTER_APP PortNum = 34
// Store and Forward++ module included in the firmware
// ENCODING: protobuf
// This module is specifically for Native Linux nodes, and provides a Git-style
// chain of messages.
PortNum_STORE_FORWARD_PLUSPLUS_APP PortNum = 35
// Node Status module
// ENCODING: protobuf
// This module allows setting an extra string of status for a node.
// Broadcasts on change and on a timer, possibly once a day.
PortNum_NODE_STATUS_APP PortNum = 36
// Provides a hardware serial interface to send and receive from the Meshtastic network.
// Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic
// network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network.
// Maximum packet size of 240 bytes.
// Module is disabled by default can be turned on by setting SERIAL_MODULE_ENABLED = 1 in SerialPlugh.cpp.
// ENCODING: binary undefined
PortNum_SERIAL_APP PortNum = 64
// STORE_FORWARD_APP (Work in Progress)
// Maintained by Jm Casler (MC Hamster) : jm@casler.org
// ENCODING: Protobuf
PortNum_STORE_FORWARD_APP PortNum = 65
// Optional port for messages for the range test module.
// ENCODING: ASCII Plaintext
// NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
PortNum_RANGE_TEST_APP PortNum = 66
// Provides a format to send and receive telemetry data from the Meshtastic network.
// Maintained by Charles Crossan (crossan007) : crossan007@gmail.com
// ENCODING: Protobuf
PortNum_TELEMETRY_APP PortNum = 67
// Experimental tools for estimating node position without a GPS
// Maintained by Github user a-f-G-U-C (a Meshtastic contributor)
// Project files at https://github.com/a-f-G-U-C/Meshtastic-ZPS
// ENCODING: arrays of int64 fields
PortNum_ZPS_APP PortNum = 68
// Used to let multiple instances of Linux native applications communicate
// as if they did using their LoRa chip.
// Maintained by GitHub user GUVWAF.
// Project files at https://github.com/GUVWAF/Meshtasticator
// ENCODING: Protobuf (?)
PortNum_SIMULATOR_APP PortNum = 69
// Provides a traceroute functionality to show the route a packet towards
// a certain destination would take on the mesh. Contains a RouteDiscovery message as payload.
// ENCODING: Protobuf
PortNum_TRACEROUTE_APP PortNum = 70
// Aggregates edge info for the network by sending out a list of each node's neighbors
// ENCODING: Protobuf
PortNum_NEIGHBORINFO_APP PortNum = 71
// ATAK Plugin
// Portnum for payloads from the official Meshtastic ATAK plugin
PortNum_ATAK_PLUGIN PortNum = 72
// Provides unencrypted information about a node for consumption by a map via MQTT
PortNum_MAP_REPORT_APP PortNum = 73
// PowerStress based monitoring support (for automated power consumption testing)
PortNum_POWERSTRESS_APP PortNum = 74
// LoraWAN Payload Transport
// ENCODING: compact binary LoRaWAN uplink (10-byte RF metadata + PHY payload) - see LoRaWANBridgeModule
PortNum_LORAWAN_BRIDGE PortNum = 75
// Reticulum Network Stack Tunnel App
// ENCODING: Fragmented RNS Packet. Handled by Meshtastic RNS interface
PortNum_RETICULUM_TUNNEL_APP PortNum = 76
// App for transporting Cayenne Low Power Payload, popular for LoRaWAN sensor nodes. Offers ability to send
// arbitrary telemetry over meshtastic that is not covered by telemetry.proto
// ENCODING: CayenneLLP
PortNum_CAYENNE_APP PortNum = 77
// Private applications should use portnums >= 256.
// To simplify initial development and testing you can use "PRIVATE_APP"
// in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh))
PortNum_PRIVATE_APP PortNum = 256
// ATAK Forwarder Module https://github.com/paulmandal/atak-forwarder
// ENCODING: libcotshrink
PortNum_ATAK_FORWARDER PortNum = 257
// Currently we limit port nums to no higher than this value
PortNum_MAX PortNum = 511
)
// Enum value maps for PortNum.
var (
PortNum_name = map[int32]string{
0: "UNKNOWN_APP",
1: "TEXT_MESSAGE_APP",
2: "REMOTE_HARDWARE_APP",
3: "POSITION_APP",
4: "NODEINFO_APP",
5: "ROUTING_APP",
6: "ADMIN_APP",
7: "TEXT_MESSAGE_COMPRESSED_APP",
8: "WAYPOINT_APP",
9: "AUDIO_APP",
10: "DETECTION_SENSOR_APP",
11: "ALERT_APP",
12: "KEY_VERIFICATION_APP",
32: "REPLY_APP",
33: "IP_TUNNEL_APP",
34: "PAXCOUNTER_APP",
35: "STORE_FORWARD_PLUSPLUS_APP",
36: "NODE_STATUS_APP",
64: "SERIAL_APP",
65: "STORE_FORWARD_APP",
66: "RANGE_TEST_APP",
67: "TELEMETRY_APP",
68: "ZPS_APP",
69: "SIMULATOR_APP",
70: "TRACEROUTE_APP",
71: "NEIGHBORINFO_APP",
72: "ATAK_PLUGIN",
73: "MAP_REPORT_APP",
74: "POWERSTRESS_APP",
75: "LORAWAN_BRIDGE",
76: "RETICULUM_TUNNEL_APP",
77: "CAYENNE_APP",
256: "PRIVATE_APP",
257: "ATAK_FORWARDER",
511: "MAX",
}
PortNum_value = map[string]int32{
"UNKNOWN_APP": 0,
"TEXT_MESSAGE_APP": 1,
"REMOTE_HARDWARE_APP": 2,
"POSITION_APP": 3,
"NODEINFO_APP": 4,
"ROUTING_APP": 5,
"ADMIN_APP": 6,
"TEXT_MESSAGE_COMPRESSED_APP": 7,
"WAYPOINT_APP": 8,
"AUDIO_APP": 9,
"DETECTION_SENSOR_APP": 10,
"ALERT_APP": 11,
"KEY_VERIFICATION_APP": 12,
"REPLY_APP": 32,
"IP_TUNNEL_APP": 33,
"PAXCOUNTER_APP": 34,
"STORE_FORWARD_PLUSPLUS_APP": 35,
"NODE_STATUS_APP": 36,
"SERIAL_APP": 64,
"STORE_FORWARD_APP": 65,
"RANGE_TEST_APP": 66,
"TELEMETRY_APP": 67,
"ZPS_APP": 68,
"SIMULATOR_APP": 69,
"TRACEROUTE_APP": 70,
"NEIGHBORINFO_APP": 71,
"ATAK_PLUGIN": 72,
"MAP_REPORT_APP": 73,
"POWERSTRESS_APP": 74,
"LORAWAN_BRIDGE": 75,
"RETICULUM_TUNNEL_APP": 76,
"CAYENNE_APP": 77,
"PRIVATE_APP": 256,
"ATAK_FORWARDER": 257,
"MAX": 511,
}
)
func (x PortNum) Enum() *PortNum {
p := new(PortNum)
*p = x
return p
}
func (x PortNum) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PortNum) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_portnums_proto_enumTypes[0].Descriptor()
}
func (PortNum) Type() protoreflect.EnumType {
return &file_meshtastic_portnums_proto_enumTypes[0]
}
func (x PortNum) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PortNum.Descriptor instead.
func (PortNum) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_portnums_proto_rawDescGZIP(), []int{0}
}
var File_meshtastic_portnums_proto protoreflect.FileDescriptor
const file_meshtastic_portnums_proto_rawDesc = "" +
"\n" +
"\x19meshtastic/portnums.proto\x12\n" +
"meshtastic*\xbf\x05\n" +
"\aPortNum\x12\x0f\n" +
"\vUNKNOWN_APP\x10\x00\x12\x14\n" +
"\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n" +
"\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n" +
"\fPOSITION_APP\x10\x03\x12\x10\n" +
"\fNODEINFO_APP\x10\x04\x12\x0f\n" +
"\vROUTING_APP\x10\x05\x12\r\n" +
"\tADMIN_APP\x10\x06\x12\x1f\n" +
"\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\a\x12\x10\n" +
"\fWAYPOINT_APP\x10\b\x12\r\n" +
"\tAUDIO_APP\x10\t\x12\x18\n" +
"\x14DETECTION_SENSOR_APP\x10\n" +
"\x12\r\n" +
"\tALERT_APP\x10\v\x12\x18\n" +
"\x14KEY_VERIFICATION_APP\x10\f\x12\r\n" +
"\tREPLY_APP\x10 \x12\x11\n" +
"\rIP_TUNNEL_APP\x10!\x12\x12\n" +
"\x0ePAXCOUNTER_APP\x10\"\x12\x1e\n" +
"\x1aSTORE_FORWARD_PLUSPLUS_APP\x10#\x12\x13\n" +
"\x0fNODE_STATUS_APP\x10$\x12\x0e\n" +
"\n" +
"SERIAL_APP\x10@\x12\x15\n" +
"\x11STORE_FORWARD_APP\x10A\x12\x12\n" +
"\x0eRANGE_TEST_APP\x10B\x12\x11\n" +
"\rTELEMETRY_APP\x10C\x12\v\n" +
"\aZPS_APP\x10D\x12\x11\n" +
"\rSIMULATOR_APP\x10E\x12\x12\n" +
"\x0eTRACEROUTE_APP\x10F\x12\x14\n" +
"\x10NEIGHBORINFO_APP\x10G\x12\x0f\n" +
"\vATAK_PLUGIN\x10H\x12\x12\n" +
"\x0eMAP_REPORT_APP\x10I\x12\x13\n" +
"\x0fPOWERSTRESS_APP\x10J\x12\x12\n" +
"\x0eLORAWAN_BRIDGE\x10K\x12\x18\n" +
"\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n" +
"\vCAYENNE_APP\x10M\x12\x10\n" +
"\vPRIVATE_APP\x10\x80\x02\x12\x13\n" +
"\x0eATAK_FORWARDER\x10\x81\x02\x12\b\n" +
"\x03MAX\x10\xff\x03B^\n" +
"\x14org.meshtastic.protoB\bPortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_portnums_proto_rawDescOnce sync.Once
file_meshtastic_portnums_proto_rawDescData []byte
)
func file_meshtastic_portnums_proto_rawDescGZIP() []byte {
file_meshtastic_portnums_proto_rawDescOnce.Do(func() {
file_meshtastic_portnums_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_portnums_proto_rawDesc), len(file_meshtastic_portnums_proto_rawDesc)))
})
return file_meshtastic_portnums_proto_rawDescData
}
var file_meshtastic_portnums_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_meshtastic_portnums_proto_goTypes = []any{
(PortNum)(0), // 0: meshtastic.PortNum
}
var file_meshtastic_portnums_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_meshtastic_portnums_proto_init() }
func file_meshtastic_portnums_proto_init() {
if File_meshtastic_portnums_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_portnums_proto_rawDesc), len(file_meshtastic_portnums_proto_rawDesc)),
NumEnums: 1,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_portnums_proto_goTypes,
DependencyIndexes: file_meshtastic_portnums_proto_depIdxs,
EnumInfos: file_meshtastic_portnums_proto_enumTypes,
}.Build()
File_meshtastic_portnums_proto = out.File
file_meshtastic_portnums_proto_goTypes = nil
file_meshtastic_portnums_proto_depIdxs = nil
}

View File

@@ -0,0 +1,418 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/powermon.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Any significant power changing event in meshtastic should be tagged with a powermon state transition.
// If you are making new meshtastic features feel free to add new entries at the end of this definition.
type PowerMon_State int32
const (
PowerMon_None PowerMon_State = 0
PowerMon_CPU_DeepSleep PowerMon_State = 1
PowerMon_CPU_LightSleep PowerMon_State = 2
// The external Vext1 power is on. Many boards have auxillary power rails that the CPU turns on only
// occasionally. In cases where that rail has multiple devices on it we usually want to have logging on
// the state of that rail as an independent record.
// For instance on the Heltec Tracker 1.1 board, this rail is the power source for the GPS and screen.
//
// The log messages will be short and complete (see PowerMon.Event in the protobufs for details).
// something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of all current states.
// (We use a bitmask for states so that if a log message gets lost it won't be fatal)
PowerMon_Vext1_On PowerMon_State = 4
PowerMon_Lora_RXOn PowerMon_State = 8
PowerMon_Lora_TXOn PowerMon_State = 16
PowerMon_Lora_RXActive PowerMon_State = 32
PowerMon_BT_On PowerMon_State = 64
PowerMon_LED_On PowerMon_State = 128
PowerMon_Screen_On PowerMon_State = 256
PowerMon_Screen_Drawing PowerMon_State = 512
PowerMon_Wifi_On PowerMon_State = 1024
// GPS is actively trying to find our location
// See GPSPowerState for more details
PowerMon_GPS_Active PowerMon_State = 2048
)
// Enum value maps for PowerMon_State.
var (
PowerMon_State_name = map[int32]string{
0: "None",
1: "CPU_DeepSleep",
2: "CPU_LightSleep",
4: "Vext1_On",
8: "Lora_RXOn",
16: "Lora_TXOn",
32: "Lora_RXActive",
64: "BT_On",
128: "LED_On",
256: "Screen_On",
512: "Screen_Drawing",
1024: "Wifi_On",
2048: "GPS_Active",
}
PowerMon_State_value = map[string]int32{
"None": 0,
"CPU_DeepSleep": 1,
"CPU_LightSleep": 2,
"Vext1_On": 4,
"Lora_RXOn": 8,
"Lora_TXOn": 16,
"Lora_RXActive": 32,
"BT_On": 64,
"LED_On": 128,
"Screen_On": 256,
"Screen_Drawing": 512,
"Wifi_On": 1024,
"GPS_Active": 2048,
}
)
func (x PowerMon_State) Enum() *PowerMon_State {
p := new(PowerMon_State)
*p = x
return p
}
func (x PowerMon_State) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PowerMon_State) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_powermon_proto_enumTypes[0].Descriptor()
}
func (PowerMon_State) Type() protoreflect.EnumType {
return &file_meshtastic_powermon_proto_enumTypes[0]
}
func (x PowerMon_State) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PowerMon_State.Descriptor instead.
func (PowerMon_State) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_powermon_proto_rawDescGZIP(), []int{0, 0}
}
// What operation would we like the UUT to perform.
// note: senders should probably set want_response in their request packets, so that they can know when the state
// machine has started processing their request
type PowerStressMessage_Opcode int32
const (
// Unset/unused
PowerStressMessage_UNSET PowerStressMessage_Opcode = 0
PowerStressMessage_PRINT_INFO PowerStressMessage_Opcode = 1 // Print board version slog and send an ack that we are alive and ready to process commands
PowerStressMessage_FORCE_QUIET PowerStressMessage_Opcode = 2 // Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation)
PowerStressMessage_END_QUIET PowerStressMessage_Opcode = 3 // Stop powerstress processing - probably by just rebooting the board
PowerStressMessage_SCREEN_ON PowerStressMessage_Opcode = 16 // Turn the screen on
PowerStressMessage_SCREEN_OFF PowerStressMessage_Opcode = 17 // Turn the screen off
PowerStressMessage_CPU_IDLE PowerStressMessage_Opcode = 32 // Let the CPU run but we assume mostly idling for num_seconds
PowerStressMessage_CPU_DEEPSLEEP PowerStressMessage_Opcode = 33 // Force deep sleep for FIXME seconds
PowerStressMessage_CPU_FULLON PowerStressMessage_Opcode = 34 // Spin the CPU as fast as possible for num_seconds
PowerStressMessage_LED_ON PowerStressMessage_Opcode = 48 // Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes)
PowerStressMessage_LED_OFF PowerStressMessage_Opcode = 49 // Force the LED off for num_seconds
PowerStressMessage_LORA_OFF PowerStressMessage_Opcode = 64 // Completely turn off the LORA radio for num_seconds
PowerStressMessage_LORA_TX PowerStressMessage_Opcode = 65 // Send Lora packets for num_seconds
PowerStressMessage_LORA_RX PowerStressMessage_Opcode = 66 // Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel)
PowerStressMessage_BT_OFF PowerStressMessage_Opcode = 80 // Turn off the BT radio for num_seconds
PowerStressMessage_BT_ON PowerStressMessage_Opcode = 81 // Turn on the BT radio for num_seconds
PowerStressMessage_WIFI_OFF PowerStressMessage_Opcode = 96 // Turn off the WIFI radio for num_seconds
PowerStressMessage_WIFI_ON PowerStressMessage_Opcode = 97 // Turn on the WIFI radio for num_seconds
PowerStressMessage_GPS_OFF PowerStressMessage_Opcode = 112 // Turn off the GPS radio for num_seconds
PowerStressMessage_GPS_ON PowerStressMessage_Opcode = 113 // Turn on the GPS radio for num_seconds
)
// Enum value maps for PowerStressMessage_Opcode.
var (
PowerStressMessage_Opcode_name = map[int32]string{
0: "UNSET",
1: "PRINT_INFO",
2: "FORCE_QUIET",
3: "END_QUIET",
16: "SCREEN_ON",
17: "SCREEN_OFF",
32: "CPU_IDLE",
33: "CPU_DEEPSLEEP",
34: "CPU_FULLON",
48: "LED_ON",
49: "LED_OFF",
64: "LORA_OFF",
65: "LORA_TX",
66: "LORA_RX",
80: "BT_OFF",
81: "BT_ON",
96: "WIFI_OFF",
97: "WIFI_ON",
112: "GPS_OFF",
113: "GPS_ON",
}
PowerStressMessage_Opcode_value = map[string]int32{
"UNSET": 0,
"PRINT_INFO": 1,
"FORCE_QUIET": 2,
"END_QUIET": 3,
"SCREEN_ON": 16,
"SCREEN_OFF": 17,
"CPU_IDLE": 32,
"CPU_DEEPSLEEP": 33,
"CPU_FULLON": 34,
"LED_ON": 48,
"LED_OFF": 49,
"LORA_OFF": 64,
"LORA_TX": 65,
"LORA_RX": 66,
"BT_OFF": 80,
"BT_ON": 81,
"WIFI_OFF": 96,
"WIFI_ON": 97,
"GPS_OFF": 112,
"GPS_ON": 113,
}
)
func (x PowerStressMessage_Opcode) Enum() *PowerStressMessage_Opcode {
p := new(PowerStressMessage_Opcode)
*p = x
return p
}
func (x PowerStressMessage_Opcode) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PowerStressMessage_Opcode) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_powermon_proto_enumTypes[1].Descriptor()
}
func (PowerStressMessage_Opcode) Type() protoreflect.EnumType {
return &file_meshtastic_powermon_proto_enumTypes[1]
}
func (x PowerStressMessage_Opcode) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PowerStressMessage_Opcode.Descriptor instead.
func (PowerStressMessage_Opcode) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_powermon_proto_rawDescGZIP(), []int{1, 0}
}
// Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs).
// But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us)
type PowerMon struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PowerMon) Reset() {
*x = PowerMon{}
mi := &file_meshtastic_powermon_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PowerMon) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PowerMon) ProtoMessage() {}
func (x *PowerMon) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_powermon_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PowerMon.ProtoReflect.Descriptor instead.
func (*PowerMon) Descriptor() ([]byte, []int) {
return file_meshtastic_powermon_proto_rawDescGZIP(), []int{0}
}
// PowerStress testing support via the C++ PowerStress module
type PowerStressMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
// What type of HardwareMessage is this?
Cmd PowerStressMessage_Opcode `protobuf:"varint,1,opt,name=cmd,proto3,enum=meshtastic.PowerStressMessage_Opcode" json:"cmd,omitempty"`
NumSeconds float32 `protobuf:"fixed32,2,opt,name=num_seconds,json=numSeconds,proto3" json:"numSeconds,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PowerStressMessage) Reset() {
*x = PowerStressMessage{}
mi := &file_meshtastic_powermon_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PowerStressMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PowerStressMessage) ProtoMessage() {}
func (x *PowerStressMessage) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_powermon_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PowerStressMessage.ProtoReflect.Descriptor instead.
func (*PowerStressMessage) Descriptor() ([]byte, []int) {
return file_meshtastic_powermon_proto_rawDescGZIP(), []int{1}
}
func (x *PowerStressMessage) GetCmd() PowerStressMessage_Opcode {
if x != nil {
return x.Cmd
}
return PowerStressMessage_UNSET
}
func (x *PowerStressMessage) GetNumSeconds() float32 {
if x != nil {
return x.NumSeconds
}
return 0
}
var File_meshtastic_powermon_proto protoreflect.FileDescriptor
const file_meshtastic_powermon_proto_rawDesc = "" +
"\n" +
"\x19meshtastic/powermon.proto\x12\n" +
"meshtastic\"\xe0\x01\n" +
"\bPowerMon\"\xd3\x01\n" +
"\x05State\x12\b\n" +
"\x04None\x10\x00\x12\x11\n" +
"\rCPU_DeepSleep\x10\x01\x12\x12\n" +
"\x0eCPU_LightSleep\x10\x02\x12\f\n" +
"\bVext1_On\x10\x04\x12\r\n" +
"\tLora_RXOn\x10\b\x12\r\n" +
"\tLora_TXOn\x10\x10\x12\x11\n" +
"\rLora_RXActive\x10 \x12\t\n" +
"\x05BT_On\x10@\x12\v\n" +
"\x06LED_On\x10\x80\x01\x12\x0e\n" +
"\tScreen_On\x10\x80\x02\x12\x13\n" +
"\x0eScreen_Drawing\x10\x80\x04\x12\f\n" +
"\aWifi_On\x10\x80\b\x12\x0f\n" +
"\n" +
"GPS_Active\x10\x80\x10\"\x90\x03\n" +
"\x12PowerStressMessage\x127\n" +
"\x03cmd\x18\x01 \x01(\x0e2%.meshtastic.PowerStressMessage.OpcodeR\x03cmd\x12\x1f\n" +
"\vnum_seconds\x18\x02 \x01(\x02R\n" +
"numSeconds\"\x9f\x02\n" +
"\x06Opcode\x12\t\n" +
"\x05UNSET\x10\x00\x12\x0e\n" +
"\n" +
"PRINT_INFO\x10\x01\x12\x0f\n" +
"\vFORCE_QUIET\x10\x02\x12\r\n" +
"\tEND_QUIET\x10\x03\x12\r\n" +
"\tSCREEN_ON\x10\x10\x12\x0e\n" +
"\n" +
"SCREEN_OFF\x10\x11\x12\f\n" +
"\bCPU_IDLE\x10 \x12\x11\n" +
"\rCPU_DEEPSLEEP\x10!\x12\x0e\n" +
"\n" +
"CPU_FULLON\x10\"\x12\n" +
"\n" +
"\x06LED_ON\x100\x12\v\n" +
"\aLED_OFF\x101\x12\f\n" +
"\bLORA_OFF\x10@\x12\v\n" +
"\aLORA_TX\x10A\x12\v\n" +
"\aLORA_RX\x10B\x12\n" +
"\n" +
"\x06BT_OFF\x10P\x12\t\n" +
"\x05BT_ON\x10Q\x12\f\n" +
"\bWIFI_OFF\x10`\x12\v\n" +
"\aWIFI_ON\x10a\x12\v\n" +
"\aGPS_OFF\x10p\x12\n" +
"\n" +
"\x06GPS_ON\x10qBd\n" +
"\x14org.meshtastic.protoB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_powermon_proto_rawDescOnce sync.Once
file_meshtastic_powermon_proto_rawDescData []byte
)
func file_meshtastic_powermon_proto_rawDescGZIP() []byte {
file_meshtastic_powermon_proto_rawDescOnce.Do(func() {
file_meshtastic_powermon_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_powermon_proto_rawDesc), len(file_meshtastic_powermon_proto_rawDesc)))
})
return file_meshtastic_powermon_proto_rawDescData
}
var file_meshtastic_powermon_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_meshtastic_powermon_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_meshtastic_powermon_proto_goTypes = []any{
(PowerMon_State)(0), // 0: meshtastic.PowerMon.State
(PowerStressMessage_Opcode)(0), // 1: meshtastic.PowerStressMessage.Opcode
(*PowerMon)(nil), // 2: meshtastic.PowerMon
(*PowerStressMessage)(nil), // 3: meshtastic.PowerStressMessage
}
var file_meshtastic_powermon_proto_depIdxs = []int32{
1, // 0: meshtastic.PowerStressMessage.cmd:type_name -> meshtastic.PowerStressMessage.Opcode
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_meshtastic_powermon_proto_init() }
func file_meshtastic_powermon_proto_init() {
if File_meshtastic_powermon_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_powermon_proto_rawDesc), len(file_meshtastic_powermon_proto_rawDesc)),
NumEnums: 2,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_powermon_proto_goTypes,
DependencyIndexes: file_meshtastic_powermon_proto_depIdxs,
EnumInfos: file_meshtastic_powermon_proto_enumTypes,
MessageInfos: file_meshtastic_powermon_proto_msgTypes,
}.Build()
File_meshtastic_powermon_proto = out.File
file_meshtastic_powermon_proto_goTypes = nil
file_meshtastic_powermon_proto_depIdxs = nil
}

View File

@@ -0,0 +1,235 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/remote_hardware.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// TODO: REPLACE
type HardwareMessage_Type int32
const (
// Unset/unused
HardwareMessage_UNSET HardwareMessage_Type = 0
// Set gpio gpios based on gpio_mask/gpio_value
HardwareMessage_WRITE_GPIOS HardwareMessage_Type = 1
// We are now interested in watching the gpio_mask gpios.
// If the selected gpios change, please broadcast GPIOS_CHANGED.
// Will implicitly change the gpios requested to be INPUT gpios.
HardwareMessage_WATCH_GPIOS HardwareMessage_Type = 2
// The gpios listed in gpio_mask have changed, the new values are listed in gpio_value
HardwareMessage_GPIOS_CHANGED HardwareMessage_Type = 3
// Read the gpios specified in gpio_mask, send back a READ_GPIOS_REPLY reply with gpio_value populated
HardwareMessage_READ_GPIOS HardwareMessage_Type = 4
// A reply to READ_GPIOS. gpio_mask and gpio_value will be populated
HardwareMessage_READ_GPIOS_REPLY HardwareMessage_Type = 5
)
// Enum value maps for HardwareMessage_Type.
var (
HardwareMessage_Type_name = map[int32]string{
0: "UNSET",
1: "WRITE_GPIOS",
2: "WATCH_GPIOS",
3: "GPIOS_CHANGED",
4: "READ_GPIOS",
5: "READ_GPIOS_REPLY",
}
HardwareMessage_Type_value = map[string]int32{
"UNSET": 0,
"WRITE_GPIOS": 1,
"WATCH_GPIOS": 2,
"GPIOS_CHANGED": 3,
"READ_GPIOS": 4,
"READ_GPIOS_REPLY": 5,
}
)
func (x HardwareMessage_Type) Enum() *HardwareMessage_Type {
p := new(HardwareMessage_Type)
*p = x
return p
}
func (x HardwareMessage_Type) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (HardwareMessage_Type) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_remote_hardware_proto_enumTypes[0].Descriptor()
}
func (HardwareMessage_Type) Type() protoreflect.EnumType {
return &file_meshtastic_remote_hardware_proto_enumTypes[0]
}
func (x HardwareMessage_Type) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use HardwareMessage_Type.Descriptor instead.
func (HardwareMessage_Type) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_remote_hardware_proto_rawDescGZIP(), []int{0, 0}
}
// An example app to show off the module system. This message is used for
// REMOTE_HARDWARE_APP PortNums.
// Also provides easy remote access to any GPIO.
// In the future other remote hardware operations can be added based on user interest
// (i.e. serial output, spi/i2c input/output).
// FIXME - currently this feature is turned on by default which is dangerous
// because no security yet (beyond the channel mechanism).
// It should be off by default and then protected based on some TBD mechanism
// (a special channel once multichannel support is included?)
type HardwareMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
// What type of HardwareMessage is this?
Type HardwareMessage_Type `protobuf:"varint,1,opt,name=type,proto3,enum=meshtastic.HardwareMessage_Type" json:"type,omitempty"`
// What gpios are we changing. Not used for all MessageTypes, see MessageType for details
GpioMask uint64 `protobuf:"varint,2,opt,name=gpio_mask,json=gpioMask,proto3" json:"gpioMask,omitempty"`
// For gpios that were listed in gpio_mask as valid, what are the signal levels for those gpios.
// Not used for all MessageTypes, see MessageType for details
GpioValue uint64 `protobuf:"varint,3,opt,name=gpio_value,json=gpioValue,proto3" json:"gpioValue,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HardwareMessage) Reset() {
*x = HardwareMessage{}
mi := &file_meshtastic_remote_hardware_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HardwareMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HardwareMessage) ProtoMessage() {}
func (x *HardwareMessage) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_remote_hardware_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HardwareMessage.ProtoReflect.Descriptor instead.
func (*HardwareMessage) Descriptor() ([]byte, []int) {
return file_meshtastic_remote_hardware_proto_rawDescGZIP(), []int{0}
}
func (x *HardwareMessage) GetType() HardwareMessage_Type {
if x != nil {
return x.Type
}
return HardwareMessage_UNSET
}
func (x *HardwareMessage) GetGpioMask() uint64 {
if x != nil {
return x.GpioMask
}
return 0
}
func (x *HardwareMessage) GetGpioValue() uint64 {
if x != nil {
return x.GpioValue
}
return 0
}
var File_meshtastic_remote_hardware_proto protoreflect.FileDescriptor
const file_meshtastic_remote_hardware_proto_rawDesc = "" +
"\n" +
" meshtastic/remote_hardware.proto\x12\n" +
"meshtastic\"\xf1\x01\n" +
"\x0fHardwareMessage\x124\n" +
"\x04type\x18\x01 \x01(\x0e2 .meshtastic.HardwareMessage.TypeR\x04type\x12\x1b\n" +
"\tgpio_mask\x18\x02 \x01(\x04R\bgpioMask\x12\x1d\n" +
"\n" +
"gpio_value\x18\x03 \x01(\x04R\tgpioValue\"l\n" +
"\x04Type\x12\t\n" +
"\x05UNSET\x10\x00\x12\x0f\n" +
"\vWRITE_GPIOS\x10\x01\x12\x0f\n" +
"\vWATCH_GPIOS\x10\x02\x12\x11\n" +
"\rGPIOS_CHANGED\x10\x03\x12\x0e\n" +
"\n" +
"READ_GPIOS\x10\x04\x12\x14\n" +
"\x10READ_GPIOS_REPLY\x10\x05Bd\n" +
"\x14org.meshtastic.protoB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_remote_hardware_proto_rawDescOnce sync.Once
file_meshtastic_remote_hardware_proto_rawDescData []byte
)
func file_meshtastic_remote_hardware_proto_rawDescGZIP() []byte {
file_meshtastic_remote_hardware_proto_rawDescOnce.Do(func() {
file_meshtastic_remote_hardware_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_remote_hardware_proto_rawDesc), len(file_meshtastic_remote_hardware_proto_rawDesc)))
})
return file_meshtastic_remote_hardware_proto_rawDescData
}
var file_meshtastic_remote_hardware_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_meshtastic_remote_hardware_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_meshtastic_remote_hardware_proto_goTypes = []any{
(HardwareMessage_Type)(0), // 0: meshtastic.HardwareMessage.Type
(*HardwareMessage)(nil), // 1: meshtastic.HardwareMessage
}
var file_meshtastic_remote_hardware_proto_depIdxs = []int32{
0, // 0: meshtastic.HardwareMessage.type:type_name -> meshtastic.HardwareMessage.Type
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_meshtastic_remote_hardware_proto_init() }
func file_meshtastic_remote_hardware_proto_init() {
if File_meshtastic_remote_hardware_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_remote_hardware_proto_rawDesc), len(file_meshtastic_remote_hardware_proto_rawDesc)),
NumEnums: 1,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_remote_hardware_proto_goTypes,
DependencyIndexes: file_meshtastic_remote_hardware_proto_depIdxs,
EnumInfos: file_meshtastic_remote_hardware_proto_enumTypes,
MessageInfos: file_meshtastic_remote_hardware_proto_msgTypes,
}.Build()
File_meshtastic_remote_hardware_proto = out.File
file_meshtastic_remote_hardware_proto_goTypes = nil
file_meshtastic_remote_hardware_proto_depIdxs = nil
}

View File

@@ -0,0 +1,126 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/rtttl.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Canned message module configuration.
type RTTTLConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Ringtone for PWM Buzzer in RTTTL Format.
Ringtone string `protobuf:"bytes,1,opt,name=ringtone,proto3" json:"ringtone,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RTTTLConfig) Reset() {
*x = RTTTLConfig{}
mi := &file_meshtastic_rtttl_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RTTTLConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RTTTLConfig) ProtoMessage() {}
func (x *RTTTLConfig) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_rtttl_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RTTTLConfig.ProtoReflect.Descriptor instead.
func (*RTTTLConfig) Descriptor() ([]byte, []int) {
return file_meshtastic_rtttl_proto_rawDescGZIP(), []int{0}
}
func (x *RTTTLConfig) GetRingtone() string {
if x != nil {
return x.Ringtone
}
return ""
}
var File_meshtastic_rtttl_proto protoreflect.FileDescriptor
const file_meshtastic_rtttl_proto_rawDesc = "" +
"\n" +
"\x16meshtastic/rtttl.proto\x12\n" +
"meshtastic\")\n" +
"\vRTTTLConfig\x12\x1a\n" +
"\bringtone\x18\x01 \x01(\tR\bringtoneBg\n" +
"\x14org.meshtastic.protoB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_rtttl_proto_rawDescOnce sync.Once
file_meshtastic_rtttl_proto_rawDescData []byte
)
func file_meshtastic_rtttl_proto_rawDescGZIP() []byte {
file_meshtastic_rtttl_proto_rawDescOnce.Do(func() {
file_meshtastic_rtttl_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_rtttl_proto_rawDesc), len(file_meshtastic_rtttl_proto_rawDesc)))
})
return file_meshtastic_rtttl_proto_rawDescData
}
var file_meshtastic_rtttl_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_meshtastic_rtttl_proto_goTypes = []any{
(*RTTTLConfig)(nil), // 0: meshtastic.RTTTLConfig
}
var file_meshtastic_rtttl_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_meshtastic_rtttl_proto_init() }
func file_meshtastic_rtttl_proto_init() {
if File_meshtastic_rtttl_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_rtttl_proto_rawDesc), len(file_meshtastic_rtttl_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_rtttl_proto_goTypes,
DependencyIndexes: file_meshtastic_rtttl_proto_depIdxs,
MessageInfos: file_meshtastic_rtttl_proto_msgTypes,
}.Build()
File_meshtastic_rtttl_proto = out.File
file_meshtastic_rtttl_proto_goTypes = nil
file_meshtastic_rtttl_proto_depIdxs = nil
}

View File

@@ -0,0 +1,613 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/storeforward.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 001 - 063 = From Router
// 064 - 127 = From Client
type StoreAndForward_RequestResponse int32
const (
// Unset/unused
StoreAndForward_UNSET StoreAndForward_RequestResponse = 0
// Router is an in error state.
StoreAndForward_ROUTER_ERROR StoreAndForward_RequestResponse = 1
// Router heartbeat
StoreAndForward_ROUTER_HEARTBEAT StoreAndForward_RequestResponse = 2
// Router has requested the client respond. This can work as a
// "are you there" message.
StoreAndForward_ROUTER_PING StoreAndForward_RequestResponse = 3
// The response to a "Ping"
StoreAndForward_ROUTER_PONG StoreAndForward_RequestResponse = 4
// Router is currently busy. Please try again later.
StoreAndForward_ROUTER_BUSY StoreAndForward_RequestResponse = 5
// Router is responding to a request for history.
StoreAndForward_ROUTER_HISTORY StoreAndForward_RequestResponse = 6
// Router is responding to a request for stats.
StoreAndForward_ROUTER_STATS StoreAndForward_RequestResponse = 7
// Router sends a text message from its history that was a direct message.
StoreAndForward_ROUTER_TEXT_DIRECT StoreAndForward_RequestResponse = 8
// Router sends a text message from its history that was a broadcast.
StoreAndForward_ROUTER_TEXT_BROADCAST StoreAndForward_RequestResponse = 9
// Client is an in error state.
StoreAndForward_CLIENT_ERROR StoreAndForward_RequestResponse = 64
// Client has requested a replay from the router.
StoreAndForward_CLIENT_HISTORY StoreAndForward_RequestResponse = 65
// Client has requested stats from the router.
StoreAndForward_CLIENT_STATS StoreAndForward_RequestResponse = 66
// Client has requested the router respond. This can work as a
// "are you there" message.
StoreAndForward_CLIENT_PING StoreAndForward_RequestResponse = 67
// The response to a "Ping"
StoreAndForward_CLIENT_PONG StoreAndForward_RequestResponse = 68
// Client has requested that the router abort processing the client's request
StoreAndForward_CLIENT_ABORT StoreAndForward_RequestResponse = 106
)
// Enum value maps for StoreAndForward_RequestResponse.
var (
StoreAndForward_RequestResponse_name = map[int32]string{
0: "UNSET",
1: "ROUTER_ERROR",
2: "ROUTER_HEARTBEAT",
3: "ROUTER_PING",
4: "ROUTER_PONG",
5: "ROUTER_BUSY",
6: "ROUTER_HISTORY",
7: "ROUTER_STATS",
8: "ROUTER_TEXT_DIRECT",
9: "ROUTER_TEXT_BROADCAST",
64: "CLIENT_ERROR",
65: "CLIENT_HISTORY",
66: "CLIENT_STATS",
67: "CLIENT_PING",
68: "CLIENT_PONG",
106: "CLIENT_ABORT",
}
StoreAndForward_RequestResponse_value = map[string]int32{
"UNSET": 0,
"ROUTER_ERROR": 1,
"ROUTER_HEARTBEAT": 2,
"ROUTER_PING": 3,
"ROUTER_PONG": 4,
"ROUTER_BUSY": 5,
"ROUTER_HISTORY": 6,
"ROUTER_STATS": 7,
"ROUTER_TEXT_DIRECT": 8,
"ROUTER_TEXT_BROADCAST": 9,
"CLIENT_ERROR": 64,
"CLIENT_HISTORY": 65,
"CLIENT_STATS": 66,
"CLIENT_PING": 67,
"CLIENT_PONG": 68,
"CLIENT_ABORT": 106,
}
)
func (x StoreAndForward_RequestResponse) Enum() *StoreAndForward_RequestResponse {
p := new(StoreAndForward_RequestResponse)
*p = x
return p
}
func (x StoreAndForward_RequestResponse) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (StoreAndForward_RequestResponse) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_storeforward_proto_enumTypes[0].Descriptor()
}
func (StoreAndForward_RequestResponse) Type() protoreflect.EnumType {
return &file_meshtastic_storeforward_proto_enumTypes[0]
}
func (x StoreAndForward_RequestResponse) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use StoreAndForward_RequestResponse.Descriptor instead.
func (StoreAndForward_RequestResponse) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_storeforward_proto_rawDescGZIP(), []int{0, 0}
}
// TODO: REPLACE
type StoreAndForward struct {
state protoimpl.MessageState `protogen:"open.v1"`
// TODO: REPLACE
Rr StoreAndForward_RequestResponse `protobuf:"varint,1,opt,name=rr,proto3,enum=meshtastic.StoreAndForward_RequestResponse" json:"rr,omitempty"`
// TODO: REPLACE
//
// Types that are valid to be assigned to Variant:
//
// *StoreAndForward_Stats
// *StoreAndForward_History_
// *StoreAndForward_Heartbeat_
// *StoreAndForward_Text
Variant isStoreAndForward_Variant `protobuf_oneof:"variant"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *StoreAndForward) Reset() {
*x = StoreAndForward{}
mi := &file_meshtastic_storeforward_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *StoreAndForward) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StoreAndForward) ProtoMessage() {}
func (x *StoreAndForward) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_storeforward_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StoreAndForward.ProtoReflect.Descriptor instead.
func (*StoreAndForward) Descriptor() ([]byte, []int) {
return file_meshtastic_storeforward_proto_rawDescGZIP(), []int{0}
}
func (x *StoreAndForward) GetRr() StoreAndForward_RequestResponse {
if x != nil {
return x.Rr
}
return StoreAndForward_UNSET
}
func (x *StoreAndForward) GetVariant() isStoreAndForward_Variant {
if x != nil {
return x.Variant
}
return nil
}
func (x *StoreAndForward) GetStats() *StoreAndForward_Statistics {
if x != nil {
if x, ok := x.Variant.(*StoreAndForward_Stats); ok {
return x.Stats
}
}
return nil
}
func (x *StoreAndForward) GetHistory() *StoreAndForward_History {
if x != nil {
if x, ok := x.Variant.(*StoreAndForward_History_); ok {
return x.History
}
}
return nil
}
func (x *StoreAndForward) GetHeartbeat() *StoreAndForward_Heartbeat {
if x != nil {
if x, ok := x.Variant.(*StoreAndForward_Heartbeat_); ok {
return x.Heartbeat
}
}
return nil
}
func (x *StoreAndForward) GetText() []byte {
if x != nil {
if x, ok := x.Variant.(*StoreAndForward_Text); ok {
return x.Text
}
}
return nil
}
type isStoreAndForward_Variant interface {
isStoreAndForward_Variant()
}
type StoreAndForward_Stats struct {
// TODO: REPLACE
Stats *StoreAndForward_Statistics `protobuf:"bytes,2,opt,name=stats,proto3,oneof"`
}
type StoreAndForward_History_ struct {
// TODO: REPLACE
History *StoreAndForward_History `protobuf:"bytes,3,opt,name=history,proto3,oneof"`
}
type StoreAndForward_Heartbeat_ struct {
// TODO: REPLACE
Heartbeat *StoreAndForward_Heartbeat `protobuf:"bytes,4,opt,name=heartbeat,proto3,oneof"`
}
type StoreAndForward_Text struct {
// Text from history message.
Text []byte `protobuf:"bytes,5,opt,name=text,proto3,oneof"`
}
func (*StoreAndForward_Stats) isStoreAndForward_Variant() {}
func (*StoreAndForward_History_) isStoreAndForward_Variant() {}
func (*StoreAndForward_Heartbeat_) isStoreAndForward_Variant() {}
func (*StoreAndForward_Text) isStoreAndForward_Variant() {}
// TODO: REPLACE
type StoreAndForward_Statistics struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Number of messages we have ever seen
MessagesTotal uint32 `protobuf:"varint,1,opt,name=messages_total,json=messagesTotal,proto3" json:"messagesTotal,omitempty"`
// Number of messages we have currently saved our history.
MessagesSaved uint32 `protobuf:"varint,2,opt,name=messages_saved,json=messagesSaved,proto3" json:"messagesSaved,omitempty"`
// Maximum number of messages we will save
MessagesMax uint32 `protobuf:"varint,3,opt,name=messages_max,json=messagesMax,proto3" json:"messagesMax,omitempty"`
// Router uptime in seconds
UpTime uint32 `protobuf:"varint,4,opt,name=up_time,json=upTime,proto3" json:"upTime,omitempty"`
// Number of times any client sent a request to the S&F.
Requests uint32 `protobuf:"varint,5,opt,name=requests,proto3" json:"requests,omitempty"`
// Number of times the history was requested.
RequestsHistory uint32 `protobuf:"varint,6,opt,name=requests_history,json=requestsHistory,proto3" json:"requestsHistory,omitempty"`
// Is the heartbeat enabled on the server?
Heartbeat bool `protobuf:"varint,7,opt,name=heartbeat,proto3" json:"heartbeat,omitempty"`
// Maximum number of messages the server will return.
ReturnMax uint32 `protobuf:"varint,8,opt,name=return_max,json=returnMax,proto3" json:"returnMax,omitempty"`
// Maximum history window in minutes the server will return messages from.
ReturnWindow uint32 `protobuf:"varint,9,opt,name=return_window,json=returnWindow,proto3" json:"returnWindow,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *StoreAndForward_Statistics) Reset() {
*x = StoreAndForward_Statistics{}
mi := &file_meshtastic_storeforward_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *StoreAndForward_Statistics) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StoreAndForward_Statistics) ProtoMessage() {}
func (x *StoreAndForward_Statistics) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_storeforward_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StoreAndForward_Statistics.ProtoReflect.Descriptor instead.
func (*StoreAndForward_Statistics) Descriptor() ([]byte, []int) {
return file_meshtastic_storeforward_proto_rawDescGZIP(), []int{0, 0}
}
func (x *StoreAndForward_Statistics) GetMessagesTotal() uint32 {
if x != nil {
return x.MessagesTotal
}
return 0
}
func (x *StoreAndForward_Statistics) GetMessagesSaved() uint32 {
if x != nil {
return x.MessagesSaved
}
return 0
}
func (x *StoreAndForward_Statistics) GetMessagesMax() uint32 {
if x != nil {
return x.MessagesMax
}
return 0
}
func (x *StoreAndForward_Statistics) GetUpTime() uint32 {
if x != nil {
return x.UpTime
}
return 0
}
func (x *StoreAndForward_Statistics) GetRequests() uint32 {
if x != nil {
return x.Requests
}
return 0
}
func (x *StoreAndForward_Statistics) GetRequestsHistory() uint32 {
if x != nil {
return x.RequestsHistory
}
return 0
}
func (x *StoreAndForward_Statistics) GetHeartbeat() bool {
if x != nil {
return x.Heartbeat
}
return false
}
func (x *StoreAndForward_Statistics) GetReturnMax() uint32 {
if x != nil {
return x.ReturnMax
}
return 0
}
func (x *StoreAndForward_Statistics) GetReturnWindow() uint32 {
if x != nil {
return x.ReturnWindow
}
return 0
}
// TODO: REPLACE
type StoreAndForward_History struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Number of that will be sent to the client
HistoryMessages uint32 `protobuf:"varint,1,opt,name=history_messages,json=historyMessages,proto3" json:"historyMessages,omitempty"`
// The window of messages that was used to filter the history client requested
Window uint32 `protobuf:"varint,2,opt,name=window,proto3" json:"window,omitempty"`
// Index in the packet history of the last message sent in a previous request to the server.
// Will be sent to the client before sending the history and can be set in a subsequent request to avoid getting packets the server already sent to the client.
LastRequest uint32 `protobuf:"varint,3,opt,name=last_request,json=lastRequest,proto3" json:"lastRequest,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *StoreAndForward_History) Reset() {
*x = StoreAndForward_History{}
mi := &file_meshtastic_storeforward_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *StoreAndForward_History) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StoreAndForward_History) ProtoMessage() {}
func (x *StoreAndForward_History) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_storeforward_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StoreAndForward_History.ProtoReflect.Descriptor instead.
func (*StoreAndForward_History) Descriptor() ([]byte, []int) {
return file_meshtastic_storeforward_proto_rawDescGZIP(), []int{0, 1}
}
func (x *StoreAndForward_History) GetHistoryMessages() uint32 {
if x != nil {
return x.HistoryMessages
}
return 0
}
func (x *StoreAndForward_History) GetWindow() uint32 {
if x != nil {
return x.Window
}
return 0
}
func (x *StoreAndForward_History) GetLastRequest() uint32 {
if x != nil {
return x.LastRequest
}
return 0
}
// TODO: REPLACE
type StoreAndForward_Heartbeat struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Period in seconds that the heartbeat is sent out that will be sent to the client
Period uint32 `protobuf:"varint,1,opt,name=period,proto3" json:"period,omitempty"`
// If set, this is not the primary Store & Forward router on the mesh
Secondary uint32 `protobuf:"varint,2,opt,name=secondary,proto3" json:"secondary,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *StoreAndForward_Heartbeat) Reset() {
*x = StoreAndForward_Heartbeat{}
mi := &file_meshtastic_storeforward_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *StoreAndForward_Heartbeat) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StoreAndForward_Heartbeat) ProtoMessage() {}
func (x *StoreAndForward_Heartbeat) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_storeforward_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StoreAndForward_Heartbeat.ProtoReflect.Descriptor instead.
func (*StoreAndForward_Heartbeat) Descriptor() ([]byte, []int) {
return file_meshtastic_storeforward_proto_rawDescGZIP(), []int{0, 2}
}
func (x *StoreAndForward_Heartbeat) GetPeriod() uint32 {
if x != nil {
return x.Period
}
return 0
}
func (x *StoreAndForward_Heartbeat) GetSecondary() uint32 {
if x != nil {
return x.Secondary
}
return 0
}
var File_meshtastic_storeforward_proto protoreflect.FileDescriptor
const file_meshtastic_storeforward_proto_rawDesc = "" +
"\n" +
"\x1dmeshtastic/storeforward.proto\x12\n" +
"meshtastic\"\xec\b\n" +
"\x0fStoreAndForward\x12;\n" +
"\x02rr\x18\x01 \x01(\x0e2+.meshtastic.StoreAndForward.RequestResponseR\x02rr\x12>\n" +
"\x05stats\x18\x02 \x01(\v2&.meshtastic.StoreAndForward.StatisticsH\x00R\x05stats\x12?\n" +
"\ahistory\x18\x03 \x01(\v2#.meshtastic.StoreAndForward.HistoryH\x00R\ahistory\x12E\n" +
"\theartbeat\x18\x04 \x01(\v2%.meshtastic.StoreAndForward.HeartbeatH\x00R\theartbeat\x12\x14\n" +
"\x04text\x18\x05 \x01(\fH\x00R\x04text\x1a\xbf\x02\n" +
"\n" +
"Statistics\x12%\n" +
"\x0emessages_total\x18\x01 \x01(\rR\rmessagesTotal\x12%\n" +
"\x0emessages_saved\x18\x02 \x01(\rR\rmessagesSaved\x12!\n" +
"\fmessages_max\x18\x03 \x01(\rR\vmessagesMax\x12\x17\n" +
"\aup_time\x18\x04 \x01(\rR\x06upTime\x12\x1a\n" +
"\brequests\x18\x05 \x01(\rR\brequests\x12)\n" +
"\x10requests_history\x18\x06 \x01(\rR\x0frequestsHistory\x12\x1c\n" +
"\theartbeat\x18\a \x01(\bR\theartbeat\x12\x1d\n" +
"\n" +
"return_max\x18\b \x01(\rR\treturnMax\x12#\n" +
"\rreturn_window\x18\t \x01(\rR\freturnWindow\x1ao\n" +
"\aHistory\x12)\n" +
"\x10history_messages\x18\x01 \x01(\rR\x0fhistoryMessages\x12\x16\n" +
"\x06window\x18\x02 \x01(\rR\x06window\x12!\n" +
"\flast_request\x18\x03 \x01(\rR\vlastRequest\x1aA\n" +
"\tHeartbeat\x12\x16\n" +
"\x06period\x18\x01 \x01(\rR\x06period\x12\x1c\n" +
"\tsecondary\x18\x02 \x01(\rR\tsecondary\"\xbc\x02\n" +
"\x0fRequestResponse\x12\t\n" +
"\x05UNSET\x10\x00\x12\x10\n" +
"\fROUTER_ERROR\x10\x01\x12\x14\n" +
"\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n" +
"\vROUTER_PING\x10\x03\x12\x0f\n" +
"\vROUTER_PONG\x10\x04\x12\x0f\n" +
"\vROUTER_BUSY\x10\x05\x12\x12\n" +
"\x0eROUTER_HISTORY\x10\x06\x12\x10\n" +
"\fROUTER_STATS\x10\a\x12\x16\n" +
"\x12ROUTER_TEXT_DIRECT\x10\b\x12\x19\n" +
"\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n" +
"\fCLIENT_ERROR\x10@\x12\x12\n" +
"\x0eCLIENT_HISTORY\x10A\x12\x10\n" +
"\fCLIENT_STATS\x10B\x12\x0f\n" +
"\vCLIENT_PING\x10C\x12\x0f\n" +
"\vCLIENT_PONG\x10D\x12\x10\n" +
"\fCLIENT_ABORT\x10jB\t\n" +
"\avariantBk\n" +
"\x14org.meshtastic.protoB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_storeforward_proto_rawDescOnce sync.Once
file_meshtastic_storeforward_proto_rawDescData []byte
)
func file_meshtastic_storeforward_proto_rawDescGZIP() []byte {
file_meshtastic_storeforward_proto_rawDescOnce.Do(func() {
file_meshtastic_storeforward_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_storeforward_proto_rawDesc), len(file_meshtastic_storeforward_proto_rawDesc)))
})
return file_meshtastic_storeforward_proto_rawDescData
}
var file_meshtastic_storeforward_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_meshtastic_storeforward_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_meshtastic_storeforward_proto_goTypes = []any{
(StoreAndForward_RequestResponse)(0), // 0: meshtastic.StoreAndForward.RequestResponse
(*StoreAndForward)(nil), // 1: meshtastic.StoreAndForward
(*StoreAndForward_Statistics)(nil), // 2: meshtastic.StoreAndForward.Statistics
(*StoreAndForward_History)(nil), // 3: meshtastic.StoreAndForward.History
(*StoreAndForward_Heartbeat)(nil), // 4: meshtastic.StoreAndForward.Heartbeat
}
var file_meshtastic_storeforward_proto_depIdxs = []int32{
0, // 0: meshtastic.StoreAndForward.rr:type_name -> meshtastic.StoreAndForward.RequestResponse
2, // 1: meshtastic.StoreAndForward.stats:type_name -> meshtastic.StoreAndForward.Statistics
3, // 2: meshtastic.StoreAndForward.history:type_name -> meshtastic.StoreAndForward.History
4, // 3: meshtastic.StoreAndForward.heartbeat:type_name -> meshtastic.StoreAndForward.Heartbeat
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_meshtastic_storeforward_proto_init() }
func file_meshtastic_storeforward_proto_init() {
if File_meshtastic_storeforward_proto != nil {
return
}
file_meshtastic_storeforward_proto_msgTypes[0].OneofWrappers = []any{
(*StoreAndForward_Stats)(nil),
(*StoreAndForward_History_)(nil),
(*StoreAndForward_Heartbeat_)(nil),
(*StoreAndForward_Text)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_storeforward_proto_rawDesc), len(file_meshtastic_storeforward_proto_rawDesc)),
NumEnums: 1,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_storeforward_proto_goTypes,
DependencyIndexes: file_meshtastic_storeforward_proto_depIdxs,
EnumInfos: file_meshtastic_storeforward_proto_enumTypes,
MessageInfos: file_meshtastic_storeforward_proto_msgTypes,
}.Build()
File_meshtastic_storeforward_proto = out.File
file_meshtastic_storeforward_proto_goTypes = nil
file_meshtastic_storeforward_proto_depIdxs = nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,228 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.4
// source: meshtastic/xmodem.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type XModem_Control int32
const (
XModem_NUL XModem_Control = 0
XModem_SOH XModem_Control = 1
XModem_STX XModem_Control = 2
XModem_EOT XModem_Control = 4
XModem_ACK XModem_Control = 6
XModem_NAK XModem_Control = 21
XModem_CAN XModem_Control = 24
XModem_CTRLZ XModem_Control = 26
)
// Enum value maps for XModem_Control.
var (
XModem_Control_name = map[int32]string{
0: "NUL",
1: "SOH",
2: "STX",
4: "EOT",
6: "ACK",
21: "NAK",
24: "CAN",
26: "CTRLZ",
}
XModem_Control_value = map[string]int32{
"NUL": 0,
"SOH": 1,
"STX": 2,
"EOT": 4,
"ACK": 6,
"NAK": 21,
"CAN": 24,
"CTRLZ": 26,
}
)
func (x XModem_Control) Enum() *XModem_Control {
p := new(XModem_Control)
*p = x
return p
}
func (x XModem_Control) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (XModem_Control) Descriptor() protoreflect.EnumDescriptor {
return file_meshtastic_xmodem_proto_enumTypes[0].Descriptor()
}
func (XModem_Control) Type() protoreflect.EnumType {
return &file_meshtastic_xmodem_proto_enumTypes[0]
}
func (x XModem_Control) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use XModem_Control.Descriptor instead.
func (XModem_Control) EnumDescriptor() ([]byte, []int) {
return file_meshtastic_xmodem_proto_rawDescGZIP(), []int{0, 0}
}
type XModem struct {
state protoimpl.MessageState `protogen:"open.v1"`
Control XModem_Control `protobuf:"varint,1,opt,name=control,proto3,enum=meshtastic.XModem_Control" json:"control,omitempty"`
Seq uint32 `protobuf:"varint,2,opt,name=seq,proto3" json:"seq,omitempty"`
Crc16 uint32 `protobuf:"varint,3,opt,name=crc16,proto3" json:"crc16,omitempty"`
Buffer []byte `protobuf:"bytes,4,opt,name=buffer,proto3" json:"buffer,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *XModem) Reset() {
*x = XModem{}
mi := &file_meshtastic_xmodem_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *XModem) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*XModem) ProtoMessage() {}
func (x *XModem) ProtoReflect() protoreflect.Message {
mi := &file_meshtastic_xmodem_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use XModem.ProtoReflect.Descriptor instead.
func (*XModem) Descriptor() ([]byte, []int) {
return file_meshtastic_xmodem_proto_rawDescGZIP(), []int{0}
}
func (x *XModem) GetControl() XModem_Control {
if x != nil {
return x.Control
}
return XModem_NUL
}
func (x *XModem) GetSeq() uint32 {
if x != nil {
return x.Seq
}
return 0
}
func (x *XModem) GetCrc16() uint32 {
if x != nil {
return x.Crc16
}
return 0
}
func (x *XModem) GetBuffer() []byte {
if x != nil {
return x.Buffer
}
return nil
}
var File_meshtastic_xmodem_proto protoreflect.FileDescriptor
const file_meshtastic_xmodem_proto_rawDesc = "" +
"\n" +
"\x17meshtastic/xmodem.proto\x12\n" +
"meshtastic\"\xd3\x01\n" +
"\x06XModem\x124\n" +
"\acontrol\x18\x01 \x01(\x0e2\x1a.meshtastic.XModem.ControlR\acontrol\x12\x10\n" +
"\x03seq\x18\x02 \x01(\rR\x03seq\x12\x14\n" +
"\x05crc16\x18\x03 \x01(\rR\x05crc16\x12\x16\n" +
"\x06buffer\x18\x04 \x01(\fR\x06buffer\"S\n" +
"\aControl\x12\a\n" +
"\x03NUL\x10\x00\x12\a\n" +
"\x03SOH\x10\x01\x12\a\n" +
"\x03STX\x10\x02\x12\a\n" +
"\x03EOT\x10\x04\x12\a\n" +
"\x03ACK\x10\x06\x12\a\n" +
"\x03NAK\x10\x15\x12\a\n" +
"\x03CAN\x10\x18\x12\t\n" +
"\x05CTRLZ\x10\x1aBb\n" +
"\x14org.meshtastic.protoB\fXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00b\x06proto3"
var (
file_meshtastic_xmodem_proto_rawDescOnce sync.Once
file_meshtastic_xmodem_proto_rawDescData []byte
)
func file_meshtastic_xmodem_proto_rawDescGZIP() []byte {
file_meshtastic_xmodem_proto_rawDescOnce.Do(func() {
file_meshtastic_xmodem_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_meshtastic_xmodem_proto_rawDesc), len(file_meshtastic_xmodem_proto_rawDesc)))
})
return file_meshtastic_xmodem_proto_rawDescData
}
var file_meshtastic_xmodem_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_meshtastic_xmodem_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_meshtastic_xmodem_proto_goTypes = []any{
(XModem_Control)(0), // 0: meshtastic.XModem.Control
(*XModem)(nil), // 1: meshtastic.XModem
}
var file_meshtastic_xmodem_proto_depIdxs = []int32{
0, // 0: meshtastic.XModem.control:type_name -> meshtastic.XModem.Control
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_meshtastic_xmodem_proto_init() }
func file_meshtastic_xmodem_proto_init() {
if File_meshtastic_xmodem_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_meshtastic_xmodem_proto_rawDesc), len(file_meshtastic_xmodem_proto_rawDesc)),
NumEnums: 1,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_meshtastic_xmodem_proto_goTypes,
DependencyIndexes: file_meshtastic_xmodem_proto_depIdxs,
EnumInfos: file_meshtastic_xmodem_proto_enumTypes,
MessageInfos: file_meshtastic_xmodem_proto_msgTypes,
}.Build()
File_meshtastic_xmodem_proto = out.File
file_meshtastic_xmodem_proto_goTypes = nil
file_meshtastic_xmodem_proto_depIdxs = nil
}

View File

@@ -21,8 +21,9 @@ const (
type Packet struct {
Time time.Time `json:"time,omitempty"` // Receive time stamp
Protocol string `json:"protocol"` // Protocol name
Radio string `json:"radio"` // Radio name/ID
SNR float64 `json:"snr"` // Signal-to-noise Ratio
RSSI int8 `json:"rssi"` // Received Signal Strength Indicator
RSSI int `json:"rssi"` // Received Signal Strength Indicator
Raw []byte `json:"raw"` // Raw packet
}

View File

@@ -7,6 +7,7 @@ import (
// Info descriptor.
type Info struct {
ID string `yaml:"id" json:"id"` // Unique identifier (e.g. serial number, MAC address)
Name string `yaml:"name" json:"name"` // Name of the device
Device string `yaml:"device" json:"device"` // Device type
Manufacturer string `yaml:"manufacturer" json:"manufacturer"` // Device manufacturer
@@ -28,6 +29,15 @@ type Info struct {
Extra map[string]any `yaml:"extra" json:"extra"` // Extra metadata
}
// Stats descriptor.
type Stats struct {
Voltage float64 `json:"voltage"` // System or battery voltage
Connections int `json:"connections"` // Active connections/neighbours/peers.
Backlog int `json:"backlog"` // Packet/payload backlog
Errors int `json:"errors"` // Errors encountered
NoiseFloor float64 `json:"noise_floor"` // Noise floor
}
type Position struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`