Files

274 lines
8.9 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- Trip Odometer 1.1 — wadamesh Lua app
-- GPS trip computer: distance, time, avg/max speed, min/current altitude,
-- return-to-home bearing.
-- Tap Start/Stop to track, Reset to clear. Saves your trip across app restarts.
-- Contributed by samuelcoustet
local ui, sys, geo, store, timer = wada.ui, wada.sys, wada.geo, wada.store, wada.timer
local C = ui.colors
local app = {}
local MIN_SPEED_KMH = 2.0 -- below this, GPS drift is ignored
local POLL_MS = 2000
-- trip state
local total_km, max_kmh, avg_kmh
local min_alt_m, elapsed_s
local last_lat, last_lon
local home_lat, home_lon -- persisted; set on first GPS fix after Start
local running, fix
local current_spd = 0.0 -- for display (may be derived when speed_kmh absent)
-- labels
local spd_lbl, dist_lbl, time_lbl
local avgspd_lbl, maxspd_lbl
local alt_lbl, minalt_lbl
local home_lbl
local sats_lbl, st_lbl
-- ── Helpers ────────────────────────────────────────────────────────────────
local function haversine(lat1, lon1, lat2, lon2)
local R = 6371.0
local dlat = (lat2 - lat1) * math.pi / 180.0
local dlon = (lon2 - lon1) * math.pi / 180.0
local a = math.sin(dlat * 0.5) ^ 2
+ math.cos(lat1 * math.pi / 180.0)
* math.cos(lat2 * math.pi / 180.0)
* math.sin(dlon * 0.5) ^ 2
return R * 2.0 * math.atan(math.sqrt(a), math.sqrt(1.0 - a))
end
local function bearing(lat1, lon1, lat2, lon2)
if geo then return geo.bearing(lat1, lon1, lat2, lon2) end
local dlon = (lon2 - lon1) * math.pi / 180.0
local y = math.sin(dlon) * math.cos(lat2 * math.pi / 180.0)
local x = math.cos(lat1 * math.pi / 180.0) * math.sin(lat2 * math.pi / 180.0)
- math.sin(lat1 * math.pi / 180.0) * math.cos(lat2 * math.pi / 180.0) * math.cos(dlon)
return (math.atan(y, x) * 180.0 / math.pi + 360.0) % 360.0
end
local function cardinal(brg)
if geo then return geo.cardinal(brg) end
local dirs = {"N","NE","E","SE","S","SW","W","NW"}
return dirs[math.floor((brg + 22.5) / 45) % 8 + 1]
end
local function fmt_time(s)
local h = math.floor(s / 3600)
local m = math.floor((s % 3600) / 60)
local sec = s % 60
if h > 0 then return string.format("%d:%02d:%02d", h, m, sec) end
return string.format("%d:%02d", m, sec)
end
local function fmt_dist(km)
if km < 1.0 then return string.format("%d m", math.floor(km * 1000)) end
return string.format("%.2f km", km)
end
local function save()
store.set("trip_km_x1000", math.floor(total_km * 1000))
store.set("trip_max_x10", math.floor(max_kmh * 10))
store.set("trip_s", elapsed_s)
store.set("trip_minalt", min_alt_m or -9999)
if home_lat then
store.set("trip_home_lat_e6", math.floor(home_lat * 1e6))
store.set("trip_home_lon_e6", math.floor(home_lon * 1e6))
end
end
local function redraw()
spd_lbl:set(string.format("%.1f", current_spd))
dist_lbl:set(fmt_dist(total_km))
time_lbl:set(fmt_time(elapsed_s))
maxspd_lbl:set(string.format("%.1f km/h", max_kmh))
avg_kmh = (elapsed_s > 0) and (total_km / elapsed_s * 3600.0) or 0.0
avgspd_lbl:set(string.format("%.1f km/h", avg_kmh))
if fix then
alt_lbl:set(string.format("%d m", fix.alt_m or 0))
minalt_lbl:set(min_alt_m and string.format("min %d m", min_alt_m) or "min -- m")
sats_lbl:set(string.format("%d sats", fix.sats or 0))
if running then
st_lbl:set("tracking"); st_lbl:color(C.good)
else
st_lbl:set("paused"); st_lbl:color(C.sub)
end
else
alt_lbl:set("-- m")
minalt_lbl:set("min -- m")
sats_lbl:set("no GPS")
st_lbl:set("no fix"); st_lbl:color(C.bad)
end
-- return-to-home row
if home_lat and fix and fix.lat and fix.lon then
local d = haversine(home_lat, home_lon, fix.lat, fix.lon)
local brg = bearing(fix.lat, fix.lon, home_lat, home_lon)
local card = cardinal(brg)
home_lbl:set(string.format("%s %s", fmt_dist(d), card))
home_lbl:color(C.accent)
elseif running then
home_lbl:set("acquiring…"); home_lbl:color(C.sub)
else
home_lbl:set("--"); home_lbl:color(C.sub)
end
end
-- ── App lifecycle ──────────────────────────────────────────────────────────
function app.on_open(w, h)
total_km = store.get("trip_km_x1000", 0) / 1000.0
max_kmh = store.get("trip_max_x10", 0) / 10.0
elapsed_s = store.get("trip_s", 0)
local saved_min = store.get("trip_minalt", -9999)
min_alt_m = (saved_min ~= -9999) and saved_min or nil
local hl = store.get("trip_home_lat_e6", nil)
local hlo = store.get("trip_home_lon_e6", nil)
home_lat = hl and (hl / 1e6) or nil
home_lon = hlo and (hlo / 1e6) or nil
running = false
fix = nil
last_lat = nil; last_lon = nil
current_spd = 0.0
avg_kmh = 0.0
-- Proportional layout: speed block (fixed) then rows fill remaining height.
local col2 = math.floor(w / 2)
local btn_y = h - 46
-- Speed block (rows 0–1): header at 6, big value at 20, unit near bottom
ui.label("SPEED", 8, 6, 11, C.sub)
spd_lbl = ui.label("0.0", 8, 20, 36, C.accent)
ui.label("km/h", 8, 64, 12, C.sub)
-- Content rows: distribute evenly between y=78 and btn_y
local rh = math.floor((btn_y - 78) / 5)
local y2 = 78
local y3 = y2 + rh
local y4 = y3 + rh
local y5 = y4 + rh
local y6 = y5 + rh
-- choose a readable value font size that fits the row height
local val_sz = (rh >= 34) and 20 or (rh >= 26) and 16 or 13
-- row 2: distance | time
ui.label("DISTANCE", 8, y2, 11, C.sub)
dist_lbl = ui.label("0 m", 8, y2 + 13, val_sz, C.text)
ui.label("TIME", col2, y2, 11, C.sub)
time_lbl = ui.label("0:00",col2, y2 + 13, val_sz, C.text)
-- row 3: avg speed | max speed
ui.label("AVG SPEED", 8, y3, 11, C.sub)
avgspd_lbl = ui.label("0.0 km/h", 8, y3 + 13, val_sz, C.text)
ui.label("MAX SPEED", col2, y3, 11, C.sub)
maxspd_lbl = ui.label("0.0 km/h", col2, y3 + 13, val_sz, C.text)
-- row 4: altitude | min altitude
ui.label("ALTITUDE", 8, y4, 11, C.sub)
alt_lbl = ui.label("-- m", 8, y4 + 13, val_sz, C.text)
minalt_lbl = ui.label("min -- m", col2, y4 + 13, 12, C.sub)
-- row 5: return to home
ui.label("HOME", 8, y5, 11, C.sub)
home_lbl = ui.label("--", 8, y5 + 13, val_sz, C.sub)
-- row 6: GPS status
sats_lbl = ui.label("no GPS", 8, y6, 13, C.sub)
st_lbl = ui.label("no fix", col2, y6, 13, C.sub)
-- buttons anchored to bottom
local bw = math.floor((w - 20) / 2)
ui.button("Start / Stop", 8, btn_y, bw, 36, function()
running = not running
if running then
last_lat = nil; last_lon = nil -- fresh segment on resume
end
redraw()
end)
ui.button("Reset", 12 + bw, btn_y, bw, 36, function()
total_km = 0; max_kmh = 0; elapsed_s = 0; avg_kmh = 0
min_alt_m = nil
last_lat = nil; last_lon = nil
home_lat = nil; home_lon = nil
store.set("trip_home_lat_e6", nil)
store.set("trip_home_lon_e6", nil)
running = false
save()
redraw()
sys.toast("Trip reset", 1200)
end)
timer.every(POLL_MS)
redraw()
end
function app.on_tick()
fix = sys.gps()
if running then
elapsed_s = elapsed_s + math.floor(POLL_MS / 1000)
if fix and fix.lat and fix.lon then
-- derive speed from successive fixes when the GPS field is absent (non-M9)
local spd = fix.speed_kmh
if spd == nil and last_lat then
spd = haversine(last_lat, last_lon, fix.lat, fix.lon)
/ (POLL_MS / 1000.0) * 3600.0
end
current_spd = spd or 0.0
if current_spd > max_kmh then max_kmh = current_spd end
-- track altitude extremes
if fix.alt_m then
if min_alt_m == nil or fix.alt_m < min_alt_m then
min_alt_m = fix.alt_m
end
end
-- record home on the very first GPS fix after Start
if home_lat == nil then
home_lat = fix.lat; home_lon = fix.lon
save()
end
-- accumulate distance when moving (speed filter suppresses GPS drift)
if current_spd >= MIN_SPEED_KMH and last_lat then
local d = haversine(last_lat, last_lon, fix.lat, fix.lon)
if d > 0.003 then total_km = total_km + d end
end
-- always record position so the next tick can derive speed
last_lat = fix.lat; last_lon = fix.lon
end
if elapsed_s % 10 == 0 then save() end
else
current_spd = (fix and fix.speed_kmh) or 0.0
end
redraw()
end
function app.on_input(ev)
-- swipe down = quick reset (only when stopped)
if ev.type == "swipe" and ev.dir == "down" and not running then
total_km = 0; max_kmh = 0; elapsed_s = 0; avg_kmh = 0
min_alt_m = nil
last_lat = nil; last_lon = nil
home_lat = nil; home_lon = nil
store.set("trip_home_lat_e6", nil)
store.set("trip_home_lon_e6", nil)
save(); redraw()
sys.toast("Trip reset", 1200)
end
end
function app.on_close()
save()
end
return app