summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Keller <tjk@tjkeller.xyz>2026-06-03 13:54:18 -0500
committerTim Keller <tjk@tjkeller.xyz>2026-06-03 13:54:18 -0500
commit8264c4659dbdb8d98310414badf288f8200fe242 (patch)
treed4e967cd63990a464e36b7754676a58379e0f871
parenta6d9a9de1defe2c39c9514c0de76d37dcf8a0576 (diff)
downloadnixos-8264c4659dbdb8d98310414badf288f8200fe242.tar.xz
nixos-8264c4659dbdb8d98310414badf288f8200fe242.zip
aliases for printer and new power control relay. moonraker power control for printer enabled and accessible via mainsail. power control python script for power control via http in moonrakerHEADmaster
-rw-r--r--hosts/gnuslashprinter/configuration.nix11
-rw-r--r--hosts/gnuslashprinter/flask-relay-ctl.nix37
-rw-r--r--hosts/gnuslashprinter/klipper.nix66
-rw-r--r--hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml10
-rw-r--r--hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py54
-rw-r--r--hosts/gnuslashprinter/resources/klipper/printer.cfg2
6 files changed, 159 insertions, 21 deletions
diff --git a/hosts/gnuslashprinter/configuration.nix b/hosts/gnuslashprinter/configuration.nix
index 6af701a..9e1f734 100644
--- a/hosts/gnuslashprinter/configuration.nix
+++ b/hosts/gnuslashprinter/configuration.nix
@@ -1,5 +1,8 @@
{
- imports = [ ./klipper.nix ];
+ imports = [
+ ./flask-relay-ctl.nix
+ ./klipper.nix
+ ];
boot._loader.enable = true;
@@ -16,5 +19,11 @@
# Enable user timmy
_users.timmy.enable = true;
+ # Name devices
+ services.udev.extraRules = ''
+ SUBSYSTEM=="tty", KERNELS=="3-9", SYMLINK+="gsp-power"
+ SUBSYSTEM=="tty", KERNELS=="3-10", SYMLINK+="gsp-control"
+ '';
+
system.stateVersion = "25.11";
}
diff --git a/hosts/gnuslashprinter/flask-relay-ctl.nix b/hosts/gnuslashprinter/flask-relay-ctl.nix
new file mode 100644
index 0000000..27507b6
--- /dev/null
+++ b/hosts/gnuslashprinter/flask-relay-ctl.nix
@@ -0,0 +1,37 @@
+{ pkgs, ... }: let
+ flaskrelayctl = pkgs.python3Packages.buildPythonApplication {
+ pname = "flaskrelayctl";
+ version = "1.0";
+ src = ./resources/flaskrelayctl;
+ pyproject = true;
+ build-system = with pkgs.python3Packages; [ setuptools ];
+ dependencies = with pkgs.python3Packages; [
+ flask
+ pyserial
+ ];
+ };
+in {
+ users.users.relay-api = {
+ isSystemUser = true;
+ group = "relay-api";
+ extraGroups = [ "dialout" ];
+ };
+ users.groups.relay-api = {};
+
+ systemd.services.relay-api = let
+ RELAYCTL_DEV = "/dev/gsp-power";
+ in {
+ description = "USB Relay Flask API";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ User = "relay-api";
+ Group = "relay-api";
+ ExecStart = "${flaskrelayctl}/bin/flaskrelayctl";
+ Restart = "always";
+ DeviceAllow = [ "${RELAYCTL_DEV} rw" ];
+ };
+ };
+
+ environment.systemPackages = [ flaskrelayctl ];
+}
diff --git a/hosts/gnuslashprinter/klipper.nix b/hosts/gnuslashprinter/klipper.nix
index a19261c..331a0c7 100644
--- a/hosts/gnuslashprinter/klipper.nix
+++ b/hosts/gnuslashprinter/klipper.nix
@@ -6,7 +6,7 @@
mcu = {
enable = true;
# Serial port connected to the microcontroller
- serial = "/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0";
+ serial = "/dev/gsp-control";
# Klipper flash must be enabled in order to build mcu firmware
# The resulting `klipper-flash-mcu` command will show the location of the firmware bin in the nix store
enableKlipperFlash = true;
@@ -22,29 +22,31 @@
#configDir = "/var/lib/moonraker/config"; # Accessible by moonraker # TODO
# Moonraker web-api
+ security.polkit.enable = true; # required for services.moonraker.allowSystemControl
services.moonraker = {
user = "root";
enable = true;
address = "0.0.0.0";
+ allowSystemControl = true;
settings = {
- #authorization = {
- # force_logins = true;
- # cors_domains = [
- # "*.local"
- # "*.lan"
- # "*://app.fluidd.xyz"
- # "*://my.mainsail.xyz"
- # ];
- # trusted_clients = [
- # "10.0.0.0/8"
- # "127.0.0.0/8"
- # "169.254.0.0/16"
- # "172.16.0.0/12"
- # "192.168.0.0/16"
- # "FE80::/10"
- # "::1/128"
- # ];
- #};
+ authorization = {
+ force_logins = true;
+ cors_domains = [
+ "*.local"
+ "*.lan"
+ "*://app.fluidd.xyz"
+ "*://my.mainsail.xyz"
+ ];
+ trusted_clients = [
+ "10.0.0.0/8"
+ "127.0.0.0/8"
+ "169.254.0.0/16"
+ "172.16.0.0/12"
+ "192.168.0.0/16"
+ "FE80::/10"
+ "::1/128"
+ ];
+ };
#file_manager.check_klipper_config_path = false; # Disable warning when klipper config is not accessible by moonraker
# mainsail.cfg
#"update_manager mainsail-config" = {
@@ -54,6 +56,31 @@
# origin = "https://github.com/mainsail-crew/mainsail-config.git";
# managed_services = "klipper";
#};
+
+ "power printer" = let
+ relayApiHost = "http://localhost:5050";
+ in {
+ type = "http";
+ on_url = "${relayApiHost}/on";
+ off_url = "${relayApiHost}/off";
+ status_url = "${relayApiHost}/status";
+ response_template = ''
+
+ # The module will perform the "GET" request using the appropriate url.
+ # We use the `last_response` method to fetch the result and decode the
+ # json response.
+ {% set resp = http_request.last_response().json() %}
+ # The expression below will render "on" or "off".
+ {resp["state"].lower()}
+ '';
+ off_when_shutdown = true;
+ on_when_job_queued = true;
+ locked_while_printing = true;
+ restart_klipper_when_powered = true;
+ restart_delay = "1";
+ bound_services = "klipper";
+ initial_state = "off";
+ };
};
};
@@ -67,6 +94,7 @@
networking.firewall.allowedTCPPorts = [ 80 ]; # Port for mainsail via nginx
# Webcam support in mainsail
+ # TODO hook to restart ustreamer when webcam is connected
services.ustreamer.enable = true;
services.mainsail.nginx.locations."/webcam/".proxyPass = "http://localhost:8080/stream"; # Default location for ustreamer stream
}
diff --git a/hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml b/hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml
new file mode 100644
index 0000000..b151b74
--- /dev/null
+++ b/hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "flaskrelayctl"
+version = "1.0"
+
+[project.scripts]
+flaskrelayctl = "server:main"
diff --git a/hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py b/hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py
new file mode 100644
index 0000000..f2c0bc3
--- /dev/null
+++ b/hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py
@@ -0,0 +1,54 @@
+from flask import Flask, jsonify
+import serial
+import os
+
+app = Flask(__name__)
+
+RELAY_DEV = os.environ.get("RELAYCTL_DEV", "/dev/ttyUSB0")
+RELAY_HOST = os.environ.get("RELAYCTL_HOST", "127.0.0.1")
+RELAY_PORT = os.environ.get("RELAYCTL_PORT", "5050")
+RELAY_BAUD = 9600
+RELAY_ID = 1 # NOTE: multi relay boards are not supported (only relay id 1)
+
+def get_cmd(state: bool):
+ """
+ LCUS relay 4-byte control command syntax is as follows:
+ start: always 0xA0
+ relay: id of relay (counts from 1)
+ state: coil energized 0 or 1
+ checksum: sum of previous 3 bytes
+ """
+ start = 0xA0
+ relay = RELAY_ID
+ state = int(state)
+ checksum = start + relay + state
+ return bytes((start, relay, state, checksum))
+
+def get_serial():
+ return serial.Serial(RELAY_DEV, RELAY_BAUD, timeout=1)
+
+@app.route("/on", methods=["GET", "POST"])
+def on():
+ with get_serial() as ser:
+ ser.write(get_cmd(True))
+ return jsonify({"state": "ON"})
+
+@app.route("/off", methods=["GET", "POST"])
+def off():
+ with get_serial() as ser:
+ ser.write(get_cmd(False))
+ return jsonify({"state": "OFF"})
+
+@app.route("/status", methods=["GET"])
+def status():
+ with get_serial() as ser:
+ ser.write(bytes((0xFF,))) # write 0xFF to get status
+ r = ser.readline()
+ state = r.decode("utf-8").removeprefix("CH1: ").strip()
+ return jsonify({"state": state})
+
+def main():
+ app.run(host=RELAY_HOST, port=int(RELAY_PORT))
+
+if __name__ == "__main__":
+ main()
diff --git a/hosts/gnuslashprinter/resources/klipper/printer.cfg b/hosts/gnuslashprinter/resources/klipper/printer.cfg
index 1e39048..4baba56 100644
--- a/hosts/gnuslashprinter/resources/klipper/printer.cfg
+++ b/hosts/gnuslashprinter/resources/klipper/printer.cfg
@@ -90,7 +90,7 @@ max_temp: 130
pin: PA0
[mcu]
-serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
+serial: /dev/gsp-control
restart_method: command
[printer]