From 7fe6d48ff08eea5d8c1e2ce425c424ada819f7bb Mon Sep 17 00:00:00 2001 From: arcnmx Date: Sun, 14 Jul 2024 18:44:05 -0700 Subject: [PATCH] feat(octoprint): motion and notifs --- nixos/cameras/kitchen.nix | 34 ++++++ nixos/cameras/logistics-webcam.nix | 12 ++ nixos/cameras/printer.nix | 34 ++++++ nixos/{kitchencam.nix => motion.nix} | 25 ----- nixos/octoprint.nix | 105 ++++++++++++++++-- overlays/print.nix | 14 ++- packages/octoprint/octorant.nix | 6 +- .../prometheus-exporter-deregister.patch | 39 +++++++ packages/octoprint/prometheus-exporter.nix | 4 + packages/octoprint/queue.nix | 23 ++++ systems/logistics/nixos.nix | 14 +-- 11 files changed, 263 insertions(+), 47 deletions(-) create mode 100644 nixos/cameras/kitchen.nix create mode 100644 nixos/cameras/logistics-webcam.nix create mode 100644 nixos/cameras/printer.nix rename nixos/{kitchencam.nix => motion.nix} (58%) create mode 100644 packages/octoprint/prometheus-exporter-deregister.patch create mode 100644 packages/octoprint/queue.nix diff --git a/nixos/cameras/kitchen.nix b/nixos/cameras/kitchen.nix new file mode 100644 index 00000000..0d1776a3 --- /dev/null +++ b/nixos/cameras/kitchen.nix @@ -0,0 +1,34 @@ +{ + config, + gensokyo-zone, + lib, + ... +}: let + inherit (gensokyo-zone.lib) mapDefaults; + inherit (config.services) motion; +in { + services.motion.cameras.kitchencam.settings = mapDefaults { + videodevice = "/dev/kitchencam"; + v4l2_palette = 8; + width = 640; + height = 480; + framerate = 5; + camera_id = 1; + text_left = "kitchen"; + }; + services.udev.extraRules = let + inherit (lib.strings) concatStringsSep; + rules = [ + ''SUBSYSTEM=="video4linux"'' + ''ACTION=="add"'' + ''ATTR{index}=="0"'' + ''ATTRS{idProduct}=="2a25"'' + ''ATTRS{idVendor}=="1224"'' + ''SYMLINK+="kitchencam"'' + ''OWNER="${motion.user}"'' + ''TAG+="systemd"'' + ''ENV{SYSTEMD_WANTS}="motion.service"'' + ]; + rulesLine = concatStringsSep ", " rules; + in rulesLine; +} diff --git a/nixos/cameras/logistics-webcam.nix b/nixos/cameras/logistics-webcam.nix new file mode 100644 index 00000000..468d80c5 --- /dev/null +++ b/nixos/cameras/logistics-webcam.nix @@ -0,0 +1,12 @@ +{ + gensokyo-zone, + ... +}: let + inherit (gensokyo-zone.lib) mapDefaults; +in { + services.motion.cameras.webcam.settings = mapDefaults { + videodevice = "/dev/video0"; + camera_id = 3; + text_left = "logistics"; + }; +} diff --git a/nixos/cameras/printer.nix b/nixos/cameras/printer.nix new file mode 100644 index 00000000..24321729 --- /dev/null +++ b/nixos/cameras/printer.nix @@ -0,0 +1,34 @@ +{ + config, + gensokyo-zone, + lib, + ... +}: let + inherit (gensokyo-zone.lib) mapDefaults; + inherit (config.services) motion; +in { + services.motion.cameras.printercam.settings = mapDefaults { + videodevice = "/dev/printercam"; + width = 640; + height = 480; + framerate = 5; + camera_id = 2; + text_left = ""; + #text_right = ""; + }; + services.udev.extraRules = let + inherit (lib.strings) concatStringsSep; + rules = [ + ''SUBSYSTEM=="video4linux"'' + ''ACTION=="add"'' + ''ATTR{index}=="0"'' + ''ATTRS{idProduct}=="6366"'' + ''ATTRS{idVendor}=="0c45"'' + ''SYMLINK+="printercam"'' + ''OWNER="${motion.user}"'' + ''TAG+="systemd"'' + ''ENV{SYSTEMD_WANTS}="motion.service"'' + ]; + rulesLine = concatStringsSep ", " rules; + in rulesLine; +} diff --git a/nixos/kitchencam.nix b/nixos/motion.nix similarity index 58% rename from nixos/kitchencam.nix rename to nixos/motion.nix index b16793ef..8e9ee02a 100644 --- a/nixos/kitchencam.nix +++ b/nixos/motion.nix @@ -33,32 +33,7 @@ in { webcontrol_port = webPort; stream_port = streamPort; }; - cameras.kitchencam.settings = mapDefaults { - videodevice = "/dev/kitchencam"; - v4l2_palette = 8; - width = 640; - height = 480; - framerate = 5; - camera_id = 1; - text_left = "kitchen"; - }; }; - services.udev.extraRules = let - inherit (lib.strings) concatStringsSep; - rules = [ - ''SUBSYSTEM=="video4linux"'' - ''ACTION=="add"'' - ''ATTR{index}=="0"'' - ''ATTRS{idProduct}=="2a25"'' - ''ATTRS{idVendor}=="1224"'' - ''SYMLINK+="kitchencam"'' - ''OWNER="${cfg.user}"'' - ''TAG+="systemd"'' - ''ENV{SYSTEMD_WANTS}="motion.service"'' - ]; - rulesLine = concatStringsSep ", " rules; - in - mkIf cfg.enable rulesLine; networking.firewall.interfaces.local = mkIf cfg.enable { allowedTCPPorts = [cfg.settings.stream_port cfg.settings.webcontrol_port]; }; diff --git a/nixos/octoprint.nix b/nixos/octoprint.nix index a9753888..bf0694f6 100644 --- a/nixos/octoprint.nix +++ b/nixos/octoprint.nix @@ -1,14 +1,16 @@ { - pkgs, config, access, + gensokyo-zone, lib, ... }: let + inherit (gensokyo-zone.lib) domain; inherit (lib.modules) mkIf mkMerge mkDefault; inherit (config.services) motion; cfg = config.services.octoprint; - behindVouch = false; + vouchHeader = null; + #vouchHeader = "X-Vouch-User"; in { services.octoprint = { enable = mkDefault true; @@ -16,6 +18,7 @@ in { plugins = python3Packages: with python3Packages; [ prometheus-exporter octorant + queue abl-expert bedlevelvisualizer #displayprogress / displaylayerprogress? @@ -66,27 +69,113 @@ in { serial = { port = "/dev/ttyUSB0"; baudrate = 115200; - #autoconnect = true; + autoconnect = true; + }; + } + { + plugins.octorant = let + media = { + none = "none"; + webcam = "snapshot"; + #timelapse = ?; + }; + in { + _config_version = 2; + events = { + printer_state_error.media = media.none; + printer_state_operational = { + enabled = false; + media = media.none; + }; + printer_state_unknown.media = media.none; + printing_started = { + message = "New print started: **{name}**"; + }; + printing_cancelled = { + message = "Print cancelled after {time_formatted}"; + }; + printing_paused = { + message = "Print paused"; + media = media.none; + }; + printing_failed.message = "Print failed! :<"; + printing_progress.message = "Printed **{progress}%** with {remaining_formatted} remaining"; + printing_resumed = { + message = "Print resumed"; + media = media.none; + }; + shutdown = { + #enabled = false; + media = media.none; + }; + startup = { + #enabled = false; + media = media.none; + }; + timelapse_done = { + enabled = true; + # TODO: movie_basename needs uri encoding if it contains spaces .-. + message = "Timelapse for {gcode}: https://print.${domain}/downloads/timelapse/{movie_basename}"; + media = media.none; + }; + timelapse_failed.media = media.none; + transfer_done.media = media.none; + transfer_failed.media = media.none; + transfer_progress.media = media.none; + progress = { + #percentage_enabled = false; + percentage_step = "14"; + throttle_enabled = true; + time_enabled = true; + throttle_step = "540"; + time_step = "600"; + }; + }; + # TODO: url = "https://discord.com/api/webhooks/etc"; }; } (mkIf motion.enable { webcam = { - # TODO + bitrate = "6000k"; + ffmpegThreads = 2; + timelapse = { + fps = 25; + options.interval = 3; + postRoll = 0; + type = "timed"; + }; + }; + plugins = { + classicwebcam = let + inherit (motion.cameras) printercam; + inherit (printercam.settings) camera_id; + in { + _config_version = 1; + snapshot = "https://kitchen.local.${domain}/${toString camera_id}/current"; + stream = "https://kitchen.local.${domain}/${toString camera_id}/stream"; + streamRatio = "4:3"; + }; }; }) - (mkIf (!behindVouch) { + (mkIf (vouchHeader == null) { accessControl = { autologinLocal = true; + autologinHeadsupAcknowledged = true; #autologinAs = "guest"; autologinAs = "admin"; - localNetworks = access.cidrForNetwork.allLocal.all; + localNetworks = access.cidrForNetwork.allLocal.all + ++ [ + # vouch protects it from the outside world so... + "0.0.0.0/0" + "::/0" + ]; }; }) - (mkIf behindVouch { + (mkIf (vouchHeader != null) { accessControl = { trustRemoteUser = true; addRemoteUsers = true; - remoteUserHeader = "X-Vouch-User"; + remoteUserHeader = vouchHeader; }; }) ]; diff --git a/overlays/print.nix b/overlays/print.nix index 46f3f59f..a527ff8d 100644 --- a/overlays/print.nix +++ b/overlays/print.nix @@ -11,7 +11,7 @@ in { inherit (python3Packages) buildPlugin; }; packageOverrides = python3Packages: python3Packages'prev: lib.mapAttrs (mapPlugin python3Packages) { - inherit (final.octoprintPlugins) prometheus-exporter octorant; + inherit (final.octoprintPlugins) prometheus-exporter octorant queue printtimegenius; }; octoprint = prev.octoprint.override (old: { packageOverrides = lib.composeExtensions old.packageOverrides or (_: _: {}) packageOverrides; @@ -31,8 +31,20 @@ in { prometheus-exporter = callPackage ../packages/octoprint/prometheus-exporter.nix { }; octorant = callPackage ../packages/octoprint/octorant.nix { }; + queue = callPackage ../packages/octoprint/queue.nix { }; + printtimegenius = let + printtimegenius = { fetchFromGitHub, python3Packages, buildPlugin }: octoprintPlugins.printtimegenius.overrideAttrs (old: rec { + version = lib.warnIf (lib.versionAtLeast old.version "2.3.2") "printtimegenius updated upstream" "2.3.3"; + src = fetchFromGitHub { + inherit (old.src) owner repo; + rev = version; + sha256 = "sha256-hqm8RShCNpsVbrVXquat5VXqcVc7q5tn5+7Ipqmaw4U="; + }; + }); + in callPackage printtimegenius { }; }; + # XXX: build broken upstream ugh... curaengine = prev.curaengine.override { inherit (final.python311Packages) libarcus; }; diff --git a/packages/octoprint/octorant.nix b/packages/octoprint/octorant.nix index 6397b3aa..834c57fc 100644 --- a/packages/octoprint/octorant.nix +++ b/packages/octoprint/octorant.nix @@ -2,14 +2,14 @@ , python3Packages , fetchFromGitHub }: let + pname = "OctoPrint-Octorant"; version = "1.3.4"; in buildPlugin { - pname = "OctoPrint-Octorant"; - inherit version; + inherit pname version; src = fetchFromGitHub { owner = "bchanudet"; - repo = "OctoPrint-Octorant"; + repo = pname; rev = version; sha256 = "sha256-gP79zlJ8gdtpddXOJIMhouSbwXnrSf+c1bURkN/7jvw="; }; diff --git a/packages/octoprint/prometheus-exporter-deregister.patch b/packages/octoprint/prometheus-exporter-deregister.patch new file mode 100644 index 00000000..5f6609a4 --- /dev/null +++ b/packages/octoprint/prometheus-exporter-deregister.patch @@ -0,0 +1,39 @@ +diff --git a/octoprint_prometheus_exporter/__init__.py b/octoprint_prometheus_exporter/__init__.py +--- a/octoprint_prometheus_exporter/__init__.py ++++ b/octoprint_prometheus_exporter/__init__.py +@@ -53,15 +53,22 @@ class PrometheusExporterPlugin(octoprint.plugin.BlueprintPlugin, + self.print_completion_timer = None + + def print_deregister_callback(self, label): +- if label != '': ++ self.print_progress_label = '' ++ if label == '': ++ return ++ try: + self.metrics.print_progress.remove(label) + self.metrics.print_time_elapsed.remove(label) + self.metrics.print_time_est.remove(label) + self.metrics.print_time_left_est.remove(label) +- self.print_progress_label = '' ++ except Exception as err: ++ self._logger.warning(err) + + def slice_deregister_callback(self, label): +- self.metrics.slice_progress.remove(label) ++ try: ++ self.metrics.slice_progress.remove(label) ++ except Exception as err: ++ self._logger.warning(err) + + def print_complete(self): + self.metrics.printing_time_total.inc(time.time() - self.print_time_start) +@@ -74,7 +81,8 @@ class PrometheusExporterPlugin(octoprint.plugin.BlueprintPlugin, + + self.print_completion_timer = Timer(30, self.print_complete_callback) + self.print_completion_timer.start() +- Timer(30, lambda: self.print_deregister_callback(self.print_progress_label)).start() ++ print_progress_label = self.print_progress_label ++ Timer(30, lambda: self.print_deregister_callback(print_progress_label)).start() + + def deactivateMetricsIfOffline(self, payload): + if payload['state_id'] == 'OFFLINE': diff --git a/packages/octoprint/prometheus-exporter.nix b/packages/octoprint/prometheus-exporter.nix index 5fed1911..2b2c112d 100644 --- a/packages/octoprint/prometheus-exporter.nix +++ b/packages/octoprint/prometheus-exporter.nix @@ -18,6 +18,10 @@ in buildPlugin { prometheus-client ]; + patches = [ + ./prometheus-exporter-deregister.patch + ]; + meta = { homepage = "https://github.com/tg44/OctoPrint-Prometheus-Exporter"; }; diff --git a/packages/octoprint/queue.nix b/packages/octoprint/queue.nix new file mode 100644 index 00000000..4517293e --- /dev/null +++ b/packages/octoprint/queue.nix @@ -0,0 +1,23 @@ +{ buildPlugin +, python3Packages +, fetchFromGitHub +}: let + version = "2.0.0"; + pname = "OctoPrint-Queue"; +in buildPlugin { + inherit pname version; + + src = fetchFromGitHub { + owner = "chennes"; + repo = pname; + rev = "v${version}"; + sha256 = "sha256-uAG6GrUKXUdUTtzmjKWPiHxMa3ekvoLpSIvFMiJI+/8="; + }; + + propagatedBuildInputs = with python3Packages; [ + ]; + + meta = { + homepage = "https://github.com/chennes/OctoPrint-Queue"; + }; +} diff --git a/systems/logistics/nixos.nix b/systems/logistics/nixos.nix index 9070f6ee..1ded219c 100644 --- a/systems/logistics/nixos.nix +++ b/systems/logistics/nixos.nix @@ -13,7 +13,10 @@ in { nixos.sops nixos.base nixos.barcodebuddy-scanner - nixos.kitchencam + nixos.motion + nixos.cameras.kitchen + nixos.cameras.printer + nixos.cameras.logistics-webcam nixos.octoprint ./hardware-configuration.nix ]; @@ -49,15 +52,6 @@ in { #jack.enable = true; }; - services.motion.cameras.webcam = { - #enable = false; - settings = { - videodevice = "/dev/video0"; - camera_id = 2; - text_left = "logistics"; - }; - }; - environment.systemPackages = [ pkgs.cura-octoprint ]; users.users.logistics = {