Select the bmspy-ups device from the configured devices
bmspy-ups selected its device from the devices that had data at startup, and exited with status 0 if the selection failed. One failed first read ended the monitor or made it select the wrong device. The server has a new DEVICES command that lists the configured names, and client.list_devices calls it. bmspy-ups selects from that list and then waits for the first data of its device. A wrong or missing --device now exits with status 2. The service file does not restart on that status, because a restart cannot correct it. An older server ends on the unknown DEVICES command, so update the server first.
This commit is contained in:
@@ -125,6 +125,12 @@ def socket_comms(socket_path: str, request_data: dict[str, Any], debug: int = 0)
|
||||
return response_data
|
||||
|
||||
|
||||
def list_devices(socket_path: str, client_name: str, debug: int = 0) -> list[str]:
|
||||
"""Return the names of all configured devices, including those without data."""
|
||||
data = socket_comms(socket_path, {"command": "DEVICES", "client": client_name}, debug)
|
||||
return list(data["devices"])
|
||||
|
||||
|
||||
def read_data(socket_path: str, client_name: str, ups: str | None = None, debug: int = 0) -> dict[str, UPS]:
|
||||
"""Return {ups_name: UPS} for all devices, or just the named one."""
|
||||
request: dict[str, Any] = {"command": "GET", "client": client_name}
|
||||
|
||||
@@ -433,6 +433,10 @@ def main():
|
||||
debug,
|
||||
)
|
||||
|
||||
case "DEVICES":
|
||||
# Configured names, including devices that have no data yet.
|
||||
send_response(connection, {"devices": list(ups_devices)}, client, debug)
|
||||
|
||||
case "GET":
|
||||
ups_filter = request_data.get("ups")
|
||||
targets = (
|
||||
|
||||
+30
-4
@@ -1,16 +1,20 @@
|
||||
import argparse
|
||||
import atexit
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import smtplib
|
||||
import ssl
|
||||
import socket
|
||||
from collections import deque
|
||||
from collections.abc import Collection
|
||||
|
||||
from bmspy import client
|
||||
from bmspy.classes import UPS, BMSScalarField
|
||||
|
||||
DAEMON_UPDATE_PERIOD = 30
|
||||
STARTUP_RETRY_PERIOD = 5
|
||||
USAGE_ERROR_STATUS = 2
|
||||
|
||||
scheduled_shutdown = False
|
||||
critical_sent = False
|
||||
@@ -26,9 +30,11 @@ def _get_field_value(device: UPS, field_name: str) -> float | None:
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_ups_device(data: dict[str, UPS], requested: str | None) -> str | None:
|
||||
def _resolve_ups_device(data: Collection[str], requested: str | None) -> str | None:
|
||||
"""Return the device name to monitor, or None and print an error if the selection is invalid.
|
||||
|
||||
*data* holds the available device names. A dict keyed by name also works.
|
||||
|
||||
Rules:
|
||||
- No devices → error.
|
||||
- requested is set and found → use it.
|
||||
@@ -58,6 +64,25 @@ def _resolve_ups_device(data: dict[str, UPS], requested: str | None) -> str | No
|
||||
return next(iter(data))
|
||||
|
||||
|
||||
def _wait_for_ups_device(socket_path: str, requested: str | None) -> str | None:
|
||||
"""Select the device to monitor, then wait until the server has data for it.
|
||||
|
||||
The selection uses the configured device names, not the devices that have
|
||||
data now. A device that misses its first read at startup then cannot change
|
||||
the selection. A wrong or missing choice is final: it returns None.
|
||||
"""
|
||||
name = _resolve_ups_device(client.list_devices(socket_path, "ups"), requested)
|
||||
if name is None:
|
||||
return None
|
||||
waiting = False
|
||||
while name not in client.read_data(socket_path, "ups", ups=name):
|
||||
if not waiting:
|
||||
print("UPS '{}' has no data yet, waiting...".format(name))
|
||||
waiting = True
|
||||
time.sleep(STARTUP_RETRY_PERIOD)
|
||||
return name
|
||||
|
||||
|
||||
def handle_shutdown(action: str = "cancel", delay: int = 0, debug: int = 0) -> None:
|
||||
"""Schedule or cancel a system shutdown; only schedules once per incident."""
|
||||
global scheduled_shutdown
|
||||
@@ -224,10 +249,11 @@ def main() -> None:
|
||||
client.handle_registration(args.socket, "ups", debug)
|
||||
atexit.register(client.handle_registration, args.socket, "ups", debug)
|
||||
|
||||
initial_data = client.read_data(args.socket, "ups")
|
||||
device_name = _resolve_ups_device(initial_data, args.device)
|
||||
device_name = _wait_for_ups_device(args.socket, args.device)
|
||||
if device_name is None:
|
||||
return
|
||||
# A wrong or missing --device is a usage error, as in argparse. A retry
|
||||
# cannot fix it, so the service file does not restart on this status.
|
||||
sys.exit(USAGE_ERROR_STATUS)
|
||||
|
||||
history = deque()
|
||||
while True:
|
||||
|
||||
Reference in New Issue
Block a user