mirror of
https://github.com/migatu/vtt_work.git
synced 2026-07-14 13:34:42 +00:00
1.13
This commit is contained in:
@@ -265,6 +265,11 @@ class Thresholds:
|
|||||||
# sources for y and z
|
# sources for y and z
|
||||||
y_source: Optional[str] = None
|
y_source: Optional[str] = None
|
||||||
z_source: Optional[str] = None
|
z_source: Optional[str] = None
|
||||||
|
expr_x_source_dead_low: str = "raw" # "raw" | "x_avg" | "dx" | "dx_avg"
|
||||||
|
expr_x_source_low: str = "raw"
|
||||||
|
expr_x_source_operating: str = "raw"
|
||||||
|
expr_x_source_high: str = "raw"
|
||||||
|
expr_x_source_extreme_high: str = "raw"
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VarInfo:
|
class VarInfo:
|
||||||
@@ -297,6 +302,7 @@ class ActionTask:
|
|||||||
x_src: Optional[str] = None
|
x_src: Optional[str] = None
|
||||||
y_src: Optional[str] = None
|
y_src: Optional[str] = None
|
||||||
z_src: Optional[str] = None
|
z_src: Optional[str] = None
|
||||||
|
x_mode: str = "raw" # "raw" | "x_avg" | "dx" | "dx_avg"
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# Scheduler thread
|
# Scheduler thread
|
||||||
@@ -353,9 +359,27 @@ class ActionScheduler(threading.Thread):
|
|||||||
return self.get_value_cb(n) if n else None
|
return self.get_value_cb(n) if n else None
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
x = gv(task.x_src)
|
# stara wersja:
|
||||||
y = gv(task.y_src)
|
# x = gv(task.x_src); y = gv(task.y_src); z = gv(task.z_src)
|
||||||
z = gv(task.z_src)
|
def pick(stats: Optional[dict], mode: str) -> Optional[float]:
|
||||||
|
if not stats:
|
||||||
|
return None
|
||||||
|
if mode == "x_avg":
|
||||||
|
return stats.get("x_avg")
|
||||||
|
if mode == "dx":
|
||||||
|
return stats.get("dx")
|
||||||
|
if mode == "dx_avg":
|
||||||
|
return stats.get("dx_avg")
|
||||||
|
return stats.get("x")
|
||||||
|
|
||||||
|
xs = self.get_value_cb(task.x_src)
|
||||||
|
ys = self.get_value_cb(task.y_src)
|
||||||
|
zs = self.get_value_cb(task.z_src)
|
||||||
|
|
||||||
|
x = pick(xs, getattr(task, "x_mode", "raw"))
|
||||||
|
y = pick(ys, getattr(task, "y_mode", "raw"))
|
||||||
|
z = pick(zs, getattr(task, "z_mode", "raw"))
|
||||||
|
|
||||||
if x is None:
|
if x is None:
|
||||||
x = 0.0
|
x = 0.0
|
||||||
try:
|
try:
|
||||||
@@ -486,7 +510,10 @@ class App(tk.Tk):
|
|||||||
self.variables_keys: List[str] = [] # list of lower-case keys in selection order
|
self.variables_keys: List[str] = [] # list of lower-case keys in selection order
|
||||||
self.functions_list: List[str] = [] # preserve original casing
|
self.functions_list: List[str] = [] # preserve original casing
|
||||||
|
|
||||||
self.scheduler = ActionScheduler(lambda: build_base_url(self.host_var.get().strip(), int(self.port_var.get())), self.get_current_value)
|
self.scheduler = ActionScheduler(
|
||||||
|
lambda: build_base_url(self.host_var.get().strip(), int(self.port_var.get())),
|
||||||
|
self.get_stats_for
|
||||||
|
)
|
||||||
self.scheduler.start()
|
self.scheduler.start()
|
||||||
|
|
||||||
controls = ttk.Frame(self, padding=10)
|
controls = ttk.Frame(self, padding=10)
|
||||||
@@ -653,6 +680,36 @@ class App(tk.Tk):
|
|||||||
self.actions_menu.add_command(label="Run Now", command=self.run_selected_task_now)
|
self.actions_menu.add_command(label="Run Now", command=self.run_selected_task_now)
|
||||||
self.actions_menu.add_command(label="Remove", command=self.remove_selected_task)
|
self.actions_menu.add_command(label="Remove", command=self.remove_selected_task)
|
||||||
self.actions_tree.bind("<Button-3>", self.on_actions_right_click)
|
self.actions_tree.bind("<Button-3>", self.on_actions_right_click)
|
||||||
|
def get_stats_for(self, name: Optional[str]) -> Optional[dict]:
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
key = str(name).lower()
|
||||||
|
vi = self.vars.get(key)
|
||||||
|
|
||||||
|
def _avg(dq, n):
|
||||||
|
if not dq or n <= 0 or len(dq) < n:
|
||||||
|
return None
|
||||||
|
lst = list(dq)[-n:]
|
||||||
|
try:
|
||||||
|
return sum(lst) / float(n)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
n = max(1, int(self.avg_window_var.get()))
|
||||||
|
except Exception:
|
||||||
|
n = 60
|
||||||
|
|
||||||
|
if vi:
|
||||||
|
return {
|
||||||
|
"x": parse_first_float(vi.last_value),
|
||||||
|
"x_avg": _avg(vi.history, n),
|
||||||
|
"dx": vi.delta_last,
|
||||||
|
"dx_avg": _avg(vi.history_delta, n),
|
||||||
|
}
|
||||||
|
# fallback dla zmiennych nie-monitorowanych
|
||||||
|
val = self.get_current_value(name)
|
||||||
|
return {"x": val, "x_avg": None, "dx": None, "dx_avg": None}
|
||||||
|
|
||||||
# --- Discovery ---
|
# --- Discovery ---
|
||||||
def reload_discovery(self) -> None:
|
def reload_discovery(self) -> None:
|
||||||
@@ -1297,6 +1354,7 @@ class App(tk.Tk):
|
|||||||
|
|
||||||
def open_thresholds_dialog(self):
|
def open_thresholds_dialog(self):
|
||||||
sel = self.tree.selection()
|
sel = self.tree.selection()
|
||||||
|
expr_xsrc_vars = {}
|
||||||
if not sel:
|
if not sel:
|
||||||
messagebox.showinfo("No selection", "Select a variable first.")
|
messagebox.showinfo("No selection", "Select a variable first.")
|
||||||
return
|
return
|
||||||
@@ -1418,6 +1476,20 @@ class App(tk.Tk):
|
|||||||
s.grid(row=r, column=3, sticky="w", padx=6)
|
s.grid(row=r, column=3, sticky="w", padx=6)
|
||||||
self._attach_search_filter_to_combobox(s, self.functions_list)
|
self._attach_search_filter_to_combobox(s, self.functions_list)
|
||||||
expr_target_vars[tkey] = s
|
expr_target_vars[tkey] = s
|
||||||
|
ttk.Label(grid, text="x from").grid(row=r, column=4, sticky="e")
|
||||||
|
src_combo = ttk.Combobox(grid, values=["raw", "x_avg", "dx", "dx_avg"], width=8)
|
||||||
|
# mapowanie nazwy wiersza na pole w Thresholds:
|
||||||
|
statekey = {
|
||||||
|
"Dead Low expr": "dead_low",
|
||||||
|
"Low expr": "low",
|
||||||
|
"Operating expr": "operating",
|
||||||
|
"High expr": "high",
|
||||||
|
"Extreme High expr": "extreme_high",
|
||||||
|
}[label]
|
||||||
|
field_name = f"expr_x_source_{statekey}"
|
||||||
|
src_combo.set(getattr(t, field_name, "raw"))
|
||||||
|
src_combo.grid(row=r, column=5, sticky="w")
|
||||||
|
expr_xsrc_vars[field_name] = src_combo
|
||||||
|
|
||||||
# Bottom bar
|
# Bottom bar
|
||||||
btns = ttk.Frame(dlg); btns.pack(fill=tk.X, padx=10, pady=10)
|
btns = ttk.Frame(dlg); btns.pack(fill=tk.X, padx=10, pady=10)
|
||||||
@@ -1458,6 +1530,11 @@ class App(tk.Tk):
|
|||||||
expr_target_extreme_high=(expr_target_vars["expr_target_extreme_high"].get().strip() or None),
|
expr_target_extreme_high=(expr_target_vars["expr_target_extreme_high"].get().strip() or None),
|
||||||
y_source=(y_source_var.get().strip() or None),
|
y_source=(y_source_var.get().strip() or None),
|
||||||
z_source=(z_source_var.get().strip() or None),
|
z_source=(z_source_var.get().strip() or None),
|
||||||
|
expr_x_source_dead_low = expr_xsrc_vars["expr_x_source_dead_low"].get(),
|
||||||
|
expr_x_source_low = expr_xsrc_vars["expr_x_source_low"].get(),
|
||||||
|
expr_x_source_operating = expr_xsrc_vars["expr_x_source_operating"].get(),
|
||||||
|
expr_x_source_high = expr_xsrc_vars["expr_x_source_high"].get(),
|
||||||
|
expr_x_source_extreme_high = expr_xsrc_vars["expr_x_source_extreme_high"].get(),
|
||||||
)
|
)
|
||||||
vi.thresholds = nt
|
vi.thresholds = nt
|
||||||
# Re-enter current state to refresh scheduled actions/intervals
|
# Re-enter current state to refresh scheduled actions/intervals
|
||||||
@@ -1596,9 +1673,23 @@ class App(tk.Tk):
|
|||||||
val = parse_first_float(bd)
|
val = parse_first_float(bd)
|
||||||
if label == 'y': y_val = val
|
if label == 'y': y_val = val
|
||||||
else: z_val = val
|
else: z_val = val
|
||||||
if expr and x is not None:
|
x_variants = self.get_stats_for(vi.key) or {}
|
||||||
|
mode_map = {
|
||||||
|
"DEAD_LOW": t.expr_x_source_dead_low,
|
||||||
|
"LOW": t.expr_x_source_low,
|
||||||
|
"OPERATING": t.expr_x_source_operating,
|
||||||
|
"HIGH": t.expr_x_source_high,
|
||||||
|
"EXTREME_HIGH": t.expr_x_source_extreme_high,
|
||||||
|
}
|
||||||
|
x_for_expr = {
|
||||||
|
"raw": x_variants.get("x"),
|
||||||
|
"x_avg": x_variants.get("x_avg"),
|
||||||
|
"dx": x_variants.get("dx"),
|
||||||
|
"dx_avg": x_variants.get("dx_avg"),
|
||||||
|
}.get(mode_map.get(state, "raw"), x_variants.get("x"))
|
||||||
|
if expr and x_for_expr is not None:
|
||||||
try:
|
try:
|
||||||
computed = eval_user_expression(expr, x, y_val, z_val)
|
computed = eval_user_expression(expr, x_for_expr, y_val, z_val)
|
||||||
if target_func:
|
if target_func:
|
||||||
base = build_base_url(self.host_var.get().strip(), int(self.port_var.get()))
|
base = build_base_url(self.host_var.get().strip(), int(self.port_var.get()))
|
||||||
params = {"variable": target_func, "value": str(computed)}
|
params = {"variable": target_func, "value": str(computed)}
|
||||||
@@ -1661,8 +1752,12 @@ class App(tk.Tk):
|
|||||||
self.scheduler.add_task(task)
|
self.scheduler.add_task(task)
|
||||||
else:
|
else:
|
||||||
# schedule repeating expression task
|
# schedule repeating expression task
|
||||||
task = ActionTask(name=expr_target, value="", interval_s=max(0.0, float(expr_interval)), expr=expr_str,
|
task = ActionTask(
|
||||||
x_src=vi.key, y_src=t.y_source, z_src=t.z_source)
|
name=expr_target, value="", interval_s=max(0.0, float(expr_interval)),
|
||||||
|
expr=expr_str, x_src=vi.key, y_src=t.y_source, z_src=t.z_source,
|
||||||
|
x_mode=mode_map.get(state, "raw")
|
||||||
|
)
|
||||||
|
|
||||||
tid = self.scheduler.add_task(task)
|
tid = self.scheduler.add_task(task)
|
||||||
self.expr_state_tasks[(vi.key, state)] = tid
|
self.expr_state_tasks[(vi.key, state)] = tid
|
||||||
|
|
||||||
@@ -1920,6 +2015,11 @@ class App(tk.Tk):
|
|||||||
z_combo = ttk.Combobox(src_row, values=self.known_variables, textvariable=z_src, width=18)
|
z_combo = ttk.Combobox(src_row, values=self.known_variables, textvariable=z_src, width=18)
|
||||||
self._attach_search_filter_to_combobox(z_combo, self.known_variables)
|
self._attach_search_filter_to_combobox(z_combo, self.known_variables)
|
||||||
z_combo.pack(side=tk.LEFT, padx=4)
|
z_combo.pack(side=tk.LEFT, padx=4)
|
||||||
|
xmode_row = ttk.Frame(top); xmode_row.pack(fill=tk.X, padx=10, pady=(4,2))
|
||||||
|
ttk.Label(xmode_row, text="x from:").pack(side=tk.LEFT)
|
||||||
|
x_mode = tk.StringVar(value="raw")
|
||||||
|
xmode_combo = ttk.Combobox(xmode_row, values=["raw", "x_avg", "dx", "dx_avg"], textvariable=x_mode, width=10)
|
||||||
|
xmode_combo.pack(side=tk.LEFT, padx=6)
|
||||||
# Interval input
|
# Interval input
|
||||||
ttk.Label(top, text="Interval (seconds, can be < 1.0):").pack(pady=(6,2))
|
ttk.Label(top, text="Interval (seconds, can be < 1.0):").pack(pady=(6,2))
|
||||||
interval = tk.DoubleVar(value=1.0)
|
interval = tk.DoubleVar(value=1.0)
|
||||||
@@ -1940,7 +2040,12 @@ class App(tk.Tk):
|
|||||||
if mode.get() == "fixed":
|
if mode.get() == "fixed":
|
||||||
task = ActionTask(name=fname, value=val.get(), interval_s=max(0.0, float(interval.get())))
|
task = ActionTask(name=fname, value=val.get(), interval_s=max(0.0, float(interval.get())))
|
||||||
else:
|
else:
|
||||||
task = ActionTask(name=fname, value="", interval_s=max(0.0, float(interval.get())), expr=expr.get().strip() or "x", x_src=(x_src.get().strip() or None), y_src=(y_src.get().strip() or None), z_src=(z_src.get().strip() or None))
|
task = ActionTask(
|
||||||
|
name=fname, value="", interval_s=max(0.0, float(interval.get())),
|
||||||
|
expr=expr.get().strip() or "x",
|
||||||
|
x_src=(x_src.get().strip() or None), y_src=(y_src.get().strip() or None), z_src=(z_src.get().strip() or None),
|
||||||
|
x_mode=x_mode.get()
|
||||||
|
)
|
||||||
tid = self.scheduler.add_task(task)
|
tid = self.scheduler.add_task(task)
|
||||||
self.refresh_actions_tree()
|
self.refresh_actions_tree()
|
||||||
top.destroy()
|
top.destroy()
|
||||||
@@ -2015,6 +2120,11 @@ class App(tk.Tk):
|
|||||||
"expr_operating_interval": getattr(t, "expr_operating_interval", 1.0),
|
"expr_operating_interval": getattr(t, "expr_operating_interval", 1.0),
|
||||||
"y_source": getattr(t, "y_source", None),
|
"y_source": getattr(t, "y_source", None),
|
||||||
"z_source": getattr(t, "z_source", None),
|
"z_source": getattr(t, "z_source", None),
|
||||||
|
"expr_x_source_dead_low": t.expr_x_source_dead_low,
|
||||||
|
"expr_x_source_low": t.expr_x_source_low,
|
||||||
|
"expr_x_source_operating": t.expr_x_source_operating,
|
||||||
|
"expr_x_source_high": t.expr_x_source_high,
|
||||||
|
"expr_x_source_extreme_high": t.expr_x_source_extreme_high,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
def _deserialize_varinfo(self, key: str, data: dict) -> VarInfo:
|
def _deserialize_varinfo(self, key: str, data: dict) -> VarInfo:
|
||||||
@@ -2036,14 +2146,24 @@ class App(tk.Tk):
|
|||||||
# nowości (wstecznie opcjonalne):
|
# nowości (wstecznie opcjonalne):
|
||||||
expr_operating_interval=float(td.get("expr_operating_interval", 1.0)),
|
expr_operating_interval=float(td.get("expr_operating_interval", 1.0)),
|
||||||
y_source=td.get("y_source"), z_source=td.get("z_source"),
|
y_source=td.get("y_source"), z_source=td.get("z_source"),
|
||||||
|
expr_x_source_dead_low=td.get("expr_x_source_dead_low","raw"),
|
||||||
|
expr_x_source_low=td.get("expr_x_source_low","raw"),
|
||||||
|
expr_x_source_operating=td.get("expr_x_source_operating","raw"),
|
||||||
|
expr_x_source_high=td.get("expr_x_source_high","raw"),
|
||||||
|
expr_x_source_extreme_high=td.get("expr_x_source_extreme_high","raw"),
|
||||||
)
|
)
|
||||||
return VarInfo(key=key.lower(), display_name=name, thresholds=t)
|
return VarInfo(key=key.lower(), display_name=name, thresholds=t)
|
||||||
|
|
||||||
def _serialize_task(self, t: ActionTask):
|
def _serialize_task(self, t: ActionTask):
|
||||||
return {"name": t.name, "value": t.value, "interval_s": t.interval_s, "enabled": t.enabled, "expr": t.expr, "x_src": t.x_src, "y_src": t.y_src, "z_src": t.z_src}
|
return {"name": t.name, "value": t.value, "interval_s": t.interval_s, "enabled": t.enabled,
|
||||||
|
"expr": t.expr, "x_src": t.x_src, "y_src": t.y_src, "z_src": t.z_src, "x_mode": getattr(t, "x_mode", "raw")}
|
||||||
|
|
||||||
def _deserialize_task(self, d: dict) -> ActionTask:
|
def _deserialize_task(self, d: dict) -> ActionTask:
|
||||||
return ActionTask(name=d.get("name",""), value=d.get("value","1"), interval_s=float(d.get("interval_s", 1.0)), enabled=d.get("enabled", True), expr=d.get("expr"), x_src=d.get("x_src"), y_src=d.get("y_src"), z_src=d.get("z_src"))
|
return ActionTask(name=d.get("name",""), value=d.get("value","1"),
|
||||||
|
interval_s=float(d.get("interval_s", 1.0)), enabled=d.get("enabled", True),
|
||||||
|
expr=d.get("expr"), x_src=d.get("x_src"), y_src=d.get("y_src"), z_src=d.get("z_src"),
|
||||||
|
x_mode=d.get("x_mode","raw"))
|
||||||
|
|
||||||
def on_close(self) -> None:
|
def on_close(self) -> None:
|
||||||
try:
|
try:
|
||||||
if self.poller and self.poller.is_alive():
|
if self.poller and self.poller.is_alive():
|
||||||
|
|||||||
Reference in New Issue
Block a user