diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 616e9e6b33941..a2a62ebd88c1b 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1507,6 +1507,7 @@ ./services/web-apps/castopod.nix ./services/web-apps/coder.nix ./services/web-apps/changedetection-io.nix + ./services/web-apps/chasemapper.nix ./services/web-apps/chatgpt-retrieval-plugin.nix ./services/web-apps/cloudlog.nix ./services/web-apps/code-server.nix diff --git a/nixos/modules/services/web-apps/chasemapper.nix b/nixos/modules/services/web-apps/chasemapper.nix new file mode 100644 index 0000000000000..2dbcf6692f498 --- /dev/null +++ b/nixos/modules/services/web-apps/chasemapper.nix @@ -0,0 +1,492 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.chasemapper; + format = pkgs.formats.ini { }; + chaseCfg = format.generate "horusmapper.cfg" cfg.settings; +in +{ + options = { + services.chasemapper = { + enable = lib.mkEnableOption "chasemapper"; + + package = lib.mkPackageOption pkgs "chasemapper" { }; + + settings = { + profile_selection = { + profile_count = lib.mkOption { + default = 2; + description = '' + How many profiles have been defined + ''; + type = lib.types.int; + }; + default_profile = lib.mkOption { + default = 1; + description = '' + Index of the default profile (indexing from 1) + ''; + type = lib.types.int; + }; + }; + + profile_1 = { + profile_name = lib.mkOption { + default = "auto-rx"; + description = '' + Profile name - will be shown in the web client. + ''; + type = lib.types.str; + }; + telemetry_source_type = lib.mkOption { + default = "horus_udp"; + description = '' + Telemetry source type + ''; + type = lib.types.str; + }; + telemetry_source_port = lib.mkOption { + default = 55673; + description = '' + Telemetry source port (UDP) + ''; + type = lib.types.int; + }; + car_source_type = lib.mkOption { + default = "serial"; + description = '' + Car position source type + ''; + type = lib.types.str; + }; + car_source_port = lib.mkOption { + default = 55672; + description = '' + Car position source port (UDP) + ''; + type = lib.types.int; + }; + online_tracker = lib.mkOption { + default = "sondehub"; + description = '' + Online Tracker System + ''; + type = lib.types.str; + }; + }; + + profile_2 = { + profile_name = lib.mkOption { + default = "horus-gui"; + description = '' + Profile name + ''; + type = lib.types.str; + }; + telemetry_source_type = lib.mkOption { + default = "horus_udp"; + description = '' + Telemetry source type + ''; + type = lib.types.str; + }; + telemetry_source_port = lib.mkOption { + default = 55672; + description = '' + Telemetry source port (UDP) + ''; + type = lib.types.int; + }; + car_source_type = lib.mkOption { + default = "serial"; + description = '' + Car position source type + ''; + type = lib.types.str; + }; + car_source_port = lib.mkOption { + default = 55672; + description = '' + Car position source port (UDP) + ''; + type = lib.types.int; + }; + online_tracker = lib.mkOption { + default = "sondehubamateur"; + description = '' + Online Tracker System + ''; + type = lib.types.str; + }; + }; + + gpsd = { + gpsd_host = lib.mkOption { + default = "localhost"; + description = '' + GPSD Host + ''; + type = lib.types.str; + }; + gpsd_port = lib.mkOption { + default = 2947; + description = '' + GPSD Port + ''; + type = lib.types.int; + }; + }; + + gps_serial = { + gps_port = lib.mkOption { + default = "/dev/ttyUSB0"; + description = '' + GPS serial device + ''; + type = lib.types.str; + }; + gps_baud = lib.mkOption { + default = 9600; + description = '' + GPS baud rate + ''; + type = lib.types.int; + }; + }; + + map = { + flask_host = lib.mkOption { + default = "0.0.0.0"; + description = '' + Host to host webserver on + ''; + type = lib.types.str; + }; + flask_port = lib.mkOption { + default = 5001; + description = '' + Port for web server to listen on + ''; + type = lib.types.int; + }; + default_lat = lib.mkOption { + default = -34.9; + description = '' + Default Map Latitude + ''; + type = lib.types.float; + }; + default_lon = lib.mkOption { + default = 138.6; + description = '' + Default Map Longitude + ''; + type = lib.types.float; + }; + default_alt = lib.mkOption { + default = 0.0; + description = '' + Default Map Altitude + ''; + type = lib.types.float; + }; + payload_max_age = lib.mkOption { + default = 180; + description = '' + How long to keep payload data (minutes) + ''; + type = lib.types.int; + }; + thunderforest_api_key = lib.mkOption { + default = "none"; + description = '' + ThunderForest API Key + ''; + type = lib.types.str; + }; + stadia_api_key = lib.mkOption { + default = "none"; + description = '' + Stadia Maps API Key + ''; + type = lib.types.str; + }; + }; + + predictor = { + predictor_enabled = lib.mkOption { + default = true; + description = '' + Enable Predictor + ''; + type = lib.types.bool; + }; + default_burst = lib.mkOption { + default = 30000; + description = '' + Default burst + ''; + type = lib.types.int; + }; + default_descent_rate = lib.mkOption { + default = 5.0; + description = '' + Default descent rate + ''; + type = lib.types.float; + }; + ascent_rate_averaging = lib.mkOption { + default = 10; + description = '' + Ascent rate averaging + ''; + type = lib.types.int; + }; + offline_predictions = lib.mkOption { + default = false; + description = '' + Use offline predictions + ''; + type = lib.types.bool; + }; + pred_binary = lib.mkOption { + default = "./pred"; + description = '' + Predictory Binary Location + ''; + type = lib.types.str; + }; + gfs_directory = lib.mkOption { + default = "./gfs/"; + description = '' + Directory containing GFS model data + ''; + type = lib.types.str; + }; + model_download = lib.mkOption { + default = "none"; + description = '' + Wind Model Download Command + ''; + type = lib.types.str; + }; + }; + + offline_maps = { + tile_server_enabled = lib.mkOption { + default = false; + description = '' + Enable serving up maps from a directory of map tiles + ''; + type = lib.types.bool; + }; + tile_server_path = lib.mkOption { + default = "/home/pi/Maps/"; + description = '' + Path to map tiles + ''; + type = lib.types.str; + }; + }; + + habitat = { + habitat_upload_enabled = lib.mkOption { + default = false; + description = '' + Enable uploading of chase-car position to SondeHub / SondeHub-Amateur + ''; + type = lib.types.bool; + }; + habitat_call = lib.mkOption { + default = "N0CALL"; + description = '' + Callsign to use when uploading + ''; + type = lib.types.str; + }; + habitat_update_rate = lib.mkOption { + default = 30; + description = '' + Attempt to upload position to SondeHub every x seconds + ''; + type = lib.types.int; + }; + }; + + range_rings = { + range_rings_enabled = lib.mkOption { + default = false; + description = '' + Enable Range Rings + ''; + type = lib.types.bool; + }; + range_ring_quantity = lib.mkOption { + default = 5; + description = '' + Number of range rings to display + ''; + type = lib.types.int; + }; + range_ring_spacing = lib.mkOption { + default = 1000; + description = '' + Spacing between rings, in metres + ''; + type = lib.types.int; + }; + range_ring_weight = lib.mkOption { + default = 1.5; + description = '' + Weight of the ring, in pixels + ''; + type = lib.types.float; + }; + range_ring_color = lib.mkOption { + default = "red"; + description = '' + Color of the range rings + ''; + type = lib.types.str; + }; + range_ring_custom_color = lib.mkOption { + default = "#FF0000"; + description = '' + Custom range ring color, in hexadecimal #RRGGBB + ''; + type = lib.types.str; + }; + }; + + speedo = { + chase_car_speed = lib.mkOption { + default = true; + description = '' + Display the chase car speed at the bottom left of the display + ''; + type = lib.types.bool; + }; + }; + + bearings = { + max_bearings = lib.mkOption { + default = 300; + description = '' + Number of bearings to store + ''; + type = lib.types.int; + }; + max_bearing_age = lib.mkOption { + default = 10; + description = '' + Maximum age of bearings, in _minutes_ + ''; + type = lib.types.int; + }; + car_speed_gate = lib.mkOption { + default = 10; + description = '' + Car heading speed gate + ''; + type = lib.types.int; + }; + turn_rate_threshold = lib.mkOption { + default = 4.0; + description = '' + Turn rate threshold + ''; + type = lib.types.float; + }; + bearing_length = lib.mkOption { + default = 10; + description = '' + Bearing length in km + ''; + type = lib.types.int; + }; + bearing_weight = lib.mkOption { + default = 1.0; + description = '' + Weight of the bearing lines, in pixels + ''; + type = lib.types.float; + }; + bearing_color = lib.mkOption { + default = "red"; + description = '' + Color of the bearings + ''; + type = lib.types.str; + }; + bearing_custom_color = lib.mkOption { + default = "#FF0000"; + description = '' + Custom bearing color, in hexadecimal #RRGGBB + ''; + type = lib.types.str; + }; + }; + + units = { + unitselection = lib.mkOption { + default = "metric"; + description = '' + unitselection allows choice of metric + ''; + type = lib.types.str; + }; + switch_miles_feet = lib.mkOption { + default = 400; + description = '' + This is the threshold for switching from miles to feet, set in metres. + ''; + type = lib.types.int; + }; + }; + + history = { + reload_last_position = lib.mkOption { + default = false; + description = '' + Enable load of last position from log files + ''; + type = lib.types.bool; + }; + }; + }; + + openPorts = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open firewall ports for chasemapper"; + }; + + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.chasemapper = { + serviceConfig = { + Type = "simple"; + Restart = "always"; + + DynamicUser = true; + ExecStart = "${lib.getExe cfg.package} --nolog -c ${chaseCfg}"; + }; + + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + description = "Chasemapper"; + wantedBy = [ "multi-user.target" ]; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openPorts [ + cfg.settings.map.flask_port + ]; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 824ae2f0a46f5..4fac63c164a8b 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -297,6 +297,7 @@ in cfssl = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./cfssl.nix { }; cgit = runTest ./cgit.nix; charliecloud = handleTest ./charliecloud.nix { }; + chasemapper = runTest ./chasemapper.nix; chromadb = runTest ./chromadb.nix; chromium = (handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./chromium.nix { }).stable or { }; chrony = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./chrony.nix { }; diff --git a/nixos/tests/chasemapper.nix b/nixos/tests/chasemapper.nix new file mode 100644 index 0000000000000..0e753503adc47 --- /dev/null +++ b/nixos/tests/chasemapper.nix @@ -0,0 +1,19 @@ +{ lib, ... }: + +{ + name = "chasemapper"; + meta.maintainers = with lib.maintainers; [ scd31 ]; + + nodes.machine = + { ... }: + { + services.chasemapper.enable = true; + }; + + testScript = '' + machine.wait_for_unit("chasemapper.service") + machine.wait_for_open_port(5001) + machine.succeed("curl --fail http://localhost:5001") + ''; + +} diff --git a/pkgs/by-name/ch/chasemapper/package.nix b/pkgs/by-name/ch/chasemapper/package.nix new file mode 100644 index 0000000000000..d9511aa86c248 --- /dev/null +++ b/pkgs/by-name/ch/chasemapper/package.nix @@ -0,0 +1,64 @@ +{ + lib, + stdenv, + fetchFromGitHub, + python3, + bash, +}: +let + customPython = python3.withPackages ( + ps: with ps; [ + eccodes + cusfpredict + flask + flask-socketio + lxml + numpy + python-dateutil + pytz + requests + pyserial + simple-websocket + ] + ); +in +stdenv.mkDerivation (finalAttrs: { + pname = "chasemapper"; + version = "1.5.3"; + + src = fetchFromGitHub { + owner = "projecthorus"; + repo = "chasemapper"; + tag = "v${finalAttrs.version}"; + hash = "sha256-KamXFMwozCQnH4zQRqsxq28Jr1NEjDL/Jimy/XEP0Bs="; + }; + + postPatch = '' + substituteInPlace horusmapper.py \ + --replace-fail '#!/usr/bin/env python2.7' '#!${customPython}/bin/python3' + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin + cp -r chasemapper $out/ + cp -r static $out/ + cp -r templates $out/ + install horusmapper.py $out/ + echo "#!${bash}/bin/bash" > $out/bin/chasemapper + echo $out/horusmapper.py '"$@"' >> $out/bin/chasemapper + chmod +x $out/bin/chasemapper + + runHook postInstall + ''; + + meta = { + description = "Browser-based HAB chase map"; + homepage = "https://github.com/projecthorus/chasemapper"; + changelog = "https://github.com/projecthorus/chasemapper/releases/tag/v${finalAttrs.version}"; + license = lib.licenses.gpl3Plus; + mainProgram = "chasemapper"; + maintainers = with lib.maintainers; [ scd31 ]; + }; +}) diff --git a/pkgs/by-name/cu/cusf-predictor-wrapper/package.nix b/pkgs/by-name/cu/cusf-predictor-wrapper/package.nix new file mode 100644 index 0000000000000..8f0a9f4584d19 --- /dev/null +++ b/pkgs/by-name/cu/cusf-predictor-wrapper/package.nix @@ -0,0 +1,46 @@ +{ + lib, + stdenv, + fetchFromGitHub, + cmake, + pkg-config, + glib, + installShellFiles, +}: +stdenv.mkDerivation { + pname = "cusf_predictor_wrapper"; + version = "0-unstable-2025-03-04"; + + src = fetchFromGitHub { + owner = "darksidelemm"; + repo = "cusf_predictor_wrapper"; + rev = "f4352834a037e3e2bf01a3fd7d5a25aa482e27c6"; + hash = "sha256-C8/5x8tim6s0hWgCC7LpN1hesdVME5kpQFqDTEyXHtg="; + }; + + sourceRoot = "source/src"; + + nativeBuildInputs = [ + cmake + pkg-config + glib + installShellFiles + ]; + + installPhase = '' + runHook preInstall + + mv pred cusf_predictor_wrapper + installBin cusf_predictor_wrapper + + runHook postInstall + ''; + + meta = { + description = "CUSF standalone predictor"; + homepage = "https://github.com/darksidelemm/cusf_predictor_wrapper"; + license = lib.licenses.gpl3; + mainProgram = "cusf_predictor_wrapper"; + maintainers = with lib.maintainers; [ scd31 ]; + }; +} diff --git a/pkgs/development/python-modules/cusfpredict/default.nix b/pkgs/development/python-modules/cusfpredict/default.nix new file mode 100644 index 0000000000000..202be4d7371c2 --- /dev/null +++ b/pkgs/development/python-modules/cusfpredict/default.nix @@ -0,0 +1,38 @@ +{ + lib, + buildPythonPackage, + fetchFromGitHub, + deprecated, + hopcroftkarp, + joblib, + matplotlib, + numpy, + scikit-learn, + scipy, + pytestCheckHook, + pythonAtLeast, + pythonOlder, +}: +buildPythonPackage rec { + pname = "cusfpredict"; + version = "0.2.1"; + format = "setuptools"; + + src = fetchFromGitHub { + owner = "darksidelemm"; + repo = "cusf_predictor_wrapper"; + rev = "f4352834a037e3e2bf01a3fd7d5a25aa482e27c6"; + hash = "sha256-C8/5x8tim6s0hWgCC7LpN1hesdVME5kpQFqDTEyXHtg="; + }; + + doCheck = false; # No tests available + + pythonImportsCheck = [ "cusfpredict" ]; + + meta = { + description = "CUSF standalone predictor - Python wrapper"; + homepage = "https://github.com/darksidelemm/cusf_predictor_wrapper"; + license = lib.licenses.gpl3; + maintainers = with lib.maintainers; [ scd31 ]; + }; +} diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index 7258866282118..49a2efe02bd0c 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -3038,6 +3038,8 @@ self: super: with self; { curvefitgui = callPackage ../development/python-modules/curvefitgui { }; + cusfpredict = callPackage ../development/python-modules/cusfpredict { }; + customtkinter = callPackage ../development/python-modules/customtkinter { }; cvelib = callPackage ../development/python-modules/cvelib { };