V0.3.1
This commit is contained in:
BIN
Binary file not shown.
Binary file not shown.
@@ -45,7 +45,7 @@
|
|||||||
"RelativeDocumentMoniker": "ZCAM Manager\\ZCAM_Manager.py",
|
"RelativeDocumentMoniker": "ZCAM Manager\\ZCAM_Manager.py",
|
||||||
"ToolTip": "C:\\Users\\william\\Documents\\Git\\ZCAM-Manager\\ZCAM Manager\\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",
|
"RelativeToolTip": "ZCAM Manager\\ZCAM_Manager.py",
|
||||||
"ViewState": "AgIAAAwAAAAAAAAAAAAAADQAAAAyAAAAAAAAAA==",
|
"ViewState": "AgIAABcAAAAAAAAAAAAgwDQAAAAAAAAAAAAAAA==",
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|",
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|",
|
||||||
"WhenOpened": "2026-03-30T10:08:49.086Z",
|
"WhenOpened": "2026-03-30T10:08:49.086Z",
|
||||||
"EditorCaption": ""
|
"EditorCaption": ""
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"RelativeDocumentMoniker": "ZCAM Manager\\ZCAM_Manager.py",
|
"RelativeDocumentMoniker": "ZCAM Manager\\ZCAM_Manager.py",
|
||||||
"ToolTip": "C:\\Users\\william\\Documents\\Git\\ZCAM-Manager\\ZCAM Manager\\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",
|
"RelativeToolTip": "ZCAM Manager\\ZCAM_Manager.py",
|
||||||
"ViewState": "AgIAAPwAAAAAAAAAAAAgwBMBAAAAAAAAAAAAAA==",
|
"ViewState": "AgIAAJAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAA==",
|
||||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|",
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|",
|
||||||
"WhenOpened": "2026-03-30T10:08:49.086Z",
|
"WhenOpened": "2026-03-30T10:08:49.086Z",
|
||||||
"EditorCaption": ""
|
"EditorCaption": ""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<Solution>
|
<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" />
|
<Build Project="false" />
|
||||||
</Project>
|
</Project>
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
@@ -68,10 +68,20 @@ if 'settings_need_fix' not in st.session_state:
|
|||||||
if 'master_cam_id' not in st.session_state:
|
if 'master_cam_id' not in st.session_state:
|
||||||
st.session_state.master_cam_id = None
|
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 ---
|
# --- Helper Function for Parallel Requests ---
|
||||||
def fetch_camera_data(cam):
|
def fetch_camera_data(args):
|
||||||
"""Hits both endpoints every time it is called."""
|
"""Hits both endpoints every time it is called. Piggybacks the 2-min exposure check if active."""
|
||||||
results = {"name": cam['name'], "ip": cam['ip'], "online": False, "offset": None}
|
cam, check_max_exp, last_check, current_status = args
|
||||||
|
results = {"id": cam["id"], "name": cam['name'], "ip": cam['ip'], "online": False, "offset": None}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. General Status (Fast)
|
# 1. General Status (Fast)
|
||||||
status_resp = requests.get(f"http://{cam['ip']}/camera_status", timeout=1.0)
|
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")
|
raw_offset = time_resp.json().get("offset")
|
||||||
if raw_offset is not None:
|
if raw_offset is not None:
|
||||||
results["offset"] = int(raw_offset / 1000)
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def sync_camera_to_master(ref_ip, slave_ip):
|
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."
|
return False, "Error: Invalid JSON response."
|
||||||
|
|
||||||
# --- Navigation Tabs ---
|
# --- Navigation Tabs ---
|
||||||
APP_VERSION = "0.2.1"
|
APP_VERSION = "0.3.1"
|
||||||
st.title("ZCAM Manager")
|
st.title("ZCAM Manager")
|
||||||
st.caption(f"Version {APP_VERSION}")
|
st.caption(f"Version {APP_VERSION}")
|
||||||
tab_dash, tab_cam, tab_sync, tab_app = st.tabs(["Dashboard", "Camera Settings", "Camera Sync", "App Settings"])
|
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():
|
def render_dashboard_status():
|
||||||
st.subheader("Quick 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:
|
if not st.session_state.cameras:
|
||||||
st.info("No cameras configured.")
|
st.info("No cameras configured.")
|
||||||
return
|
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:
|
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")}
|
current_row = {"Timestamp": datetime.now().strftime("%H:%M:%S")}
|
||||||
|
|
||||||
@@ -183,6 +241,18 @@ def render_dashboard_status():
|
|||||||
if data["offset"] is not None:
|
if data["offset"] is not None:
|
||||||
st.metric("PTP Offset", f"{data['offset']} μs")
|
st.metric("PTP Offset", f"{data['offset']} μs")
|
||||||
current_row[data['name']] = data["offset"]
|
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:
|
else:
|
||||||
st.error("Offline")
|
st.error("Offline")
|
||||||
|
|
||||||
@@ -493,4 +563,4 @@ with tab_app:
|
|||||||
|
|
||||||
st.write("")
|
st.write("")
|
||||||
if st.button("Save App Settings", type="secondary"):
|
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