{ config, lib, pkgs, options, ... }: let cfg = config.services.prometheus.exporters.rtl_433; in { port = 9550; extraOpts = let mkMatcherOptionType = field: description: with lib.types; listOf (submodule { options = { name = lib.mkOption { type = str; description = lib.mdDoc "Name to match."; }; "${field}" = lib.mkOption { type = int; description = lib.mdDoc description; }; location = lib.mkOption { type = str; description = lib.mdDoc "Location to match."; }; }; }); in { rtl433Flags = lib.mkOption { type = lib.types.str; default = "-C si"; example = "-C si -R 19"; description = lib.mdDoc '' Flags passed verbatim to rtl_433 binary. Having `-C si` (the default) is recommended since only Celsius temperatures are parsed. ''; }; channels = lib.mkOption { type = mkMatcherOptionType "channel" "Channel to match."; default = []; example = [ { name = "Acurite"; channel = 6543; location = "Kitchen"; } ]; description = lib.mdDoc '' List of channel matchers to export. ''; }; ids = lib.mkOption { type = mkMatcherOptionType "id" "ID to match."; default = []; example = [ { name = "Nexus"; id = 1; location = "Bedroom"; } ]; description = lib.mdDoc '' List of ID matchers to export. ''; }; }; serviceOpts = { serviceConfig = { # rtl-sdr udev rules make supported USB devices +rw by plugdev. SupplementaryGroups = "plugdev"; # rtl_433 needs rw access to the USB radio. PrivateDevices = lib.mkForce false; DeviceAllow = lib.mkForce "char-usb_device rw"; RestrictAddressFamilies = [ "AF_NETLINK" ]; ExecStart = let matchers = (map (m: "--channel_matcher '${m.name},${toString m.channel},${m.location}'" ) cfg.channels) ++ (map (m: "--id_matcher '${m.name},${toString m.id},${m.location}'" ) cfg.ids); in '' ${pkgs.prometheus-rtl_433-exporter}/bin/rtl_433_prometheus \ -listen ${cfg.listenAddress}:${toString cfg.port} \ -subprocess "${pkgs.rtl_433}/bin/rtl_433 -F json ${cfg.rtl433Flags}" \ ${lib.concatStringsSep " \\\n " matchers} \ ${lib.concatStringsSep " \\\n " cfg.extraFlags} ''; }; }; }