9 Commits

Author SHA1 Message Date
Reese Norris
e09ff9e64e rename atc to controllers in datafeed 2025-05-25 14:54:31 -07:00
Reese Norris
933edc0478 update datafeed to be more vatsim-compliant 2025-05-25 14:51:45 -07:00
Reese Norris
6e3a179a3a convert visual range unit for ATC datafeed to nautical miles 2025-05-25 14:29:30 -07:00
Reese Norris
f63d89eb3e only do velocity distance calculation for relevant clients 2025-05-25 14:27:07 -07:00
Reese Norris
bc7a37e490 properly store sendfast state 2025-05-25 14:19:08 -07:00
Reese Norris
7d17066289 properly initialize Client latlon 2025-05-25 14:00:14 -07:00
Reese Norris
2943735be6 add AUTOMATIC entry to server list API for client compatibility 2025-05-25 13:52:19 -07:00
Reese Norris
c0155e78d4 store client coords as a single atomic unit 2025-05-25 11:57:06 -07:00
Reese Norris
2f423ed824 update gitignore 2025-05-25 11:56:38 -07:00
8 changed files with 130 additions and 25 deletions

View File

@@ -4,3 +4,4 @@ mkdocs.yml
README.md README.md
docs docs
**tmp** **tmp**
build-and-push.sh

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@
.vscode .vscode
*.db *.db
**tmp** **tmp**
build-and-push.sh

View File

@@ -16,7 +16,8 @@ type Client struct {
cancelCtx func() cancelCtx func()
sendChan chan string sendChan chan string
lat, lon, visRange atomic.Float64 coords atomic.Value
visRange atomic.Float64
closestVelocityClientDistance float64 // The closest Velocity-compatible client in meters closestVelocityClientDistance float64 // The closest Velocity-compatible client in meters
flightPlan atomic.String flightPlan atomic.String
@@ -37,11 +38,12 @@ type Client struct {
} }
type LatLon struct { type LatLon struct {
lat, lon float64
} }
func newClient(ctx context.Context, conn net.Conn, scanner *bufio.Scanner, loginData loginData) (client *Client) { func newClient(ctx context.Context, conn net.Conn, scanner *bufio.Scanner, loginData loginData) (client *Client) {
clientCtx, cancel := context.WithCancel(ctx) clientCtx, cancel := context.WithCancel(ctx)
return &Client{ client = &Client{
conn: conn, conn: conn,
scanner: scanner, scanner: scanner,
ctx: clientCtx, ctx: clientCtx,
@@ -49,6 +51,8 @@ func newClient(ctx context.Context, conn net.Conn, scanner *bufio.Scanner, login
sendChan: make(chan string, 32), sendChan: make(chan string, 32),
loginData: loginData, loginData: loginData,
} }
client.setLatLon(0, 0)
return
} }
func (c *Client) senderWorker() { func (c *Client) senderWorker() {
@@ -125,5 +129,10 @@ func (s *Server) eventLoop(client *Client) {
} }
func (c *Client) latLon() [2]float64 { func (c *Client) latLon() [2]float64 {
return [2]float64{c.lat.Load(), c.lon.Load()} latLon := c.coords.Load().(LatLon)
return [2]float64{latLon.lat, latLon.lon}
}
func (c *Client) setLatLon(lat, lon float64) {
c.coords.Store(LatLon{lat: lat, lon: lon})
} }

View File

@@ -165,15 +165,19 @@ func (s *Server) handlePilotPosition(client *Client, packet []byte) {
client.lastUpdated.Store(time.Now()) client.lastUpdated.Store(time.Now())
// Check if we need to update the sendfast state // Check if we need to update the sendfast state
if client.protoRevision == 101 {
if client.sendFastEnabled { if client.sendFastEnabled {
if (client.closestVelocityClientDistance / 1852.0) > 5.0 { // 5.0 nautical miles if (client.closestVelocityClientDistance / 1852.0) > 5.0 { // 5.0 nautical miles
client.sendFastEnabled = false
sendDisableSendFastPacket(client) sendDisableSendFastPacket(client)
} }
} else { } else {
if (client.closestVelocityClientDistance / 1852.0) < 5.0 { // 5.0 nautical miles if (client.closestVelocityClientDistance / 1852.0) < 5.0 { // 5.0 nautical miles
client.sendFastEnabled = true
sendEnableSendFastPacket(client) sendEnableSendFastPacket(client)
} }
} }
}
} }
// handleFastPilotPosition handles logic for fast `^`, stopped `#ST`, and slow `#SL` pilot position updates // handleFastPilotPosition handles logic for fast `^`, stopped `#ST`, and slow `#SL` pilot position updates

