summaryrefslogtreecommitdiff
path: root/hosts
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 /hosts
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 moonraker
Diffstat (limited to 'hosts')
-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]