From 27b4711f7001590f90898139a52cd6f32b628f8b Mon Sep 17 00:00:00 2001 From: William Henderson Date: Tue, 31 Mar 2026 11:13:03 +0100 Subject: [PATCH] V0.3.1 --- ...7b22148-b31f-4e1b-822b-a0289f4e27d6.vsidx} | Bin 26464 -> 28270 bytes ZCAM Manager/.vs/ZCAM Manager.slnx/v18/.suo | Bin 32768 -> 35840 bytes .../v18/DocumentLayout.backup.json | 2 +- .../ZCAM Manager.slnx/v18/DocumentLayout.json | 2 +- ZCAM Manager/ZCAM Manager.slnx | 2 +- ZCAM Manager/ZCAM Manager/ZCAM_Manager.py | 82 ++++++++++++++++-- 6 files changed, 79 insertions(+), 9 deletions(-) rename ZCAM Manager/.vs/ZCAM Manager.slnx/FileContentIndex/{fbc00939-47cb-4600-9b3c-24cca1a1fb51.vsidx => 17b22148-b31f-4e1b-822b-a0289f4e27d6.vsidx} (52%) diff --git a/ZCAM Manager/.vs/ZCAM Manager.slnx/FileContentIndex/fbc00939-47cb-4600-9b3c-24cca1a1fb51.vsidx b/ZCAM Manager/.vs/ZCAM Manager.slnx/FileContentIndex/17b22148-b31f-4e1b-822b-a0289f4e27d6.vsidx similarity index 52% rename from ZCAM Manager/.vs/ZCAM Manager.slnx/FileContentIndex/fbc00939-47cb-4600-9b3c-24cca1a1fb51.vsidx rename to ZCAM Manager/.vs/ZCAM Manager.slnx/FileContentIndex/17b22148-b31f-4e1b-822b-a0289f4e27d6.vsidx index 7e1534ea38cbf2e240513bfefbfa8a11a70304c4..b03ec0f714e16ac7b1dc393874b637be1f8a91d9 100644 GIT binary patch delta 8280 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 delta 6396 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 I*uQw!?|BWabN~PV diff --git a/ZCAM Manager/.vs/ZCAM Manager.slnx/v18/DocumentLayout.backup.json b/ZCAM Manager/.vs/ZCAM Manager.slnx/v18/DocumentLayout.backup.json index 3e890a7..1f0cfdb 100644 --- a/ZCAM Manager/.vs/ZCAM Manager.slnx/v18/DocumentLayout.backup.json +++ b/ZCAM Manager/.vs/ZCAM Manager.slnx/v18/DocumentLayout.backup.json @@ -45,7 +45,7 @@ "RelativeDocumentMoniker": "ZCAM Manager\\ZCAM_Manager.py", "ToolTip": "C:\\Users\\william\\Documents\\Git\\ZCAM-Manager\\ZCAM Manager\\ZCAM Manager\\ZCAM_Manager.py", "RelativeToolTip": "ZCAM Manager\\ZCAM_Manager.py", - "ViewState": "AgIAAAwAAAAAAAAAAAAAADQAAAAyAAAAAAAAAA==", + "ViewState": "AgIAABcAAAAAAAAAAAAgwDQAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", "WhenOpened": "2026-03-30T10:08:49.086Z", "EditorCaption": "" diff --git a/ZCAM Manager/.vs/ZCAM Manager.slnx/v18/DocumentLayout.json b/ZCAM Manager/.vs/ZCAM Manager.slnx/v18/DocumentLayout.json index dad1a88..b95c775 100644 --- a/ZCAM Manager/.vs/ZCAM Manager.slnx/v18/DocumentLayout.json +++ b/ZCAM Manager/.vs/ZCAM Manager.slnx/v18/DocumentLayout.json @@ -45,7 +45,7 @@ "RelativeDocumentMoniker": "ZCAM Manager\\ZCAM_Manager.py", "ToolTip": "C:\\Users\\william\\Documents\\Git\\ZCAM-Manager\\ZCAM Manager\\ZCAM Manager\\ZCAM_Manager.py", "RelativeToolTip": "ZCAM Manager\\ZCAM_Manager.py", - "ViewState": "AgIAAPwAAAAAAAAAAAAgwBMBAAAAAAAAAAAAAA==", + "ViewState": "AgIAAJAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAA==", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", "WhenOpened": "2026-03-30T10:08:49.086Z", "EditorCaption": "" diff --git a/ZCAM Manager/ZCAM Manager.slnx b/ZCAM Manager/ZCAM Manager.slnx index ed24e91..a460ae5 100644 --- a/ZCAM Manager/ZCAM Manager.slnx +++ b/ZCAM Manager/ZCAM Manager.slnx @@ -1,5 +1,5 @@ - + diff --git a/ZCAM Manager/ZCAM Manager/ZCAM_Manager.py b/ZCAM Manager/ZCAM Manager/ZCAM_Manager.py index 2bcbe03..24f31fb 100644 --- a/ZCAM Manager/ZCAM Manager/ZCAM_Manager.py +++ b/ZCAM Manager/ZCAM Manager/ZCAM_Manager.py @@ -68,10 +68,20 @@ if 'settings_need_fix' not in st.session_state: if 'master_cam_id' not in st.session_state: st.session_state.master_cam_id = None +# State for Max Exposure checking +if 'max_exp_last_check' not in st.session_state: + st.session_state.max_exp_last_check = {} +if 'max_exp_status' not in st.session_state: + st.session_state.max_exp_status = {} +if 'max_exp_was_enabled' not in st.session_state: + st.session_state.max_exp_was_enabled = False + # --- Helper Function for Parallel Requests --- -def fetch_camera_data(cam): - """Hits both endpoints every time it is called.""" - results = {"name": cam['name'], "ip": cam['ip'], "online": False, "offset": None} +def fetch_camera_data(args): + """Hits both endpoints every time it is called. Piggybacks the 2-min exposure check if active.""" + cam, check_max_exp, last_check, current_status = args + results = {"id": cam["id"], "name": cam['name'], "ip": cam['ip'], "online": False, "offset": None} + try: # 1. General Status (Fast) status_resp = requests.get(f"http://{cam['ip']}/camera_status", timeout=1.0) @@ -86,8 +96,37 @@ def fetch_camera_data(cam): raw_offset = time_resp.json().get("offset") if raw_offset is not None: results["offset"] = int(raw_offset / 1000) + + # 3. Max Exposure Check (Every 120s if enabled) + if check_max_exp: + now = time.time() + if now - last_check >= 120 or current_status is None: + try: + exp_url = f"http://{cam['ip']}/ctrl/get?k=max_exp_shutter_time" + exp_resp = requests.get(exp_url, timeout=1.0) + + if exp_resp.status_code == 200: + val = exp_resp.json().get("value") + if val != "1/1600": + # Force correct value + set_url = f"http://{cam['ip']}/ctrl/set?max_exp_shutter_time=1%2F1600" + requests.get(set_url, timeout=1.0) + results["max_exp_status"] = "Set to 1/1600" + else: + results["max_exp_status"] = "OK (1/1600)" + else: + results["max_exp_status"] = "Not streaming" + except Exception: + results["max_exp_status"] = "Not streaming" + + results["max_exp_timestamp"] = now + else: + results["max_exp_status"] = current_status + results["max_exp_timestamp"] = last_check + except Exception: pass + return results def sync_camera_to_master(ref_ip, slave_ip): @@ -128,7 +167,7 @@ def sync_camera_to_master(ref_ip, slave_ip): return False, "Error: Invalid JSON response." # --- Navigation Tabs --- -APP_VERSION = "0.2.1" +APP_VERSION = "0.3.1" st.title("ZCAM Manager") st.caption(f"Version {APP_VERSION}") tab_dash, tab_cam, tab_sync, tab_app = st.tabs(["Dashboard", "Camera Settings", "Camera Sync", "App Settings"]) @@ -138,12 +177,31 @@ tab_dash, tab_cam, tab_sync, tab_app = st.tabs(["Dashboard", "Camera Settings", def render_dashboard_status(): st.subheader("Quick Status") + col_stat, col_exp = st.columns([3, 1]) + with col_exp: + # Max Exposure Feature Checkbox + max_exp_enabled = st.checkbox("Max Exposure (Set 1/1600)", help="Checks and fixes max shutter speed every 2 mins.") + + # Detect if it was just turned on to trigger an immediate check + if max_exp_enabled and not st.session_state.max_exp_was_enabled: + for cam in st.session_state.cameras: + st.session_state.max_exp_last_check[cam['id']] = 0 + + st.session_state.max_exp_was_enabled = max_exp_enabled + if not st.session_state.cameras: st.info("No cameras configured.") return + # Prepare parallel arguments + args_list = [] + for cam in st.session_state.cameras: + last_check = st.session_state.max_exp_last_check.get(cam['id'], 0) + curr_status = st.session_state.max_exp_status.get(cam['id'], None) + args_list.append((cam, max_exp_enabled, last_check, curr_status)) + with ThreadPoolExecutor() as executor: - stats_list = list(executor.map(fetch_camera_data, st.session_state.cameras)) + stats_list = list(executor.map(fetch_camera_data, args_list)) current_row = {"Timestamp": datetime.now().strftime("%H:%M:%S")} @@ -183,6 +241,18 @@ def render_dashboard_status(): if data["offset"] is not None: st.metric("PTP Offset", f"{data['offset']} μs") current_row[data['name']] = data["offset"] + + # Render Max Exposure Status + if max_exp_enabled and "max_exp_timestamp" in data: + # Save state for the next fragment loop run + st.session_state.max_exp_last_check[data['id']] = data["max_exp_timestamp"] + st.session_state.max_exp_status[data['id']] = data.get("max_exp_status") + + m_status = data.get("max_exp_status") + if m_status == "Not streaming": + st.warning(f"Max Exp: {m_status}") + else: + st.success(f"Max Exp: {m_status}") else: st.error("Offline") @@ -493,4 +563,4 @@ with tab_app: st.write("") if st.button("Save App Settings", type="secondary"): - st.toast("App settings updated!") + st.toast("App settings updated!") \ No newline at end of file