Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27b4711f70 |
BIN
Binary file not shown.
Binary file not shown.
@@ -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": ""
|
||||
|
||||
@@ -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": ""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<Solution>
|
||||
<Project Path="ZCAM Manager/ZCAM Manager.pyproj" Type="888888a0-9f3d-457c-b088-3a5042f75d52">
|
||||
<Project Path="ZCAM Manager/ZCAM Manager.pyproj" Type="888888a0-9f3d-457c-b088-3a5042f75d52" Id="73a03639-ce0c-4b0b-bb86-8d2f8bba2e3e">
|
||||
<Build Project="false" />
|
||||
</Project>
|
||||
</Solution>
|
||||
|
||||
@@ -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!")
|
||||
Reference in New Issue
Block a user