Last active
May 1, 2026 14:36
-
-
Save pmenke-de/70b90f6363cfcd70dd32ce126e3b6eac to your computer and use it in GitHub Desktop.
Plexamp NUC LED status display
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Uses [intel-nuc-led](https://github.com/pmenke-de/intel-nuc-led) to control the LED ring light | |
| on a NUC, to reflect the current status of a runnung Plexamp instance as well as shairport-sync. | |
| -> For use with a NUC as headless Plexamp-player, controlled via another Plexamp instance on a smartphone. | |
| """ | |
| import nuc_wmi | |
| from nuc_wmi.set_led import set_led | |
| import xml.etree.ElementTree as ET | |
| import requests | |
| import time | |
| import dbus | |
| SHAIRPORT_BUS_NAME = "org.gnome.ShairportSync" | |
| SHAIRPORT_OBJECT_PATH = "/org/gnome/ShairportSync" | |
| SHAIRPORT_INTERFACE = "org.gnome.ShairportSync" | |
| DBUS_PROPERTIES_IFACE = "org.freedesktop.DBus.Properties" | |
| def is_airplay_active() -> bool: | |
| """ | |
| Equivalent to: | |
| dbus-send --print-reply --system --dest=org.gnome.ShairportSync \ | |
| /org/gnome/ShairportSync org.freedesktop.DBus.Properties.Get \ | |
| string:org.gnome.ShairportSync string:Active | |
| """ | |
| try: | |
| bus = dbus.SystemBus() | |
| proxy = bus.get_object(SHAIRPORT_BUS_NAME, SHAIRPORT_OBJECT_PATH) | |
| props = dbus.Interface(proxy, DBUS_PROPERTIES_IFACE) | |
| active = props.Get(SHAIRPORT_INTERFACE, "Active") | |
| return bool(active) | |
| except Exception: | |
| # If ShairportSync isn't running / D-Bus not available, treat as inactive | |
| return False | |
| def main(): | |
| last_change = time.time() | |
| last_state = "unknown" | |
| while True: | |
| loop_start = time.time() | |
| # Default: error/unknown | |
| color = "Red" | |
| freq = "0.25Hz fade" | |
| brightness = 20 | |
| # AirPlay overrides everything else | |
| if is_airplay_active(): | |
| state = "airplay" | |
| color = "Blue" | |
| freq = "Always on" | |
| else: | |
| url = "http://localhost:32500/player/timeline/poll?wait=1&includeMetadata=0&commandID=1" | |
| state = "unknown" | |
| try: | |
| res = requests.get(url) | |
| if res.ok: | |
| root = ET.fromstring(res.content) | |
| for type_tag in root.findall('Timeline'): | |
| item_type = type_tag.get('itemType') | |
| if item_type == "music": | |
| state = type_tag.get('state') | |
| match state: | |
| case "playing": | |
| color = "Green" | |
| freq = "Always on" | |
| case "paused": | |
| color = "Yellow" | |
| freq = "0.25Hz fade" | |
| case "stopped": | |
| color = "Yellow" | |
| freq = "Always on" | |
| case _: | |
| print("bad state: " + state) | |
| color = "Red" | |
| break | |
| else: | |
| state = "error" | |
| print("bad response: " + str(res.content)) | |
| except Exception as e: | |
| state = "error" | |
| print(e) | |
| pass # color is red | |
| if last_state != state: | |
| last_change = time.time() | |
| last_state = state | |
| elif time.time() - last_change > 30: | |
| # dim after 30s (including for AirPlay) | |
| brightness = 1 | |
| freq = "Always on" | |
| set_led( | |
| nuc_wmi.LED_TYPE["legacy"].index("S0 Ring LED"), | |
| brightness, | |
| nuc_wmi.LED_BLINK_FREQUENCY["legacy"].index(freq), | |
| nuc_wmi.LED_COLOR['legacy'][nuc_wmi.LED_COLOR_TYPE['legacy']["S0 Ring LED"]].index(color) | |
| ) | |
| if time.time() - loop_start < .5: | |
| time.sleep(0.5) | |
| if __name__ == "__main__": | |
| while True: | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment