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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user