diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 132416d865f4..dcd9bb8aff1d 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -459,6 +459,7 @@ ./services/hardware/udisks2.nix ./services/hardware/upower.nix ./services/hardware/usbmuxd.nix + ./services/hardware/usbrelayd.nix ./services/hardware/thermald.nix ./services/hardware/undervolt.nix ./services/hardware/vdr.nix diff --git a/nixos/modules/services/hardware/usbrelayd.nix b/nixos/modules/services/hardware/usbrelayd.nix new file mode 100644 index 000000000000..c0322e89e6b1 --- /dev/null +++ b/nixos/modules/services/hardware/usbrelayd.nix @@ -0,0 +1,44 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.usbrelayd; +in +{ + options.services.usbrelayd = with types; { + enable = mkEnableOption "USB Relay MQTT daemon"; + + broker = mkOption { + type = str; + description = "Hostname or IP address of your MQTT Broker."; + default = "127.0.0.1"; + example = [ + "mqtt" + "192.168.1.1" + ]; + }; + + clientName = mkOption { + type = str; + description = "Name, your client connects as."; + default = "MyUSBRelay"; + }; + }; + + config = mkIf cfg.enable { + + # TODO: Rename to .conf in upcomming release + environment.etc."usbrelayd.ini".text = '' + [MQTT] + BROKER = ${cfg.broker} + CLIENTNAME = ${cfg.clientName} + ''; + + services.udev.packages = [ pkgs.usbrelayd ]; + systemd.packages = [ pkgs.usbrelayd ]; + users.users.usbrelay = { + isSystemUser = true; + group = "usbrelay"; + }; + users.groups.usbrelay = { }; + }; +} diff --git a/pkgs/os-specific/linux/usbrelay/daemon.nix b/pkgs/os-specific/linux/usbrelay/daemon.nix new file mode 100644 index 000000000000..5f8d23e5201d --- /dev/null +++ b/pkgs/os-specific/linux/usbrelay/daemon.nix @@ -0,0 +1,36 @@ +{ stdenv, usbrelay, python3 }: +let + python = python3.withPackages (ps: with ps; [ usbrelay-py paho-mqtt ]); +in +# This is a separate derivation, not just an additional output of +# usbrelay, because otherwise, we have a cyclic dependency between +# usbrelay (default.nix) and the python module (python.nix). +stdenv.mkDerivation rec { + pname = "usbrelayd"; + + inherit (usbrelay) src version; + + postPatch = '' + substituteInPlace 'usbrelayd.service' \ + --replace '/usr/bin/python3' "${python}/bin/python3" \ + --replace '/usr/sbin/usbrelayd' "$out/bin/usbrelayd" + ''; + + buildInputs = [ python ]; + + dontBuild = true; + + installPhase = '' + runHook preInstall; + install -m 644 -D usbrelayd $out/bin/usbrelayd + install -m 644 -D usbrelayd.service $out/lib/systemd/system/usbrelayd.service + install -m 644 -D 50-usbrelay.rules $out/lib/udev/rules.d/50-usbrelay.rules + runHook postInstall + ''; + # TODO for later releases: install -D usbrelayd.conf $out/etc/usbrelayd.conf # include this as an example + + meta = { + description = "USB Relay MQTT service"; + inherit (usbrelay.meta) homepage license maintainers platforms; + }; +} diff --git a/pkgs/os-specific/linux/usbrelay/default.nix b/pkgs/os-specific/linux/usbrelay/default.nix new file mode 100644 index 000000000000..ebbb1dd79228 --- /dev/null +++ b/pkgs/os-specific/linux/usbrelay/default.nix @@ -0,0 +1,29 @@ +{ stdenv, lib, fetchFromGitHub, hidapi }: +stdenv.mkDerivation rec { + pname = "usbrelay"; + version = "0.9"; + + src = fetchFromGitHub { + owner = "darrylb123"; + repo = "usbrelay"; + rev = version; + sha256 = "sha256-bxME4r5W5bZKxMZ/Svi1EenqHKVWIjU6iiKaM8U6lmA="; + }; + + buildInputs = [ + hidapi + ]; + + makeFlags = [ + "DIR_VERSION=${version}" + "PREFIX=${placeholder "out"}" + ]; + + meta = with lib; { + description = "Tool to control USB HID relays"; + homepage = "https://github.com/darrylb123/usbrelay"; + license = licenses.gpl2Plus; + maintainers = with maintainers; [ wentasah ]; + platforms = platforms.linux; + }; +} diff --git a/pkgs/os-specific/linux/usbrelay/python.nix b/pkgs/os-specific/linux/usbrelay/python.nix new file mode 100644 index 000000000000..02d5ac284eda --- /dev/null +++ b/pkgs/os-specific/linux/usbrelay/python.nix @@ -0,0 +1,12 @@ +{ buildPythonPackage, usbrelay }: + +buildPythonPackage rec { + pname = "usbrelay_py"; + inherit (usbrelay) version src; + + buildInputs = [ usbrelay ]; + + pythonImportsCheck = [ "usbrelay_py" ]; + + inherit (usbrelay) meta; +} diff --git a/pkgs/os-specific/linux/usbrelay/test.nix b/pkgs/os-specific/linux/usbrelay/test.nix new file mode 100644 index 000000000000..dc5847558a69 --- /dev/null +++ b/pkgs/os-specific/linux/usbrelay/test.nix @@ -0,0 +1,63 @@ +# NixOS test for usbrelayd +# +# It is not stored in nixos/tests directory, because it requires the +# USB relay connected to the host computer and as such, it cannot be +# run automatically. +# +# Run this test as: +# +# nix-build test.nix -A driverInteractive && ./result/bin/nixos-test-driver --no-interactive +# +# The interactive driver is required because the default +# (non-interactive) driver uses qemu without support for passing USB +# devices to the guest (see +# https://discourse.nixos.org/t/hardware-dependent-nixos-tests/18564 +# for discussion of other alternatives). + +import ../../../../nixos/tests/make-test-python.nix ({ pkgs, ... }: { + name = "usbrelayd"; + + nodes.machine = { + virtualisation.qemu.options = [ + "-device qemu-xhci" + "-device usb-host,vendorid=0x16c0,productid=0x05df" + ]; + services.usbrelayd.enable = true; + systemd.services.usbrelayd = { + after = [ "mosquitto.service" ]; + }; + services.mosquitto = { + enable = true; + listeners = [{ + acl = [ "pattern readwrite #" ]; + omitPasswordAuth = true; + settings.allow_anonymous = true; + }]; + }; + environment.systemPackages = [ + pkgs.usbrelay + pkgs.mosquitto + ]; + documentation.nixos.enable = false; # building nixos manual takes long time + }; + + testScript = '' + if os.waitstatus_to_exitcode(os.system("lsusb -d 16c0:05df")) != 0: + print("No USB relay detected, skipping test") + import sys + sys.exit(2) + machine.start() + # usbrelayd is started by udev when an relay is detected + machine.wait_for_unit("usbrelayd.service") + + stdout = machine.succeed("usbrelay") + relay_id = stdout.split(sep="_")[0] + assert relay_id != "" + import time + time.sleep(1) + machine.succeed(f"mosquitto_pub -h localhost -t cmnd/{relay_id}/1 -m ON") + time.sleep(1) + machine.succeed(f"mosquitto_pub -h localhost -t cmnd/{relay_id}/1 -m OFF") + print("Did you see the relay switching on and off?") + ''; +}) diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 42b1424f1d8b..cfd080c0da6e 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -23802,6 +23802,9 @@ with pkgs; libgcrypt = null; }; + usbrelay = callPackage ../os-specific/linux/usbrelay { }; + usbrelayd = callPackage ../os-specific/linux/usbrelay/daemon.nix { }; + usbtop = callPackage ../os-specific/linux/usbtop { }; usbutils = callPackage ../os-specific/linux/usbutils { }; diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index 5ec75b92d0d3..a07866054b74 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -10571,6 +10571,8 @@ in { urwid-readline = callPackage ../development/python-modules/urwid-readline { }; + usbrelay-py = callPackage ../os-specific/linux/usbrelay/python.nix { }; + usbtmc = callPackage ../development/python-modules/usbtmc { }; us = callPackage ../development/python-modules/us { };