Add client volume settings and harden screenshot publishing

- persist client config from MQTT for display manager volume control
- apply effective VLC volume from event volume and client multiplier
- support live runtime volume updates for active video playback
- make latest screenshot handoff atomic to avoid broken latest.jpg races
- ignore broken screenshot pointers when selecting fallback images
- fix screenshot size logging and document server-side volume control
This commit is contained in:
RobbStarkAustria
2026-03-22 12:41:13 +01:00
parent 80e5ce98a0
commit cfc1931975
4 changed files with 728 additions and 40 deletions

View File

@@ -143,6 +143,7 @@ logging.info(f"Monitoring logger initialized: {MONITORING_LOG_PATH}")
# Health state file (written by display_manager, read by simclient)
HEALTH_STATE_FILE = os.path.join(os.path.dirname(__file__), "current_process_health.json")
CLIENT_SETTINGS_FILE = os.path.join(os.path.dirname(__file__), "config", "client_settings.json")
discovered = False
@@ -509,8 +510,13 @@ def get_latest_screenshot():
logging.debug(f"Could not read latest.jpg, falling back to newest file: {e}")
# Find the most recent screenshot file
screenshot_files = [f for f in os.listdir(screenshot_dir)
if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
# Exclude 'latest.jpg' (it's just a pointer) and any broken symlinks
screenshot_files = [
f for f in os.listdir(screenshot_dir)
if f.lower().endswith(('.png', '.jpg', '.jpeg'))
and f != 'latest.jpg'
and os.path.exists(os.path.join(screenshot_dir, f))
]
if not screenshot_files:
return None
@@ -554,6 +560,27 @@ def read_health_state():
return None
def save_client_settings(settings_data):
"""Persist dashboard-managed client settings for the display manager."""
try:
os.makedirs(os.path.dirname(CLIENT_SETTINGS_FILE), exist_ok=True)
with open(CLIENT_SETTINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(settings_data, f, ensure_ascii=False, indent=2)
logging.info(f"Client settings saved to {CLIENT_SETTINGS_FILE}")
except Exception as e:
logging.error(f"Error saving client settings: {e}")
def delete_client_settings():
"""Delete persisted client settings so defaults apply again."""
try:
if os.path.exists(CLIENT_SETTINGS_FILE):
os.remove(CLIENT_SETTINGS_FILE)
logging.info(f"Client settings deleted: {CLIENT_SETTINGS_FILE}")
except Exception as e:
logging.error(f"Error deleting client settings: {e}")
def publish_health_message(client, client_id):
"""Publish health status to server via MQTT"""
try:
@@ -883,6 +910,28 @@ def main():
client.message_callback_add(group_id_topic, on_group_id_message)
logging.info(f"Current group_id at start: {current_group_id if current_group_id else 'none'}")
config_topic = f"infoscreen/{client_id}/config"
def on_config_message(client, userdata, msg, properties=None):
payload = msg.payload.decode().strip()
if not payload or payload.lower() in ("null", "none", "empty", "{}"):
logging.info("Empty client config received - deleting persisted client settings")
delete_client_settings()
return
try:
config_data = json.loads(payload)
except json.JSONDecodeError as e:
logging.error(f"Invalid JSON in client config message: {e}")
return
if not isinstance(config_data, dict):
logging.warning("Ignoring non-object client config payload")
return
save_client_settings(config_data)
client.message_callback_add(config_topic, on_config_message)
# Discovery-Phase: Sende Discovery bis ACK empfangen
# The loop is already started, just wait and send discovery messages
discovery_attempts = 0