# UGREEN DXP4800 LED control # Based on https://github.com/miskcoo/ugreen_leds_controller { config, lib, pkgs, ... }: let # Disk mapping: ata port -> LED name # DXP4800 has bays 1-4, currently bays 2 and 4 are populated diskMapping = { # ata-2 (sdb) -> disk2 "2" = "disk2"; # ata-4 (sdc) -> disk4 "4" = "disk4"; }; # LED colors (R G B) colors = { healthy = "0 255 0"; # Green activity = "0 255 0"; # Green blink standby = "0 0 255"; # Dim blue when sleeping fail = "255 0 0"; # Red network = "0 255 0"; # Green power = "255 255 255"; # White }; brightness = 255; standbyBrightness = 30; # Dim when in standby refreshInterval = "0.1"; # Seconds between activity checks powerCheckInterval = 30; # Seconds between power state checks # Script to initialize LEDs on boot initLedsScript = pkgs.writeShellScript "ugreen-leds-init" '' set -euo pipefail PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli ]}:$PATH" # Wait for i2c device to be available for i in $(seq 1 30); do if [ -e /dev/i2c-0 ]; then break fi sleep 1 done # Initialize power LED - solid white ugreen_leds_cli power -on -color ${colors.power} -brightness ${toString brightness} || true # Initialize network LED - will be controlled by netdevmon ugreen_leds_cli netdev -off || true # Initialize disk LEDs based on mapping ${lib.concatStringsSep "\n" (lib.mapAttrsToList (ata: led: '' ugreen_leds_cli ${led} -off || true '') diskMapping)} echo "UGREEN LEDs initialized" ''; # Disk activity monitoring script diskMonitorScript = pkgs.writeShellScript "ugreen-diskiomon" '' set -euo pipefail PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli pkgs.coreutils pkgs.gnugrep pkgs.gawk pkgs.smartmontools pkgs.hdparm ]}:$PATH" # Build device -> LED mapping by checking ata ports declare -A devices declare -A diskio_data declare -A disk_healthy declare -A disk_standby # Discover disks based on ata port mapping for path in /dev/disk/by-path/pci-*-ata-*; do [ -e "$path" ] || continue # Skip partitions [[ "$path" == *-part* ]] && continue # Extract ata port number (e.g., ata-2 -> 2) ata_port=$(echo "$path" | grep -oP 'ata-\K[0-9]+' | head -1) case "$ata_port" in ${lib.concatStringsSep "\n " (lib.mapAttrsToList (ata: led: '' ${ata}) device=$(readlink -f "$path") short_name=$(basename "$device") devices["${led}"]="$short_name" echo "Mapped $short_name (ata-${ata}) -> ${led}" ;;'') diskMapping)} esac done if [ ''${#devices[@]} -eq 0 ]; then echo "No disks found matching ATA ports, exiting" exit 1 fi # Set initial LED state for discovered disks for led in "''${!devices[@]}"; do device="''${devices[$led]}" # Check SMART health (this will wake the disk at boot, which is acceptable) if smartctl -H "/dev/$device" 2>/dev/null | grep -q "PASSED"; then disk_healthy["$led"]=1 ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true else disk_healthy["$led"]=0 ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true fi # Initialize tracking diskio_data["$led"]="" disk_standby["$led"]=0 done echo "Starting disk activity monitoring for ''${#devices[@]} disk(s)" # Function to update LED based on current state update_led() { local led="$1" local device="''${devices[$led]}" # Check power state without waking disk local power_state power_state=$(hdparm -C "/dev/$device" 2>/dev/null | grep -oP '(standby|active/idle|active|idle)' | head -1 || echo "unknown") if [[ "$power_state" == "standby" ]]; then if [[ "''${disk_standby[$led]}" != "1" ]]; then # Disk just went to standby - dim the LED disk_standby["$led"]=1 ugreen_leds_cli "$led" -on -color ${colors.standby} -brightness ${toString standbyBrightness} || true echo "Disk $device entered standby, dimming LED" fi else if [[ "''${disk_standby[$led]}" == "1" ]]; then # Disk woke up - restore health-based color disk_standby["$led"]=0 if [[ "''${disk_healthy[$led]}" == "1" ]]; then ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true else ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true fi echo "Disk $device woke up, restoring LED" fi fi } # Background power state checker check_power_states() { while true; do sleep ${toString powerCheckInterval} for led in "''${!devices[@]}"; do update_led "$led" done done } # Start power state checker in background check_power_states & POWER_CHECK_PID=$! trap "kill $POWER_CHECK_PID 2>/dev/null || true" EXIT # Main activity monitoring loop while true; do for led in "''${!devices[@]}"; do device="''${devices[$led]}" stat_file="/sys/block/$device/stat" if [ -f "$stat_file" ]; then new_stat=$(cat "$stat_file" 2>/dev/null || echo "") if [ -n "$new_stat" ] && [ "''${diskio_data[$led]}" != "$new_stat" ]; then # Activity detected - disk must be awake now if [[ "''${disk_standby[$led]}" == "1" ]]; then disk_standby["$led"]=0 if [[ "''${disk_healthy[$led]}" == "1" ]]; then ugreen_leds_cli "$led" -on -color ${colors.healthy} -brightness ${toString brightness} || true else ugreen_leds_cli "$led" -on -color ${colors.fail} -brightness ${toString brightness} || true fi fi # Trigger LED blink for activity if [ -e "/sys/class/leds/$led/shot" ]; then echo 1 > "/sys/class/leds/$led/shot" 2>/dev/null || true else ugreen_leds_cli "$led" -blink 100 100 2>/dev/null || true sleep 0.05 ugreen_leds_cli "$led" -on 2>/dev/null || true fi fi diskio_data["$led"]="$new_stat" fi done sleep ${refreshInterval} done ''; # Network activity monitoring script netMonitorScript = pkgs.writeShellScript "ugreen-netdevmon" '' set -euo pipefail PATH="${lib.makeBinPath [ pkgs.ugreen-leds-cli pkgs.coreutils pkgs.iproute2 ]}:$PATH" INTERFACE="$1" CHECK_INTERVAL=60 echo "Starting network monitoring on $INTERFACE" # Configure LED to trigger on network activity led_path="/sys/class/leds/netdev" while true; do # Check if interface is up if ip link show "$INTERFACE" 2>/dev/null | grep -q "state UP"; then # Link is up - set green ugreen_leds_cli netdev -on -color ${colors.network} -brightness ${toString brightness} || true # Try to enable hardware trigger for activity indication if [ -e "$led_path/device_name" ]; then echo "$INTERFACE" > "$led_path/device_name" 2>/dev/null || true fi if [ -e "$led_path/rx" ]; then echo 1 > "$led_path/rx" 2>/dev/null || true fi if [ -e "$led_path/tx" ]; then echo 1 > "$led_path/tx" 2>/dev/null || true fi else # Link is down - turn off ugreen_leds_cli netdev -off || true fi sleep $CHECK_INTERVAL done ''; in { # Load i2c-dev kernel module for LED controller communication boot.kernelModules = [ "i2c-dev" ]; # Install CLI tool environment.systemPackages = [ pkgs.ugreen-leds-cli ]; # LED initialization service - runs once at boot systemd.services.ugreen-leds-init = { description = "Initialize UGREEN NAS LEDs"; wantedBy = [ "multi-user.target" ]; after = [ "local-fs.target" "systemd-modules-load.service" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = "${initLedsScript}"; }; }; # Disk activity monitoring service systemd.services.ugreen-diskiomon = { description = "UGREEN disk activity LED monitor"; wantedBy = [ "multi-user.target" ]; after = [ "ugreen-leds-init.service" "local-fs.target" ]; requires = [ "ugreen-leds-init.service" ]; serviceConfig = { Type = "simple"; ExecStart = "${diskMonitorScript}"; Restart = "always"; RestartSec = "5s"; }; }; # Network activity monitoring service (template for interface) systemd.services."ugreen-netdevmon@" = { description = "UGREEN network LED monitor for %i"; after = [ "ugreen-leds-init.service" "network-online.target" ]; requires = [ "ugreen-leds-init.service" ]; serviceConfig = { Type = "simple"; ExecStart = "${netMonitorScript} %i"; Restart = "always"; RestartSec = "10s"; }; }; # Enable network monitoring for primary interface systemd.services."ugreen-netdevmon@enp2s0" = { wantedBy = [ "multi-user.target" ]; }; }