config.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. """Configuration loader for SpoolBuddy daemon.
  2. All configuration is via environment variables. The systemd service file
  3. or a shell wrapper sets these before launching the daemon.
  4. Required:
  5. SPOOLBUDDY_BACKEND_URL — Bambuddy server URL (e.g. http://192.168.1.100:5000)
  6. SPOOLBUDDY_API_KEY — API key created in Bambuddy Settings → API Keys
  7. Optional:
  8. SPOOLBUDDY_DEVICE_ID — Unique device identifier (default: derived from MAC)
  9. SPOOLBUDDY_HOSTNAME — Display name (default: system hostname)
  10. """
  11. import os
  12. import socket
  13. from dataclasses import dataclass
  14. from pathlib import Path
  15. @dataclass
  16. class Config:
  17. backend_url: str = ""
  18. api_key: str = ""
  19. device_id: str = ""
  20. hostname: str = ""
  21. nfc_poll_interval: float = 0.3
  22. scale_read_interval: float = 0.1
  23. scale_report_interval: float = 1.0
  24. heartbeat_interval: float = 10.0
  25. stability_threshold: float = 2.0
  26. stability_window: float = 1.0
  27. tare_offset: int = 0
  28. calibration_factor: float = 1.0
  29. @classmethod
  30. def load(cls) -> "Config":
  31. cfg = cls()
  32. cfg.backend_url = os.environ.get("SPOOLBUDDY_BACKEND_URL", "")
  33. cfg.api_key = os.environ.get("SPOOLBUDDY_API_KEY", "")
  34. cfg.device_id = os.environ.get("SPOOLBUDDY_DEVICE_ID", "")
  35. cfg.hostname = os.environ.get("SPOOLBUDDY_HOSTNAME", "")
  36. if not cfg.backend_url:
  37. raise RuntimeError("SPOOLBUDDY_BACKEND_URL is required (e.g. http://192.168.1.100:5000)")
  38. if not cfg.api_key:
  39. raise RuntimeError("SPOOLBUDDY_API_KEY is required (create one in Bambuddy Settings → API Keys)")
  40. # Default device_id from MAC address
  41. if not cfg.device_id:
  42. cfg.device_id = _get_mac_id()
  43. # Default hostname from system
  44. if not cfg.hostname:
  45. cfg.hostname = socket.gethostname()
  46. return cfg
  47. def _get_mac_id() -> str:
  48. """Generate a stable device ID from the primary network interface MAC address.
  49. Interfaces are sorted by name so the same interface is always picked
  50. regardless of filesystem iteration order (eth0 before wlan0, etc.).
  51. """
  52. try:
  53. ifaces = sorted(Path("/sys/class/net").iterdir(), key=lambda p: p.name)
  54. for iface in ifaces:
  55. if iface.name == "lo":
  56. continue
  57. addr_file = iface / "address"
  58. if addr_file.exists():
  59. mac = addr_file.read_text().strip().replace(":", "")
  60. if mac and mac != "000000000000":
  61. return f"sb-{mac}"
  62. except Exception:
  63. pass
  64. import uuid
  65. return f"sb-{uuid.uuid4().hex[:12]}"