diff options
| -rw-r--r-- | hosts/gnuslashprinter/configuration.nix | 11 | ||||
| -rw-r--r-- | hosts/gnuslashprinter/flask-relay-ctl.nix | 37 | ||||
| -rw-r--r-- | hosts/gnuslashprinter/klipper.nix | 66 | ||||
| -rw-r--r-- | hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml | 10 | ||||
| -rw-r--r-- | hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py | 54 | ||||
| -rw-r--r-- | hosts/gnuslashprinter/resources/klipper/printer.cfg | 2 |
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] |
