G1000: two-way sim sync, more PFD/MFD fidelity, authentic dialogs
Sync (FlyWithLua companions in plugins/ + server/fmssync.js): - FMS flight-plan two-way sync (App <-> in-sim FMS) via fms-sync.lua - G1000 UI-state publish (page/range/inset) via ui-sync.lua + CDI source, baro, map-range follow - Terrain awareness: elevation grid probe (terrain-probe.lua) -> red/yellow MFD overlay vs aircraft altitude PFD: - AFCS mode annunciation bar from autopilot _status datarefs - CDI source GPS/VLOC colouring, BRG1/BRG2 pointers + DME windows, marker beacons - magenta speed/altitude trend vectors, selected-altitude alerting - time-based (frame-rate-independent) smoothing for attitude/heading/tapes MFD: - nav data bar (DTK/ETE/active leg), airways overlay from earth_awy.dat, compass rose anchored to the ownship Dialogs (NEAREST/FLIGHTPLAN/DIRECT-TO/PROCEDURES): - flat, square, embedded G1000 look (no shadow/rounded/transparency) - compact lower-right placement, no close X (softkey toggles), single window - NEAREST 2-line entries (ILS/VFR, COM freq, runway length), PROC action menu Service worker: network-first HTML so reloads pick up new builds (cache v2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
-- ============================================================================
|
||||
-- X-Plane Glass Cockpit — FMS two-way sync (FlyWithLua companion)
|
||||
-- ============================================================================
|
||||
-- The web cockpit's bridge can't write the FMS via X-Plane's Web API. This
|
||||
-- script runs INSIDE X-Plane (FlyWithLua) and has the FMS SDK, so it bridges
|
||||
-- the shared plan <-> the in-sim FMS through two text files:
|
||||
--
|
||||
-- <X-Plane>/Output/fms-sync/to_sim.txt written by the bridge (our plan)
|
||||
-- <X-Plane>/Output/fms-sync/from_sim.txt written here (the sim's plan)
|
||||
--
|
||||
-- A 3-decimal lat/lon signature de-dupes both sides so they never loop.
|
||||
--
|
||||
-- INSTALL: copy this file to <X-Plane>/Resources/plugins/FlyWithLua/Scripts/
|
||||
-- (install FlyWithLua NG+ first), then restart X-Plane or run
|
||||
-- "FlyWithLua > Reload all Lua script files".
|
||||
-- ============================================================================
|
||||
|
||||
local SYNC = SYSTEM_DIRECTORY .. "Output/fms-sync/"
|
||||
local TO_SIM = SYNC .. "to_sim.txt"
|
||||
local FROM_SIM = SYNC .. "from_sim.txt"
|
||||
local last_sig = nil
|
||||
|
||||
-- make sure the folder exists (bridge also creates it)
|
||||
os.execute('mkdir -p "' .. SYNC .. '" 2>/dev/null || mkdir "' .. SYNC .. '" 2>nul')
|
||||
|
||||
-- 3-decimal lat/lon signature of a waypoint list ---------------------------
|
||||
local function sig_of(wps)
|
||||
local parts = {}
|
||||
for i = 1, #wps do
|
||||
parts[i] = string.format("%.3f,%.3f", wps[i].lat, wps[i].lon)
|
||||
end
|
||||
return table.concat(parts, ";")
|
||||
end
|
||||
|
||||
local function read_file(p)
|
||||
local f = io.open(p, "r"); if not f then return nil end
|
||||
local s = f:read("*a"); f:close(); return s
|
||||
end
|
||||
|
||||
local function write_file(p, s)
|
||||
local f = io.open(p, "w"); if not f then return end
|
||||
f:write(s); f:close()
|
||||
end
|
||||
|
||||
-- parse the bridge file: skip "# sig" lines, take "lat lon alt id type" ------
|
||||
local function parse(txt)
|
||||
local wps = {}
|
||||
if not txt then return wps end
|
||||
for line in txt:gmatch("[^\r\n]+") do
|
||||
if line:sub(1, 1) ~= "#" then
|
||||
local lat, lon, alt, id = line:match("^%s*(-?%d+%.?%d*)%s+(-?%d+%.?%d*)%s+(-?%d+)%s+(%S+)")
|
||||
if lat and lon then
|
||||
wps[#wps + 1] = { lat = tonumber(lat), lon = tonumber(lon), alt = tonumber(alt) or 0, id = id or "WPT" }
|
||||
end
|
||||
end
|
||||
end
|
||||
return wps
|
||||
end
|
||||
|
||||
-- read the current in-sim FMS plan ------------------------------------------
|
||||
local function read_fms()
|
||||
local wps = {}
|
||||
local n = XPLMCountFMSEntries()
|
||||
for i = 0, n - 1 do
|
||||
-- FlyWithLua: type, id, ref, altitude, lat, lon
|
||||
local _t, id, _ref, alt, lat, lon = XPLMGetFMSEntryInfo(i)
|
||||
if lat and lon and (math.abs(lat) > 0.0001 or math.abs(lon) > 0.0001) then
|
||||
wps[#wps + 1] = { lat = lat, lon = lon, alt = alt or 0, id = (id ~= "" and id) or "WPT" }
|
||||
end
|
||||
end
|
||||
return wps
|
||||
end
|
||||
|
||||
-- write our plan into the in-sim FMS ----------------------------------------
|
||||
local function apply_to_fms(wps)
|
||||
local old = XPLMCountFMSEntries()
|
||||
for i = 1, #wps do
|
||||
-- lat/lon entries keep our exact coords -> stable round-trip (no drift)
|
||||
XPLMSetFMSEntryLatLon(i - 1, wps[i].lat, wps[i].lon, math.floor(wps[i].alt or 0))
|
||||
end
|
||||
for i = old - 1, #wps, -1 do XPLMClearFMSEntry(i) end -- trim leftovers
|
||||
if #wps >= 1 then
|
||||
XPLMSetDisplayedFMSEntry(0)
|
||||
XPLMSetDestinationFMSEntry(#wps - 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function serialize(wps)
|
||||
local lines = { "# " .. sig_of(wps) }
|
||||
for i = 1, #wps do
|
||||
lines[#lines + 1] = string.format("%.6f %.6f %d %s WPT", wps[i].lat, wps[i].lon, math.floor(wps[i].alt or 0), wps[i].id)
|
||||
end
|
||||
return table.concat(lines, "\n") .. "\n"
|
||||
end
|
||||
|
||||
-- main loop (~1×/sec): whichever side differs from the agreed plan wins ------
|
||||
function fms_sync_tick()
|
||||
local to_wps = parse(read_file(TO_SIM))
|
||||
local tsig = sig_of(to_wps)
|
||||
local fm_wps = read_fms()
|
||||
local fsig = sig_of(fm_wps)
|
||||
|
||||
if tsig ~= "" and tsig ~= last_sig then
|
||||
apply_to_fms(to_wps) -- App -> Sim
|
||||
last_sig = tsig
|
||||
elseif fsig ~= last_sig then
|
||||
write_file(FROM_SIM, serialize(fm_wps)) -- Sim -> App
|
||||
last_sig = fsig
|
||||
end
|
||||
end
|
||||
|
||||
do_often("fms_sync_tick()")
|
||||
logMsg("[glass-cockpit] FMS sync active -> " .. SYNC)
|
||||
Reference in New Issue
Block a user