From ed5c18822a0f00e40f034c1dce1a09c9f19eabe3 Mon Sep 17 00:00:00 2001 From: Rob Thomson Date: Fri, 14 Nov 2025 12:37:26 +0000 Subject: [PATCH] Deployments --- .github/workflows/pr.yml | 6 +- .github/workflows/push.yml | 4 +- .github/workflows/release.yml | 2 +- .github/workflows/snapshot.yml | 2 +- .gitignore | 1 + .vscode/deploy.json | 10 + .vscode/launch.json | 167 +----- .../subprocess_conout.cpython-312.pyc | Bin 11283 -> 11299 bytes .../subprocess_conout.cpython-313.pyc | Bin 0 -> 11445 bytes .vscode/scripts/deploy.py | 564 ++++++------------ .vscode/scripts/deploy_step_i18n.py | 53 ++ .vscode/scripts/deploy_step_soundpack.py | 115 ++++ .vscode/scripts/dummy.py | 2 + .vscode/settings.json | 4 + .vscode/tasks.json | 139 +++++ {scripts => src}/dashx/.vscode/launch.json | 0 {scripts => src}/dashx/app/app.lua | 0 {scripts => src}/dashx/app/gfx/icon.png | Bin .../dashx/app/gfx/unsupported.png | Bin {scripts => src}/dashx/app/lib/ui.lua | 0 {scripts => src}/dashx/app/lib/utils.lua | 0 {scripts => src}/dashx/app/modules/init.lua | 0 .../dashx/app/modules/logs/gfx/folder.png | Bin .../dashx/app/modules/logs/gfx/logs.png | Bin .../dashx/app/modules/logs/help.lua | 0 .../dashx/app/modules/logs/init.lua | 0 .../dashx/app/modules/logs/lib/utils.lua | 0 .../dashx/app/modules/logs/logs_logs.lua | 0 .../dashx/app/modules/logs/logs_view.lua | 0 .../dashx/app/modules/model/gfx/battery.png | Bin .../dashx/app/modules/model/gfx/triggers.png | Bin .../dashx/app/modules/model/help.lua | 0 .../dashx/app/modules/model/init.lua | 0 .../dashx/app/modules/model/model.lua | 0 .../dashx/app/modules/model/model.png | Bin .../dashx/app/modules/model/tools/battery.lua | 0 .../app/modules/model/tools/triggers.lua | 0 .../dashx/app/modules/sections.lua | 0 .../dashx/app/modules/settings/gfx/audio.png | Bin .../app/modules/settings/gfx/audio_events.png | Bin .../modules/settings/gfx/audio_switches.png | Bin .../app/modules/settings/gfx/dashboard.png | Bin .../settings/gfx/dashboard_settings.png | Bin .../modules/settings/gfx/dashboard_theme.png | Bin .../app/modules/settings/gfx/development.png | Bin .../app/modules/settings/gfx/general.png | Bin .../modules/settings/gfx/localizations.png | Bin .../dashx/app/modules/settings/gfx/model.png | Bin .../dashx/app/modules/settings/help.lua | 0 .../dashx/app/modules/settings/init.lua | 0 .../dashx/app/modules/settings/settings.lua | 0 .../dashx/app/modules/settings/settings.png | Bin .../app/modules/settings/tools/audio.lua | 0 .../modules/settings/tools/audio_events.lua | 0 .../modules/settings/tools/audio_switches.lua | 0 .../app/modules/settings/tools/dashboard.lua | 0 .../settings/tools/dashboard_settings.lua | 0 .../settings/tools/dashboard_theme.lua | 0 .../modules/settings/tools/development.lua | 0 .../app/modules/settings/tools/general.lua | 0 .../modules/settings/tools/localizations.lua | 0 {scripts => src}/dashx/app/radios.lua | 0 {scripts => src}/dashx/audio/alarm.wav | Bin {scripts => src}/dashx/audio/beep.wav | Bin .../dashx/audio/en/default/app/timeout.wav | Bin .../audio/en/default/events/alerts/armed.wav | Bin .../en/default/events/alerts/disarmed.wav | Bin .../en/default/events/alerts/lowfuel.wav | Bin .../en/default/events/alerts/lowvoltage.wav | Bin .../en/default/events/alerts/profile.wav | Bin .../audio/en/default/events/alerts/rates.wav | Bin .../en/default/status/alerts/current.wav | Bin .../audio/en/default/status/alerts/esc.wav | Bin .../audio/en/default/status/alerts/fuel.wav | Bin .../en/default/status/alerts/headspeed.wav | Bin .../en/default/status/alerts/lowfuel.wav | Bin .../en/default/status/alerts/lowvoltage.wav | Bin .../audio/en/default/status/alerts/lq.wav | Bin .../audio/en/default/status/alerts/mcu.wav | Bin .../en/default/status/alerts/remaining.wav | Bin .../audio/en/default/status/alerts/timer.wav | Bin .../en/default/status/alerts/voltage.wav | Bin .../dashx/audio/en/gb/app/timeout.wav | Bin .../dashx/audio/en/gb/events/alerts/armed.wav | Bin .../audio/en/gb/events/alerts/disarmed.wav | Bin .../audio/en/gb/events/alerts/lowfuel.wav | Bin .../audio/en/gb/events/alerts/lowvoltage.wav | Bin .../audio/en/gb/events/alerts/profile.wav | Bin .../dashx/audio/en/gb/events/alerts/rates.wav | Bin .../audio/en/gb/status/alerts/current.wav | Bin .../dashx/audio/en/gb/status/alerts/esc.wav | Bin .../dashx/audio/en/gb/status/alerts/fuel.wav | Bin .../audio/en/gb/status/alerts/headspeed.wav | Bin .../audio/en/gb/status/alerts/lowfuel.wav | Bin .../audio/en/gb/status/alerts/lowvoltage.wav | Bin .../dashx/audio/en/gb/status/alerts/lq.wav | Bin .../dashx/audio/en/gb/status/alerts/mcu.wav | Bin .../audio/en/gb/status/alerts/remaining.wav | Bin .../dashx/audio/en/gb/status/alerts/timer.wav | Bin .../audio/en/gb/status/alerts/voltage.wav | Bin .../dashx/audio/en/us/app/timeout.wav | Bin .../dashx/audio/en/us/events/alerts/armed.wav | Bin .../audio/en/us/events/alerts/disarmed.wav | Bin .../audio/en/us/events/alerts/lowfuel.wav | Bin .../audio/en/us/events/alerts/lowvoltage.wav | Bin .../audio/en/us/events/alerts/profile.wav | Bin .../dashx/audio/en/us/events/alerts/rates.wav | Bin .../audio/en/us/status/alerts/current.wav | Bin .../dashx/audio/en/us/status/alerts/esc.wav | Bin .../dashx/audio/en/us/status/alerts/fuel.wav | Bin .../audio/en/us/status/alerts/headspeed.wav | Bin .../audio/en/us/status/alerts/lowfuel.wav | Bin .../audio/en/us/status/alerts/lowvoltage.wav | Bin .../dashx/audio/en/us/status/alerts/lq.wav | Bin .../dashx/audio/en/us/status/alerts/mcu.wav | Bin .../audio/en/us/status/alerts/remaining.wav | Bin .../dashx/audio/en/us/status/alerts/timer.wav | Bin .../audio/en/us/status/alerts/voltage.wav | Bin {scripts => src}/dashx/audio/warn.wav | Bin {scripts => src}/dashx/i18n/de.json | 0 {scripts => src}/dashx/i18n/en.json | 0 {scripts => src}/dashx/i18n/es.json | 0 {scripts => src}/dashx/i18n/fr.json | 0 {scripts => src}/dashx/i18n/it.json | 0 {scripts => src}/dashx/i18n/nl.json | 0 {scripts => src}/dashx/lib/ini.lua | 0 {scripts => src}/dashx/lib/utils.lua | 0 {scripts => src}/dashx/main.lua | 0 {scripts => src}/dashx/sim/sensors/README.txt | 0 {scripts => src}/dashx/sim/sensors/accx.lua | 0 {scripts => src}/dashx/sim/sensors/accy.lua | 0 {scripts => src}/dashx/sim/sensors/accz.lua | 0 {scripts => src}/dashx/sim/sensors/adj_f.lua | 0 {scripts => src}/dashx/sim/sensors/adj_v.lua | 0 .../dashx/sim/sensors/altitude.lua | 0 .../dashx/sim/sensors/armdisableflags.lua | 0 {scripts => src}/dashx/sim/sensors/armed.lua | 0 .../dashx/sim/sensors/armflags.lua | 0 .../dashx/sim/sensors/attpitch.lua | 0 .../dashx/sim/sensors/attroll.lua | 0 {scripts => src}/dashx/sim/sensors/attyaw.lua | 0 .../dashx/sim/sensors/bec_voltage.lua | 0 .../dashx/sim/sensors/cell_count.lua | 0 .../dashx/sim/sensors/consumption.lua | 0 .../dashx/sim/sensors/current.lua | 0 .../dashx/sim/sensors/flightmode.lua | 0 {scripts => src}/dashx/sim/sensors/fuel.lua | 0 .../dashx/sim/sensors/governor.lua | 0 .../dashx/sim/sensors/gps_sats.lua | 0 .../dashx/sim/sensors/groundspeed.lua | 0 .../dashx/sim/sensors/isconnected.lua | 0 .../dashx/sim/sensors/pid_profile.lua | 0 .../dashx/sim/sensors/profile.lua | 0 .../dashx/sim/sensors/rate_profile.lua | 0 {scripts => src}/dashx/sim/sensors/rpm.lua | 0 .../sim/sensors/simevent_telemetry_state.lua | 0 .../dashx/sim/sensors/temp_esc.lua | 0 .../dashx/sim/sensors/temp_mcu.lua | 0 .../dashx/sim/sensors/throttle_percent.lua | 0 .../dashx/sim/sensors/voltage.lua | 0 .../dashx/tasks/callback/callback.lua | 0 .../dashx/tasks/callback/init.lua | 0 .../dashx/tasks/developer/developer.lua | 0 .../dashx/tasks/developer/init.lua | 0 .../dashx/tasks/events/events.lua | 0 {scripts => src}/dashx/tasks/events/init.lua | 0 .../dashx/tasks/events/tasks/flightmode.lua | 0 .../dashx/tasks/events/tasks/rxmap.lua | 0 .../dashx/tasks/events/tasks/stats.lua | 0 .../dashx/tasks/events/tasks/switches.lua | 0 .../dashx/tasks/events/tasks/telemetry.lua | 0 .../dashx/tasks/events/tasks/timer.lua | 0 {scripts => src}/dashx/tasks/logger/init.lua | 0 .../dashx/tasks/logger/lib/log.lua | 0 .../dashx/tasks/logger/logger.lua | 0 {scripts => src}/dashx/tasks/logging/init.lua | 0 .../dashx/tasks/logging/logging.lua | 0 .../dashx/tasks/onconnect/init.lua | 0 .../dashx/tasks/onconnect/tasks.lua | 0 .../tasks/onconnect/tasks/high/apiversion.lua | 0 .../onconnect/tasks/high/sensorstats.lua | 0 .../tasks/onconnect/tasks/high/timer.lua | 0 .../dashx/tasks/onconnect/tasks/high/uid.lua | 0 .../tasks/onconnect/tasks/low/battery.lua | 0 .../dashx/tasks/onconnect/tasks/low/rxmap.lua | 0 .../tasks/medium/modelpreferences.lua | 0 .../dashx/tasks/sensors/frsky.lua | 0 {scripts => src}/dashx/tasks/sensors/init.lua | 0 .../dashx/tasks/sensors/lib/smartfuel.lua | 0 .../tasks/sensors/lib/smartfuelvoltage.lua | 0 .../dashx/tasks/sensors/sensors.lua | 0 {scripts => src}/dashx/tasks/sensors/sim.lua | 0 .../dashx/tasks/sensors/smart.lua | 0 .../dashx/tasks/simevent/init.lua | 0 .../dashx/tasks/simevent/simevent.lua | 0 {scripts => src}/dashx/tasks/tasks.lua | 0 .../dashx/tasks/telemetry/init.lua | 0 .../dashx/tasks/telemetry/telemetry.lua | 0 {scripts => src}/dashx/tasks/timer/init.lua | 0 {scripts => src}/dashx/tasks/timer/timer.lua | 0 .../dashx/widgets/dashboard/dashboard.lua | 0 .../widgets/dashboard/gfx/default_image.png | Bin .../dashx/widgets/dashboard/gfx/dials/1.png | Bin .../dashx/widgets/dashboard/gfx/dials/10.png | Bin .../dashx/widgets/dashboard/gfx/dials/11.png | Bin .../dashx/widgets/dashboard/gfx/dials/12.png | Bin .../dashx/widgets/dashboard/gfx/dials/2.png | Bin .../dashx/widgets/dashboard/gfx/dials/3.png | Bin .../dashx/widgets/dashboard/gfx/dials/4.png | Bin .../dashx/widgets/dashboard/gfx/dials/5.png | Bin .../dashx/widgets/dashboard/gfx/dials/6.png | Bin .../dashx/widgets/dashboard/gfx/dials/7.png | Bin .../dashx/widgets/dashboard/gfx/dials/8.png | Bin .../dashx/widgets/dashboard/gfx/dials/9.png | Bin .../dashx/widgets/dashboard/gfx/logo.png | Bin .../dashx/widgets/dashboard/init.lua | 0 .../dashx/widgets/dashboard/lib/loaders.lua | 0 .../dashx/widgets/dashboard/lib/utils.lua | 0 .../dashx/widgets/dashboard/objects/dial.lua | 0 .../widgets/dashboard/objects/dial/image.lua | 0 .../dashboard/objects/dial/rainbow.lua | 0 .../dashx/widgets/dashboard/objects/func.lua | 0 .../widgets/dashboard/objects/func/func.lua | 0 .../dashx/widgets/dashboard/objects/gauge.lua | 0 .../widgets/dashboard/objects/gauge/arc.lua | 0 .../widgets/dashboard/objects/gauge/bar.lua | 0 .../widgets/dashboard/objects/gauge/ring.lua | 0 .../widgets/dashboard/objects/gauge/step.lua | 0 .../dashx/widgets/dashboard/objects/image.lua | 0 .../widgets/dashboard/objects/image/image.lua | 0 .../widgets/dashboard/objects/image/model.lua | 0 .../widgets/dashboard/objects/navigation.lua | 0 .../dashboard/objects/navigation/ah.lua | 0 .../dashx/widgets/dashboard/objects/text.lua | 0 .../dashboard/objects/text/apiversion.lua | 0 .../dashboard/objects/text/armflags.lua | 0 .../widgets/dashboard/objects/text/clock.lua | 0 .../dashboard/objects/text/craftname.lua | 0 .../dashboard/objects/text/governor.lua | 0 .../dashboard/objects/text/pidrates.lua | 0 .../dashboard/objects/text/session.lua | 0 .../widgets/dashboard/objects/text/stats.lua | 0 .../dashboard/objects/text/telemetry.lua | 0 .../widgets/dashboard/objects/text/text.lua | 0 .../widgets/dashboard/objects/text/watts.lua | 0 .../dashx/widgets/dashboard/objects/time.lua | 0 .../widgets/dashboard/objects/time/count.lua | 0 .../widgets/dashboard/objects/time/flight.lua | 0 .../widgets/dashboard/objects/time/total.lua | 0 .../widgets/dashboard/themes/@rt-rc/icon.png | Bin .../dashboard/themes/@rt-rc/inflight.lua | 0 .../widgets/dashboard/themes/@rt-rc/init.lua | 0 .../dashboard/themes/@rt-rc/postflight.lua | 0 .../dashboard/themes/@rt-rc/preflight.lua | 0 .../widgets/dashboard/themes/default/icon.png | Bin .../dashboard/themes/default/inflight.lua | 0 .../widgets/dashboard/themes/default/init.lua | 0 .../dashboard/themes/default/postflight.lua | 0 .../dashboard/themes/default/preflight.lua | 0 259 files changed, 540 insertions(+), 529 deletions(-) create mode 100644 .vscode/deploy.json create mode 100644 .vscode/scripts/__pycache__/subprocess_conout.cpython-313.pyc create mode 100644 .vscode/scripts/deploy_step_i18n.py create mode 100644 .vscode/scripts/deploy_step_soundpack.py create mode 100644 .vscode/scripts/dummy.py create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json rename {scripts => src}/dashx/.vscode/launch.json (100%) rename {scripts => src}/dashx/app/app.lua (100%) rename {scripts => src}/dashx/app/gfx/icon.png (100%) rename {scripts => src}/dashx/app/gfx/unsupported.png (100%) rename {scripts => src}/dashx/app/lib/ui.lua (100%) rename {scripts => src}/dashx/app/lib/utils.lua (100%) rename {scripts => src}/dashx/app/modules/init.lua (100%) rename {scripts => src}/dashx/app/modules/logs/gfx/folder.png (100%) rename {scripts => src}/dashx/app/modules/logs/gfx/logs.png (100%) rename {scripts => src}/dashx/app/modules/logs/help.lua (100%) rename {scripts => src}/dashx/app/modules/logs/init.lua (100%) rename {scripts => src}/dashx/app/modules/logs/lib/utils.lua (100%) rename {scripts => src}/dashx/app/modules/logs/logs_logs.lua (100%) rename {scripts => src}/dashx/app/modules/logs/logs_view.lua (100%) rename {scripts => src}/dashx/app/modules/model/gfx/battery.png (100%) rename {scripts => src}/dashx/app/modules/model/gfx/triggers.png (100%) rename {scripts => src}/dashx/app/modules/model/help.lua (100%) rename {scripts => src}/dashx/app/modules/model/init.lua (100%) rename {scripts => src}/dashx/app/modules/model/model.lua (100%) rename {scripts => src}/dashx/app/modules/model/model.png (100%) rename {scripts => src}/dashx/app/modules/model/tools/battery.lua (100%) rename {scripts => src}/dashx/app/modules/model/tools/triggers.lua (100%) rename {scripts => src}/dashx/app/modules/sections.lua (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/audio.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/audio_events.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/audio_switches.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/dashboard.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/dashboard_settings.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/dashboard_theme.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/development.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/general.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/localizations.png (100%) rename {scripts => src}/dashx/app/modules/settings/gfx/model.png (100%) rename {scripts => src}/dashx/app/modules/settings/help.lua (100%) rename {scripts => src}/dashx/app/modules/settings/init.lua (100%) rename {scripts => src}/dashx/app/modules/settings/settings.lua (100%) rename {scripts => src}/dashx/app/modules/settings/settings.png (100%) rename {scripts => src}/dashx/app/modules/settings/tools/audio.lua (100%) rename {scripts => src}/dashx/app/modules/settings/tools/audio_events.lua (100%) rename {scripts => src}/dashx/app/modules/settings/tools/audio_switches.lua (100%) rename {scripts => src}/dashx/app/modules/settings/tools/dashboard.lua (100%) rename {scripts => src}/dashx/app/modules/settings/tools/dashboard_settings.lua (100%) rename {scripts => src}/dashx/app/modules/settings/tools/dashboard_theme.lua (100%) rename {scripts => src}/dashx/app/modules/settings/tools/development.lua (100%) rename {scripts => src}/dashx/app/modules/settings/tools/general.lua (100%) rename {scripts => src}/dashx/app/modules/settings/tools/localizations.lua (100%) rename {scripts => src}/dashx/app/radios.lua (100%) rename {scripts => src}/dashx/audio/alarm.wav (100%) rename {scripts => src}/dashx/audio/beep.wav (100%) rename {scripts => src}/dashx/audio/en/default/app/timeout.wav (100%) rename {scripts => src}/dashx/audio/en/default/events/alerts/armed.wav (100%) rename {scripts => src}/dashx/audio/en/default/events/alerts/disarmed.wav (100%) rename {scripts => src}/dashx/audio/en/default/events/alerts/lowfuel.wav (100%) rename {scripts => src}/dashx/audio/en/default/events/alerts/lowvoltage.wav (100%) rename {scripts => src}/dashx/audio/en/default/events/alerts/profile.wav (100%) rename {scripts => src}/dashx/audio/en/default/events/alerts/rates.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/current.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/esc.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/fuel.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/headspeed.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/lowfuel.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/lowvoltage.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/lq.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/mcu.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/remaining.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/timer.wav (100%) rename {scripts => src}/dashx/audio/en/default/status/alerts/voltage.wav (100%) rename {scripts => src}/dashx/audio/en/gb/app/timeout.wav (100%) rename {scripts => src}/dashx/audio/en/gb/events/alerts/armed.wav (100%) rename {scripts => src}/dashx/audio/en/gb/events/alerts/disarmed.wav (100%) rename {scripts => src}/dashx/audio/en/gb/events/alerts/lowfuel.wav (100%) rename {scripts => src}/dashx/audio/en/gb/events/alerts/lowvoltage.wav (100%) rename {scripts => src}/dashx/audio/en/gb/events/alerts/profile.wav (100%) rename {scripts => src}/dashx/audio/en/gb/events/alerts/rates.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/current.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/esc.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/fuel.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/headspeed.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/lowfuel.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/lowvoltage.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/lq.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/mcu.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/remaining.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/timer.wav (100%) rename {scripts => src}/dashx/audio/en/gb/status/alerts/voltage.wav (100%) rename {scripts => src}/dashx/audio/en/us/app/timeout.wav (100%) rename {scripts => src}/dashx/audio/en/us/events/alerts/armed.wav (100%) rename {scripts => src}/dashx/audio/en/us/events/alerts/disarmed.wav (100%) rename {scripts => src}/dashx/audio/en/us/events/alerts/lowfuel.wav (100%) rename {scripts => src}/dashx/audio/en/us/events/alerts/lowvoltage.wav (100%) rename {scripts => src}/dashx/audio/en/us/events/alerts/profile.wav (100%) rename {scripts => src}/dashx/audio/en/us/events/alerts/rates.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/current.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/esc.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/fuel.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/headspeed.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/lowfuel.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/lowvoltage.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/lq.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/mcu.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/remaining.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/timer.wav (100%) rename {scripts => src}/dashx/audio/en/us/status/alerts/voltage.wav (100%) rename {scripts => src}/dashx/audio/warn.wav (100%) rename {scripts => src}/dashx/i18n/de.json (100%) rename {scripts => src}/dashx/i18n/en.json (100%) rename {scripts => src}/dashx/i18n/es.json (100%) rename {scripts => src}/dashx/i18n/fr.json (100%) rename {scripts => src}/dashx/i18n/it.json (100%) rename {scripts => src}/dashx/i18n/nl.json (100%) rename {scripts => src}/dashx/lib/ini.lua (100%) rename {scripts => src}/dashx/lib/utils.lua (100%) rename {scripts => src}/dashx/main.lua (100%) rename {scripts => src}/dashx/sim/sensors/README.txt (100%) rename {scripts => src}/dashx/sim/sensors/accx.lua (100%) rename {scripts => src}/dashx/sim/sensors/accy.lua (100%) rename {scripts => src}/dashx/sim/sensors/accz.lua (100%) rename {scripts => src}/dashx/sim/sensors/adj_f.lua (100%) rename {scripts => src}/dashx/sim/sensors/adj_v.lua (100%) rename {scripts => src}/dashx/sim/sensors/altitude.lua (100%) rename {scripts => src}/dashx/sim/sensors/armdisableflags.lua (100%) rename {scripts => src}/dashx/sim/sensors/armed.lua (100%) rename {scripts => src}/dashx/sim/sensors/armflags.lua (100%) rename {scripts => src}/dashx/sim/sensors/attpitch.lua (100%) rename {scripts => src}/dashx/sim/sensors/attroll.lua (100%) rename {scripts => src}/dashx/sim/sensors/attyaw.lua (100%) rename {scripts => src}/dashx/sim/sensors/bec_voltage.lua (100%) rename {scripts => src}/dashx/sim/sensors/cell_count.lua (100%) rename {scripts => src}/dashx/sim/sensors/consumption.lua (100%) rename {scripts => src}/dashx/sim/sensors/current.lua (100%) rename {scripts => src}/dashx/sim/sensors/flightmode.lua (100%) rename {scripts => src}/dashx/sim/sensors/fuel.lua (100%) rename {scripts => src}/dashx/sim/sensors/governor.lua (100%) rename {scripts => src}/dashx/sim/sensors/gps_sats.lua (100%) rename {scripts => src}/dashx/sim/sensors/groundspeed.lua (100%) rename {scripts => src}/dashx/sim/sensors/isconnected.lua (100%) rename {scripts => src}/dashx/sim/sensors/pid_profile.lua (100%) rename {scripts => src}/dashx/sim/sensors/profile.lua (100%) rename {scripts => src}/dashx/sim/sensors/rate_profile.lua (100%) rename {scripts => src}/dashx/sim/sensors/rpm.lua (100%) rename {scripts => src}/dashx/sim/sensors/simevent_telemetry_state.lua (100%) rename {scripts => src}/dashx/sim/sensors/temp_esc.lua (100%) rename {scripts => src}/dashx/sim/sensors/temp_mcu.lua (100%) rename {scripts => src}/dashx/sim/sensors/throttle_percent.lua (100%) rename {scripts => src}/dashx/sim/sensors/voltage.lua (100%) rename {scripts => src}/dashx/tasks/callback/callback.lua (100%) rename {scripts => src}/dashx/tasks/callback/init.lua (100%) rename {scripts => src}/dashx/tasks/developer/developer.lua (100%) rename {scripts => src}/dashx/tasks/developer/init.lua (100%) rename {scripts => src}/dashx/tasks/events/events.lua (100%) rename {scripts => src}/dashx/tasks/events/init.lua (100%) rename {scripts => src}/dashx/tasks/events/tasks/flightmode.lua (100%) rename {scripts => src}/dashx/tasks/events/tasks/rxmap.lua (100%) rename {scripts => src}/dashx/tasks/events/tasks/stats.lua (100%) rename {scripts => src}/dashx/tasks/events/tasks/switches.lua (100%) rename {scripts => src}/dashx/tasks/events/tasks/telemetry.lua (100%) rename {scripts => src}/dashx/tasks/events/tasks/timer.lua (100%) rename {scripts => src}/dashx/tasks/logger/init.lua (100%) rename {scripts => src}/dashx/tasks/logger/lib/log.lua (100%) rename {scripts => src}/dashx/tasks/logger/logger.lua (100%) rename {scripts => src}/dashx/tasks/logging/init.lua (100%) rename {scripts => src}/dashx/tasks/logging/logging.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/init.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/tasks.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/tasks/high/apiversion.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/tasks/high/sensorstats.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/tasks/high/timer.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/tasks/high/uid.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/tasks/low/battery.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/tasks/low/rxmap.lua (100%) rename {scripts => src}/dashx/tasks/onconnect/tasks/medium/modelpreferences.lua (100%) rename {scripts => src}/dashx/tasks/sensors/frsky.lua (100%) rename {scripts => src}/dashx/tasks/sensors/init.lua (100%) rename {scripts => src}/dashx/tasks/sensors/lib/smartfuel.lua (100%) rename {scripts => src}/dashx/tasks/sensors/lib/smartfuelvoltage.lua (100%) rename {scripts => src}/dashx/tasks/sensors/sensors.lua (100%) rename {scripts => src}/dashx/tasks/sensors/sim.lua (100%) rename {scripts => src}/dashx/tasks/sensors/smart.lua (100%) rename {scripts => src}/dashx/tasks/simevent/init.lua (100%) rename {scripts => src}/dashx/tasks/simevent/simevent.lua (100%) rename {scripts => src}/dashx/tasks/tasks.lua (100%) rename {scripts => src}/dashx/tasks/telemetry/init.lua (100%) rename {scripts => src}/dashx/tasks/telemetry/telemetry.lua (100%) rename {scripts => src}/dashx/tasks/timer/init.lua (100%) rename {scripts => src}/dashx/tasks/timer/timer.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/dashboard.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/default_image.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/1.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/10.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/11.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/12.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/2.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/3.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/4.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/5.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/6.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/7.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/8.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/dials/9.png (100%) rename {scripts => src}/dashx/widgets/dashboard/gfx/logo.png (100%) rename {scripts => src}/dashx/widgets/dashboard/init.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/lib/loaders.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/lib/utils.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/dial.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/dial/image.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/dial/rainbow.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/func.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/func/func.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/gauge.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/gauge/arc.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/gauge/bar.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/gauge/ring.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/gauge/step.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/image.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/image/image.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/image/model.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/navigation.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/navigation/ah.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/apiversion.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/armflags.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/clock.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/craftname.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/governor.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/pidrates.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/session.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/stats.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/telemetry.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/text.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/text/watts.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/time.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/time/count.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/time/flight.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/objects/time/total.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/@rt-rc/icon.png (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/@rt-rc/inflight.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/@rt-rc/init.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/@rt-rc/postflight.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/@rt-rc/preflight.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/default/icon.png (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/default/inflight.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/default/init.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/default/postflight.lua (100%) rename {scripts => src}/dashx/widgets/dashboard/themes/default/preflight.lua (100%) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 904e42e..ffcf837 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -32,18 +32,18 @@ jobs: PR_NUMBER='${{ github.event.pull_request.number }}' echo "GIT_VER=PR-${PR_NUMBER}" >> $GITHUB_ENV - # Build merged i18n JSON only for this locale (into scripts/dashx/i18n/.json) + # Build merged i18n JSON only for this locale (into src/dashx/i18n/.json) - name: Build merged i18n JSON (${{ matrix.lang }}) run: | python bin/i18n/build-single-json.py --only '${{ matrix.lang }}' - test -f "scripts/dashx/i18n/${{ matrix.lang }}.json" + test -f "src/dashx/i18n/${{ matrix.lang }}.json" # Stage scripts/ into build//scripts - name: Stage scripts tree run: | rm -rf "${{ env.STAGE }}" mkdir -p "${{ env.STAGE }}" - cp -a scripts/. "${{ env.STAGE }}/" + cp -a src/. "${{ env.STAGE }}/" # Resolve @i18n(...)@ tags in the staged tree using the staged locale file - name: Resolve i18n tags (locale=${{ matrix.lang }}) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index fc9565f..f2e68fa 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -32,7 +32,7 @@ jobs: echo "GIT_VER=commit-${SHORT_SHA}" >> $GITHUB_ENV echo "sha7=${SHORT_SHA}" >> $GITHUB_OUTPUT - # Build merged JSON for the specific locale (writes to scripts/dashx/i18n/.json) + # Build merged JSON for the specific locale (writes to src/dashx/i18n/.json) - name: Build i18n for this locale run: python bin/i18n/build-single-json.py --only ${{ matrix.lang }} @@ -42,7 +42,7 @@ jobs: run: | STAGE="build/${{ matrix.lang }}/scripts" mkdir -p "$STAGE" - rsync -a scripts/ "$STAGE"/ + rsync -a src/ "$STAGE"/ echo "STAGE=$STAGE" >> $GITHUB_ENV # Update version in the staged copy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0db53b2..36c5671 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,7 @@ jobs: run: | STAGE="build/${{ matrix.lang }}/scripts" mkdir -p "$STAGE" - rsync -a scripts/ "$STAGE"/ + rsync -a src/ "$STAGE"/ echo "STAGE=$STAGE" >> $GITHUB_ENV - name: Resolve i18n tags (locale=${{ matrix.lang }}) diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 85fe35c..433f8b6 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -46,7 +46,7 @@ jobs: run: | STAGE="build/${{ matrix.lang }}/scripts" mkdir -p "$STAGE" - rsync -a scripts/ "$STAGE"/ + rsync -a src/ "$STAGE"/ echo "STAGE=$STAGE" >> $GITHUB_ENV - name: Resolve i18n tags (locale=${{ matrix.lang }}) diff --git a/.gitignore b/.gitignore index a358891..11ddf39 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ bin/deploy/vscode/__pycache__/*.pyo bin/deploy/vscode/__pycache__/subprocess_conout.cpython-312.pyc /bin/sound-generator/soundpack/user/*.wav .vscode/scripts/__pycache__/subprocess_conout.cpython-312.pyc +simulator/* \ No newline at end of file diff --git a/.vscode/deploy.json b/.vscode/deploy.json new file mode 100644 index 0000000..23b697c --- /dev/null +++ b/.vscode/deploy.json @@ -0,0 +1,10 @@ +{ + "tgt_name": "dashx", + "ethossuite_bin": "C:\\Program Files\\Ethos Suite\\Ethos Suite.exe", + "serial_vid": "0483", + "serial_pid": "5750", + "serial_baud": 115200, + "serial_retries": 10, + "serial_retry_delay": 1.0, + "serial_name_hint": "FrSky" +} diff --git a/.vscode/launch.json b/.vscode/launch.json index aeb9790..1865523 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,202 +2,97 @@ "version": "0.2.0", "configurations": [ { - "name": "Deploy & Launch", + "name": "Deploy & Launch [SIM]", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--launch", - "--lang", "en", - ], - "console": "integratedTerminal", - "subProcess": false, // prevents auto-attach to child procs - "justMyCode": true, - "stopOnEntry": false, - }, - { - "name": "Deploy & Launch [Fast]", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--fileext", "fast", - "--launch", - "--lang", "en", - ], + "program": "${workspaceFolder}/.vscode/scripts/dummy.py", "console": "integratedTerminal", "subProcess": false, "justMyCode": true, "stopOnEntry": false, + "preLaunchTask": "Deploy & Launch [SIM]", + "postDebugTask": "ethos.Start" }, { - "name": "Deploy & Choose", + "name": "Deploy Radio", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--choose", - "--launch", - "--lang", "en", - ], + "program": "${workspaceFolder}/.vscode/scripts/dummy.py", "console": "integratedTerminal", "subProcess": false, "justMyCode": true, "stopOnEntry": false, + + "preLaunchTask": "Deploy Radio" }, { - "name": "Deploy & Choose [Fast]", + "name": "Deploy Radio [Fast]", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--fileext", "fast", - "--choose", - "--launch", - "--lang", "en", - ], + "program": "${workspaceFolder}/.vscode/scripts/dummy.py", "console": "integratedTerminal", "subProcess": false, "justMyCode": true, "stopOnEntry": false, + + "preLaunchTask": "Deploy Radio [Fast]" }, { - "name": "Deploy & Launch [choose lang]", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--launch", - "--lang", "${input:i18nLocale}", - ], - "console": "integratedTerminal", - "subProcess": false, // prevents auto-attach to child procs - "justMyCode": true, - "stopOnEntry": false, - }, - { - "name": "Deploy & Choose [choose lang]", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--choose", - "--launch", - "--lang", "${input:i18nLocale}", - ], - "console": "integratedTerminal", - "subProcess": false, - "justMyCode": true, - "stopOnEntry": false, - }, - { - "name": "Deploy Radio", + "name": "Deploy Radio + Serial Debug", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--radio", - "--lang", "en", - ], + "program": "${workspaceFolder}/.vscode/scripts/dummy.py", "console": "integratedTerminal", "subProcess": false, "justMyCode": true, "stopOnEntry": false, + + "preLaunchTask": "Deploy Radio + Serial Debug" }, { - "name": "Deploy Radio [Fast]", + "name": "Deploy Radio + Serial Debug [Fast]", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--fileext", "fast", - "--radio", - "--lang", "en", - ], + "program": "${workspaceFolder}/.vscode/scripts/dummy.py", "console": "integratedTerminal", "subProcess": false, "justMyCode": true, "stopOnEntry": false, + + "preLaunchTask": "Deploy Radio + Serial Debug [Fast]" }, { - "name": "Deploy Radio + Serial Debug", + "name": "Radio: Serial Debug", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--radio", - "--radio-debug", - "--lang", "en", - ], + "program": "${workspaceFolder}/.vscode/scripts/dummy.py", "console": "integratedTerminal", "subProcess": false, "justMyCode": true, "stopOnEntry": false, + + "preLaunchTask": "Radio: Serial Debug" }, { - "name": "Deploy Radio + Serial Debug [Fast]", + "name": "Switch Ethos Firmware", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--fileext", "fast", - "--radio", - "--radio-debug", - "--lang", "en", - ], + "program": "${workspaceFolder}/.vscode/scripts/dummy.py", "console": "integratedTerminal", "subProcess": false, "justMyCode": true, "stopOnEntry": false, - }, - { - "name": "Radio: Serial Debug", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": [ - "--config-env", "DASHX_CONFIG", - "--radio", - "--connect-only", - "--lang", "en", - ], - "console": "integratedTerminal" - }, + "postDebugTask": "ethos.SetFirmware" + }, { "name": "Clear locks", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/.vscode/scripts/deploy.py", - "args": ["--config-env", "DASHX_CONFIG", "--clear-lock"], + "program": "${workspaceFolder}/.vscode/scripts/dummy.py", "console": "integratedTerminal", "justMyCode": true, - "noDebug": true - } - ], - "inputs": [ - { - "id": "i18nLocale", - "type": "pickString", - "description": "Choose language", - "options": [ - "en", - "de", - "fr", - "es", - "it", - "nl", - ], - "default": "en" + + "preLaunchTask": "Clear locks" } ] } diff --git a/.vscode/scripts/__pycache__/subprocess_conout.cpython-312.pyc b/.vscode/scripts/__pycache__/subprocess_conout.cpython-312.pyc index cae0deba0e612037a3dc675cb04eb3a710f691e4..24235db7902d6627978c82ec2cdb673cacfc5d97 100644 GIT binary patch delta 75 zcmbOnu{eVJG%qg~0}!0#`@fOflie`X*(%08vm~Q5DW)htsU#ymw>Uo!$Slb(O3TSi b&nVH&DNWQ(1xgg_7MEt0q;4){pQi!<&2Ae* delta 59 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 literal 0 HcmV?d00001 diff --git a/.vscode/scripts/deploy.py b/.vscode/scripts/deploy.py index 2cd1856..eb6014d 100644 --- a/.vscode/scripts/deploy.py +++ b/.vscode/scripts/deploy.py @@ -14,19 +14,20 @@ import platform from hashlib import md5 from glob import glob +from pathlib import Path MIN_ETHOSSUITE_VERSION = "1.7.0" -SERIAL_PIDFILE = os.path.join(tempfile.gettempdir(), "rfdeploy-serial.pid") +SERIAL_PIDFILE = os.path.join(tempfile.gettempdir(), "deploy-serial.pid") DEPLOY_TO_RADIO = False # flag to control radio-only behavior THROTTLE_EXTS = None # unused when throttling all copies THROTTLE_MIN_BYTES = 0 # unused when throttling all copies -THROTTLE_CHUNK = 32 * 1024 # 16 KiB -THROTTLE_PAUSE_EVERY = 64 * 1024 # pause+fsync every 64 KiB written -THROTTLE_PAUSE_S = 0.1 # 200 ms +THROTTLE_CHUNK = 32 * 1024 # 32 KiB +THROTTLE_PAUSE_EVERY = 64 * 1024 # pause+fsync every 64 KiB written +THROTTLE_PAUSE_S = 0.1 # 100 ms # --- single-instance lock helpers -------------------------------------------- -LOCK_DEFAULT_NAME = "rfdeploy.single.lock" +LOCK_DEFAULT_NAME = "deploy.single.lock" if os.name == "nt": import msvcrt else: @@ -130,7 +131,7 @@ def release(self): def _lock_path_for_config(config_path: str) -> str: """Compute the exact lock file path used for this project/config.""" proj_key = md5(os.path.abspath(config_path).encode("utf-8")).hexdigest()[:8] - lock_name = f"rfdeploy-{proj_key}.lock" + lock_name = f"deploy-{proj_key}.lock" return os.path.join(tempfile.gettempdir(), lock_name) def parse_version(v: str): @@ -210,10 +211,10 @@ def _record_tail_pid_and_cleanup(): pass atexit.register(lambda: os.path.exists(SERIAL_PIDFILE) and os.remove(SERIAL_PIDFILE)) + def throttled_copyfile(src, dst): os.makedirs(os.path.dirname(dst), exist_ok=True) written_since_pause = 0 - # raw (unbuffered) file handles so we control write cadence precisely with open(src, 'rb', buffering=0) as fsrc, open(dst, 'wb', buffering=0) as fdst: while True: chunk = fsrc.read(THROTTLE_CHUNK) @@ -230,64 +231,21 @@ def throttled_copyfile(src, dst): time.sleep(THROTTLE_PAUSE_S) written_since_pause = 0 - # final drain fdst.flush() try: os.fsync(fdst.fileno()) except OSError: pass - # Match shutil.copy's behavior of preserving basic permission bits try: shutil.copymode(src, dst) except Exception: pass - -def minify_lua_file(filepath): - print(f"[MINIFY] (luamin) Processing: {filepath}") - - # Try to resolve luamin executable - luamin_cmd = shutil.which("luamin") - - if not luamin_cmd: - # Fallback for Windows NPM global installs - luamin_cmd = os.path.expandvars(r"%APPDATA%\\npm\\luamin.cmd") - - if not os.path.exists(luamin_cmd): - print("[MINIFY ERROR] 'luamin' not found in PATH or %APPDATA%\\npm.") - print("Please run: npm install -g luamin") - return - - try: - result = subprocess.run( - [luamin_cmd, '-f', filepath], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - encoding='utf-8', # ✅ force proper decoding - errors='replace' # ✅ optional: replace invalid chars to avoid crash - ) - - if result.returncode != 0: - print(f"[MINIFY ERROR] Failed to minify {filepath}:\n{result.stderr}") - return - - with open(filepath, 'w', encoding='utf-8') as f: - f.write(result.stdout) - - #print(f"[MINIFY] (luamin) Done: {filepath}") - - except Exception as e: - print(f"[MINIFY ERROR] Exception during luamin run: {e}") - def get_ethos_scripts_dir(ethossuite_bin, retries=1, delay=5): """ - Ask Ethos Suite for the SCRIPTS path. Robust against chatty output: - - Prefer explicit path lines (e.g. 'E:\\scripts') - - Else parse the 'New removable disks: [...]' blob and use mountpoint + '\\scripts' - - Else fall back to scanning drive letters for an existing '\\scripts' + Ask Ethos Suite for the SCRIPTS path. Robust against chatty output. """ import re, json, string @@ -296,11 +254,15 @@ def get_ethos_scripts_dir(ethossuite_bin, retries=1, delay=5): def _clean(line: str) -> str: line = (line or "").strip().strip('"').strip("'") - if not line: return "" + if not line: + return "" low = line.lower() - if low.startswith("exit code"): return "" - if low.startswith("new removable disks"): return line # keep for JSON parse - if line.startswith("{") or line.startswith("["): return line + if low.startswith("exit code"): + return "" + if low.startswith("new removable disks"): + return line # keep for JSON parse + if line.startswith("{") or line.startswith("["): + return line return line def _scan_drives_for_scripts(): @@ -322,27 +284,21 @@ def _scan_drives_for_scripts(): raw = res.stdout or "" lines = [_clean(l) for l in raw.splitlines() if l.strip()] - # 1) Prefer explicit path-like lines candidates = [l for l in lines if path_re.match(l)] existing = [os.path.normpath(p) for p in candidates if os.path.isdir(os.path.normpath(p))] if existing: - # Prefer the one whose basename is 'scripts' preferred = [p for p in existing if os.path.basename(p).lower() == "scripts"] return preferred[0] if preferred else existing[0] if candidates: - # If nothing exists yet, return the most plausible; wait_for_scripts_mount() will verify preferred = [p for p in candidates if "scripts" in p.lower()] return os.path.normpath(preferred[0] if preferred else candidates[0]) - # 2) Parse the "New removable disks: [...]" blob if present blob_lines = [l for l in lines if l.lower().startswith("new removable disks")] if blob_lines: - # Extract JSON array portion (first '[' ... last ']') blob = blob_lines[-1] try: start = blob.index('['); end = blob.rindex(']') + 1 arr = json.loads(blob[start:end]) - # Prefer the entry with radioDisk == 'radio', else first that has a mountpoint choice = None for item in arr: if item.get("radioDisk") == "radio": @@ -359,12 +315,10 @@ def _scan_drives_for_scripts(): except Exception: pass - # 3) Last resort: scan mounted drive letters for an existing \scripts scanned = _scan_drives_for_scripts() if scanned: return scanned - # No usable hint; let caller retry raise RuntimeError("No path-like output from Ethos Suite; mount not ready yet.") except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired, RuntimeError) as e: last_err = e @@ -374,119 +328,9 @@ def _scan_drives_for_scripts(): else: raise last_err - """ - Ask Ethos Suite for the SCRIPTS path. Returns a normalized existing path. - Skips noisy lines like "New removable disks: [...]". - """ - import re - - cmd = [ethossuite_bin, "--get-path", "SCRIPTS", "--radio", "auto"] - path_re = re.compile(r'^(?:[A-Za-z]:\\|\\\\\?\\|//)[^\r\n]+$') # Windows drive, \\?\ or UNC - - def _clean(line: str) -> str: - # Strip quotes and whitespace - line = line.strip().strip('"').strip("'") - # Ignore obvious noise - if not line: - return "" - if line.lower().startswith("exit code"): - return "" - if line.lower().startswith("new removable disks"): - return "" - if line.startswith("{") or line.startswith("["): - return "" - return line - - last_err = None - for attempt in range(retries + 1): - try: - res = subprocess.run(cmd, text=True, capture_output=True, timeout=15) - if res.returncode != 0: - raise subprocess.CalledProcessError(res.returncode, cmd, output=res.stdout, stderr=res.stderr) - - raw_lines = (res.stdout or "").splitlines() - # Clean and filter - candidates = [] - for l in raw_lines: - c = _clean(l) - if not c: - continue - # Prefer things that look like paths, ideally ending with "scripts" - if path_re.match(c): - candidates.append(c) - - # Heuristics: prefer existing paths, then those that contain \scripts - existing = [os.path.normpath(p) for p in candidates if os.path.isdir(os.path.normpath(p))] - if existing: - # If multiple, prefer one whose basename is 'scripts' - preferred = [p for p in existing if os.path.basename(p).lower() == "scripts"] - return preferred[0] if preferred else existing[0] - - # If nothing exists yet, still try best-looking candidate (may mount a moment later) - if candidates: - # Prefer entries that contain 'scripts' - preferred = [p for p in candidates if "scripts" in p.lower()] - return os.path.normpath(preferred[0] if preferred else candidates[0]) - - # No usable line; fall through to retry - raise RuntimeError("No path-like output from Ethos Suite.") - - except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired, RuntimeError) as e: - last_err = e - if attempt < retries: - print(f"[ETHOS] Could not get SCRIPTS path (attempt {attempt+1}/{retries+1}). Retrying in {delay}s…") - time.sleep(delay) - else: - raise last_err - - - """ - Ask Ethos Suite for the SCRIPTS path. Retries after `delay` seconds - if the tool returns no path or fails. Raises on final failure. - """ - cmd = [ethossuite_bin, "--get-path", "SCRIPTS", "--radio", "auto"] - last_err = None - - for attempt in range(retries + 1): - try: - result = subprocess.run( - cmd, - text=True, - capture_output=True, - timeout=15 - ) - - if result.returncode != 0: - raise subprocess.CalledProcessError( - result.returncode, cmd, output=result.stdout, stderr=result.stderr - ) - lines = [l.strip() for l in (result.stdout or "").splitlines() if l.strip()] - if not lines: - raise RuntimeError("No output from Ethos Suite.") - - # If the last line starts with 'exit code', grab the previous one - if lines[-1].lower().startswith("exit code") and len(lines) >= 2: - path_line = lines[-2] - else: - path_line = lines[-1] - - return os.path.normpath(path_line) - - except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired, RuntimeError) as e: - last_err = e - if attempt < retries: - print(f"[ETHOS] Could not get SCRIPTS path (attempt {attempt+1}/{retries+1}). Retrying in {delay}s…") - time.sleep(delay) - else: - raise last_err - -# Permission handler for Windows rm errors def on_rm_error(func, path, exc_info): - """Resilient rm error handler: - - ignore if the path vanished - - chmod + retry with small backoff for common transient Windows errors - """ + """Resilient rm error handler.""" e = exc_info[1] if isinstance(e, FileNotFoundError): return @@ -502,7 +346,6 @@ def on_rm_error(func, path, exc_info): except FileNotFoundError: return except OSError as oe: - # 5=Access denied, 32=Sharing violation, 483=Fatal device hw error if getattr(oe, "winerror", 0) in (5, 32, 483): time.sleep(0.2 * (i + 1)) continue @@ -518,14 +361,12 @@ def flush_fs(): print(f"[WARN] os.sync failed: {e}") -# Throttled, progress-bar deletion to play nice with slow devices DELETE_BATCH = 1 DELETE_PAUSE_S = 0.10 # 100ms def throttled_rmtree(root, batch=DELETE_BATCH, pause=DELETE_PAUSE_S): if not os.path.exists(root): return - # Collect files for a stable progress bar files = [] for dp, _, fs in os.walk(root): for f in fs: @@ -550,8 +391,6 @@ def throttled_rmtree(root, batch=DELETE_BATCH, pause=DELETE_PAUSE_S): time.sleep(pause * (attempt + 1)) attempt += 1 continue - # Give up on odd errors rather than crash the whole deploy - # (old file may be locked by AV or indexing; we'll try removing the directory later) break bar.update(1) @@ -561,7 +400,6 @@ def throttled_rmtree(root, batch=DELETE_BATCH, pause=DELETE_PAUSE_S): bar.close() - # Remove directories bottom-up; ignore stubborn remnants for dp, dns, _ in sorted(os.walk(root), key=lambda t: t[0], reverse=True): for d in dns: try: @@ -587,29 +425,21 @@ def delete_tree(path): time.sleep(DELETE_PAUSE_S) - def safe_full_copy(srcall, out_dir): - """Safer full-copy for slow FAT32 targets: - - If current target exists, rotate it to '.old' - - Delete the '.old' folder - - Copy new files freshly into '' - Includes small delays + best-effort flushes to give the device time to settle. - """ + """Safer full-copy for slow FAT32 targets.""" global pbar if os.path.isdir(out_dir): old_dir = out_dir + ".old" - # If a previous backup exists, remove it first if os.path.isdir(old_dir): print("Deleting previous backup…") delete_tree(old_dir) flush_fs() time.sleep(2) - # Rotate current folder to .old try: print(f"Renaming existing to {os.path.basename(old_dir)}…") - os.replace(out_dir, old_dir) # Atomic on same volume + os.replace(out_dir, old_dir) except Exception as e: print(f"[WARN] Rename failed ({e}). Falling back to direct delete.") print("Deleting files…") @@ -617,7 +447,6 @@ def safe_full_copy(srcall, out_dir): flush_fs() time.sleep(2) - # Delete the rotated .old folder if os.path.isdir(old_dir): print("Deleting files…") delete_tree(old_dir) @@ -630,46 +459,55 @@ def safe_full_copy(srcall, out_dir): shutil.copytree(srcall, out_dir, dirs_exist_ok=True, copy_function=copy_verbose) pbar.close() -# parse args early so we can know --config-env -_p = argparse.ArgumentParser(add_help=False) -_p.add_argument("--config-env", help="Name of env var that contains the config path") -_early, _ = _p.parse_known_args() +# --- config: derive repo root and load deploy.json ---------------------------- +ROOT = Path(__file__).resolve().parents[2] -env_name = _early.config_env -CONFIG_PATH = os.environ.get(env_name) +ROOT_CONFIG = ROOT / "deploy.json" +VSCODE_CONFIG = ROOT / ".vscode/deploy.json" -if not CONFIG_PATH: - print(f"[CONFIG ERROR] Environment variable '{env_name}' is not set.") - sys.exit(1) +config = { + "git_src": str(ROOT), +} -if not os.path.exists(CONFIG_PATH): - print(f"[CONFIG ERROR] Config file not found at path: {CONFIG_PATH}") +cfg_path = None +if ROOT_CONFIG.exists(): + cfg_path = ROOT_CONFIG +elif VSCODE_CONFIG.exists(): + cfg_path = VSCODE_CONFIG +else: + print("[ERROR] No deploy.json found in git root or .vscode/") sys.exit(1) +# NEW: Debug which base config file we are using +print(f"[CONFIG] Using base config file: {cfg_path}") + try: - with open(CONFIG_PATH) as f: - config = json.load(f) -except json.JSONDecodeError as e: - print(f"[CONFIG ERROR] Failed to parse JSON config file: {e}") + with open(cfg_path, "r") as f: + user_cfg = json.load(f) + if not isinstance(user_cfg, dict): + raise ValueError("Config JSON must contain an object at the top level.") + config.update(user_cfg) +except Exception as e: + print(f"[ERROR] Failed to load config: {e}") sys.exit(1) -pbar = None +if "tgt_name" not in config or not config["tgt_name"]: + print(f"[ERROR] Missing 'tgt_name' in {cfg_path}. Aborting.") + sys.exit(1) + +CONFIG_PATH = str(cfg_path) +pbar = None # === Ethos Serial + Serial Tail helpers ======================================= -DEFAULT_SERIAL_VID = "0483" # STM32 VCP / HID used by FrSky radios in Ethos logs +DEFAULT_SERIAL_VID = "0483" DEFAULT_SERIAL_PID = "5750" DEFAULT_SERIAL_BAUD = 115200 DEFAULT_SERIAL_RETRIES = 10 -DEFAULT_SERIAL_DELAY = 1.0 # seconds between attempts +DEFAULT_SERIAL_DELAY = 1.0 def ethos_serial(ethossuite_bin, action, radio=None): - """ - Start/stop Ethos serial debug mode. - action: 'start' or 'stop' - Returns (rc, stdout, stderr) and prints tool output. - """ cmd = [ethossuite_bin, "--serial", action, "--radio", "auto"] if radio: cmd += ["--radio", radio] @@ -688,9 +526,6 @@ def ethos_serial(ethossuite_bin, action, radio=None): def wait_for_scripts_mount(ethossuite_bin, attempts=10, delay=2): - """ - Poll Ethos for the SCRIPTS path until the directory actually exists. - """ last_err = None for i in range(attempts): try: @@ -705,8 +540,6 @@ def wait_for_scripts_mount(ethossuite_bin, attempts=10, delay=2): raise RuntimeError(f"Radio drive did not mount: {last_err}") - - def _find_com_port_by_vid_pid(vid_hex, pid_hex): try: from serial.tools import list_ports @@ -727,19 +560,15 @@ def _find_com_port(vid_hex=None, pid_hex=None, name_hint=None, allow_fuzzy_if_no print("[SERIAL] No serial ports detected.") return None - # Debug dump so you can see what's actually present print("[SERIAL] Detected ports:") for p in ports: desc = p.description or '' iface = getattr(p, 'interface', None) or '' print(f" - device={p.device} vid={p.vid} pid={p.pid} desc='{desc}' iface='{iface}' hwid='{p.hwid}'") - # 1) Strict VID/PID match if provided - # Helper to parse PID from the HWID string (e.g. 'USB VID:PID=0483:5750 ...') def _pid_from_hwid(hw): try: hw = hw or "" - # find ...PID=hhhh:pppp... token = "PID=" if token in hw: rhs = hw.split(token, 1)[1] @@ -749,7 +578,6 @@ def _pid_from_hwid(hw): pass return None - # 1) Strict VID/PID match if provided (no fuzzy fallback) if vid_hex and pid_hex: try: vid = int(vid_hex, 16) @@ -763,11 +591,9 @@ def _pid_from_hwid(hw): return p.device except Exception: pass - # If strict match not found, be conservative: keep waiting; do NOT choose another PID. print(f"[SERIAL] No exact VID:PID match yet ({vid_hex}:{pid_hex}). Will keep scanning.") return None - # 2) If only VID is known, prefer same-VID ports, and among them prefer PID from hwid/attr == desired if vid_hex and not pid_hex: try: vid = int(vid_hex, 16) @@ -781,19 +607,16 @@ def _pid_from_hwid(hw): except Exception: pass if candidates: - # Prefer 5750 if present (either via attribute or parsed from HWID) def _score(cp): cp_pid = getattr(cp, "pid", None) hw_pid = _pid_from_hwid(getattr(cp, "hwid", None)) if prefer_pid_from_hwid else None pid_val = cp_pid if cp_pid is not None else hw_pid - # Highest priority for 0x5750, then anything else but de-prioritize 0x5740 if pid_val == 0x5750: return 0 if pid_val == 0x5740: return 2 return 1 best = sorted(candidates, key=_score)[0] return best.device - # 3) Only if no VID/PID are provided AND allowed, use fuzzy hints if allow_fuzzy_if_no_vidpid and not vid_hex and not pid_hex: hints = [h.lower() for h in [name_hint, "frsky", "serial", "stm", "vcp", "x20", "x18", "x14"] if h] for p in ports: @@ -803,33 +626,20 @@ def _score(cp): return None - vid = int(vid_hex, 16) - pid = int(pid_hex, 16) - for p in list_ports.comports(): - try: - if p.vid == vid and p.pid == pid: - return p.device # e.g. 'COM7' - except Exception: - continue - return None - def tail_serial_debug(vid=DEFAULT_SERIAL_VID, pid=DEFAULT_SERIAL_PID, baud=DEFAULT_SERIAL_BAUD, retries=DEFAULT_SERIAL_RETRIES, delay=DEFAULT_SERIAL_DELAY, newline=b'\n', name_hint="Serial"): - "Try to attach to the radio serial log N times; print lines until Ctrl+C." try: import serial except Exception: print("[SERIAL] pyserial not installed. Install with: pip install pyserial") return 2 - # Give Windows a moment to enumerate after enabling serial time.sleep(1.5) port = None for i in range(retries): - # Strict VID/PID only; do not fall back to fuzzy when VID/PID are known port = _find_com_port(vid_hex=vid, pid_hex=pid, name_hint=name_hint, allow_fuzzy_if_no_vidpid=False) if port: @@ -842,8 +652,6 @@ def tail_serial_debug(vid=DEFAULT_SERIAL_VID, pid=DEFAULT_SERIAL_PID, return 3 print(f"[SERIAL] Connecting to {port} @ {baud} …") - # Some Windows setups expose the COM device and then rebind the driver briefly. - # Try multiple times to open the port before giving up; if it disappears, rescan. open_attempts = 8 for attempt in range(1, open_attempts+1): try: @@ -851,13 +659,11 @@ def tail_serial_debug(vid=DEFAULT_SERIAL_VID, pid=DEFAULT_SERIAL_PID, break except FileNotFoundError as e: print(f"[SERIAL] Open attempt {attempt}/{open_attempts} -> device vanished; rescanning…") - # Rescan for a replacement port that matches port = _find_com_port(vid_hex=vid, pid_hex=pid, name_hint=name_hint) if not port: import time as _t; _t.sleep(delay) continue except Exception as e: - # e.g. PermissionError / SerialException("Access is denied"), or still binding print(f"[SERIAL] Open attempt {attempt}/{open_attempts} failed: {e}") import time as _t; _t.sleep(delay) continue @@ -897,11 +703,11 @@ def tail_serial_debug(vid=DEFAULT_SERIAL_VID, pid=DEFAULT_SERIAL_PID, def copy_verbose(src, dst): pbar.update(1) if DEPLOY_TO_RADIO: - throttled_copyfile(src, dst) # paced writes for the radio - flush_fs(); - time.sleep(0.05) + throttled_copyfile(src, dst) + flush_fs() + time.sleep(0.05) else: - shutil.copy(src, dst) # normal fast copy for everything else + shutil.copy(src, dst) def count_files(dirpath, ext=None): @@ -912,48 +718,63 @@ def count_files(dirpath, ext=None): total += len(files) return total -# Interactive chooser -def choose_target(targets): - print("Available targets:") - for i, t in enumerate(targets, 1): - mark = '*' if t.get('default') else ' ' - print(f" [{i}] {t['name']} {mark}") - idx = None - while idx is None: - try: - sel = int(input("Select number: ")) - if 1 <= sel <= len(targets): - idx = sel - 1 - else: - print("Out of range") - except ValueError: - print("Enter a number") - return [targets[idx]] - - -def resolve_i18n_tags_in_place(out_dir, lang="en"): - # Path to the merged JSON created by step 1 - json_path = os.path.join(out_dir, "i18n", f"{lang}.json") # because scripts/dashx/** got copied already - if not os.path.isfile(json_path): - # Fallback for local build before copy, if needed: - json_path = os.path.join(config['git_src'], "scripts", "dashx", "i18n", f"{lang}.json") - if not os.path.isfile(json_path): - print(f"[I18N] Skipping: {lang}.json not found at {json_path}") - return - # Call the standalone resolver - import subprocess, sys - resolver = os.path.join(config['git_src'], ".vscode", "scripts", "resolve_i18n_tags.py") - if not os.path.isfile(resolver): - print(f"[I18N] Skipping: resolver not found at {resolver}") +def run_step_script(step, out_dir, lang="en"): + """ + Generic step runner. + + Conventions: + - If `step` ends with '.py' or contains a path separator, + treat it as a path (absolute or relative to git_src). + - Otherwise, look for: + /.vscode/scripts/deploy_step_.py + The step script is called as: + python