View File

@@ -110,13 +110,14 @@ func (s *Server) handleGetOnlineUsers(c *gin.Context) {
} }
for _, client := range clientMap { for _, client := range clientMap {
latLon := client.latLon()
genData := OnlineUserGeneralData{ genData := OnlineUserGeneralData{
Callsign: client.callsign, Callsign: client.callsign,
CID: client.cid, CID: client.cid,
Name: client.realName, Name: client.realName,
NetworkRating: int(client.networkRating), NetworkRating: int(client.networkRating),
Latitude: client.lat.Load(), Latitude: latLon[0],
Longitude: client.lon.Load(), Longitude: latLon[1],
LogonTime: client.loginTime, LogonTime: client.loginTime,
LastUpdated: client.lastUpdated.Load(), LastUpdated: client.lastUpdated.Load(),
} }
@@ -126,7 +127,7 @@ func (s *Server) handleGetOnlineUsers(c *gin.Context) {
OnlineUserGeneralData: genData, OnlineUserGeneralData: genData,
Frequency: client.frequency.Load(), Frequency: client.frequency.Load(),
Facility: client.facilityType, Facility: client.facilityType,
VisRange: int(client.visRange.Load()), VisRange: int(client.visRange.Load() * 0.000539957), // Convert meters to nautical miles
} }
resData.ATC = append(resData.ATC, atc) resData.ATC = append(resData.ATC, atc)
} else { } else {

View File

@@ -68,8 +68,7 @@ func (p *postOffice) updatePosition(client *Client, newCenter [2]float64, newVis
oldMin, oldMax := calculateBoundingBox(client.latLon(), client.visRange.Load()) oldMin, oldMax := calculateBoundingBox(client.latLon(), client.visRange.Load())
newMin, newMax := calculateBoundingBox(newCenter, newVisRange) newMin, newMax := calculateBoundingBox(newCenter, newVisRange)
client.lat.Store(newCenter[0]) client.setLatLon(newCenter[0], newCenter[1])
client.lon.Store(newCenter[1])
client.visRange.Store(newVisRange) client.visRange.Store(newVisRange)
// Avoid redundant updates // Avoid redundant updates
@@ -99,8 +98,10 @@ func (p *postOffice) search(client *Client, callback func(recipient *Client) boo
return true // Ignore self return true // Ignore self
} }
if foundClient.protoRevision == 101 { if !client.isAtc && client.protoRevision == 101 && foundClient.protoRevision == 101 {
dist := distance(client.lat.Load(), client.lon.Load(), foundClient.lat.Load(), foundClient.lon.Load()) clientLatLon := client.latLon()
foundClientLatLon := foundClient.latLon()
dist := distance(clientLatLon[0], clientLatLon[1], foundClientLatLon[0], foundClientLatLon[1])
if dist < client.closestVelocityClientDistance { if dist < client.closestVelocityClientDistance {
client.closestVelocityClientDistance = dist client.closestVelocityClientDistance = dist
} }

View File

@@ -145,6 +145,15 @@ func (s *Server) handleGetServersJSON(c *gin.Context) {
ClientsConnectionAllowed: 99, ClientsConnectionAllowed: 99,
IsSweatbox: isSweatbox, IsSweatbox: isSweatbox,
}, },
{
Ident: "AUTOMATIC",
HostnameOrIp: serverHostname,
Location: serverLocation,
Name: serverIdent,
ClientConnectionsAllowed: true,
ClientsConnectionAllowed: 99,
IsSweatbox: isSweatbox,
},
} }
res, err := json.Marshal(&dataJson) res, err := json.Marshal(&dataJson)
@@ -190,6 +199,15 @@ func (s *Server) generateServersTxt() (txt string, err error) {
ClientsConnectionAllowed: 99, ClientsConnectionAllowed: 99,
IsSweatbox: false, IsSweatbox: false,
}, },
{
Ident: "AUTOMATIC",
HostnameOrIp: serverHostname,
Location: serverLocation,
Name: serverIdent,
ClientConnectionsAllowed: true,
ClientsConnectionAllowed: 99,
IsSweatbox: false,
},
} }
buf := bytes.Buffer{} buf := bytes.Buffer{}
@@ -250,8 +268,49 @@ func (s *Server) getBaseURLOrErr(c *gin.Context) (baseURL string, ok bool) {
} }
type Datafeed struct { type Datafeed struct {
Pilots []fsd.OnlineUserPilot `json:"pilots"` General DatafeedGeneral `json:"general"`
ATC []fsd.OnlineUserATC `json:"atc"` Pilots []DatafeedPilot `json:"pilots"`
ATC []DatafeedATC `json:"controllers"`
}
type DatafeedGeneral struct {
Version int `json:"version"`
UpdateTimestamp time.Time `json:"update_timestamp"`
ConnectedClients int `json:"connected_clients"`
UniqueUsers int `json:"unique_users"`
}
type DatafeedPilot struct {
fsd.OnlineUserPilot
Server string `json:"server"`
PilotRating int `json:"pilot_rating"` // INOP placeholder
MilitaryRating int `json:"military_rating"` // INOP placeholder
QnhIHg float64 `json:"qnh_i_hg"` // INOP placeholder
QnhMb int `json:"qnh_mb"` // INOP placeholder
FlightPlan *DatafeedFlightplan `json:"flight_plan,omitempty"` // INOP placeholder
}
type DatafeedFlightplan struct {
FlightRules string `json:"flight_rules"`
Aircraft string `json:"aircraft"`
AircraftFAA string `json:"aircraft_faa"`
AircraftShort string `json:"aircraft_short"`
Departure string `json:"departure"`
Arrival string `json:"arrival"`
Alternate string `json:"alternate"`
DepTime string `json:"deptime"`
EnrouteTime string `json:"enroute_time"`
FuelTime string `json:"fuel_time"`
Remarks string `json:"remarks"`
Route string `json:"route"`
RevisionID int `json:"revision_id"`
AssignedTransponder string `json:"assigned_transponder"`
}
type DatafeedATC struct {
fsd.OnlineUserATC
Server string `json:"server"`
TextATIS []string `json:"text_atis"` // INOP placeholder
} }
type DatafeedCache struct { type DatafeedCache struct {
@@ -294,10 +353,38 @@ func (s *Server) generateDatafeed() (feed *DatafeedCache, err error) {
return return
} }
now := time.Now()
dataFeed := Datafeed{ dataFeed := Datafeed{
Pilots: onlineUsers.Pilots, General: DatafeedGeneral{
ATC: onlineUsers.ATC, Version: 3,
UpdateTimestamp: now,
ConnectedClients: len(onlineUsers.Pilots) + len(onlineUsers.ATC),
UniqueUsers: len(onlineUsers.Pilots) + len(onlineUsers.ATC),
},
Pilots: []DatafeedPilot{},
ATC: []DatafeedATC{},
} }
for _, pilot := range onlineUsers.Pilots {
dataFeed.Pilots = append(dataFeed.Pilots, DatafeedPilot{
OnlineUserPilot: pilot,
Server: "OPENFSD",
PilotRating: 1,
MilitaryRating: 1,
QnhIHg: 29.92,
QnhMb: 1013,
})
}
for _, atc := range onlineUsers.ATC {
dataFeed.ATC = append(dataFeed.ATC, DatafeedATC{
OnlineUserATC: atc,
Server: "OPENFSD",
TextATIS: []string{},
})
}
buf := bytes.Buffer{} buf := bytes.Buffer{}
encoder := json.NewEncoder(&buf) encoder := json.NewEncoder(&buf)
if err = encoder.Encode(&dataFeed); err != nil { if err = encoder.Encode(&dataFeed); err != nil {
@@ -306,7 +393,7 @@ func (s *Server) generateDatafeed() (feed *DatafeedCache, err error) {
feed = &DatafeedCache{ feed = &DatafeedCache{
jsonStr: buf.String(), jsonStr: buf.String(),
lastUpdated: time.Now(), lastUpdated: now,
} }
return return
} }

View File

@@ -7,6 +7,7 @@ CONNECTED CLIENTS = 1
; ;
; ;
!SERVERS: !SERVERS:
{{ range . }}{{ .Ident }}:{{ .HostnameOrIp }}:{{ .Location }}:{{ .Ident }}:{{ .ClientsConnectionAllowed }}:{{ end }} {{ range $index, $element := . }}{{ if $index }}
{{ end }}{{ $element.Ident }}:{{ $element.HostnameOrIp }}:{{ $element.Location }}:{{ $element.Name }}:{{ $element.ClientsConnectionAllowed }}:{{ end }}
; ;
; END ; END