qapu-cli 0.7.3__tar.gz → 0.7.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qapu_cli-0.7.3/qapu_cli.egg-info → qapu_cli-0.7.4}/PKG-INFO +10 -2
- qapu_cli-0.7.3/PKG-INFO → qapu_cli-0.7.4/README.md +9 -13
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/pyproject.toml +1 -1
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/qapu_cli/main.py +116 -37
- qapu_cli-0.7.3/README.md → qapu_cli-0.7.4/qapu_cli.egg-info/PKG-INFO +21 -1
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/qapu_cli/__init__.py +0 -0
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/qapu_cli/client.py +0 -0
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/qapu_cli.egg-info/SOURCES.txt +0 -0
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/qapu_cli.egg-info/dependency_links.txt +0 -0
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/qapu_cli.egg-info/entry_points.txt +0 -0
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/qapu_cli.egg-info/requires.txt +0 -0
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/qapu_cli.egg-info/top_level.txt +0 -0
- {qapu_cli-0.7.3 → qapu_cli-0.7.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qapu-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm.
|
|
5
5
|
Author: OVOO Technology
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -56,7 +56,7 @@ Either way installs a `qapu` command (see `pyproject.toml`'s `[project.scripts]`
|
|
|
56
56
|
|---|---|
|
|
57
57
|
| `qapu ct-check` | Diagnose CT (current transformer) wiring per phase - reversed polarity vs. wrong phase order - from live PF_R/S/T/AE_R/S/T (GET /hermes/data/{device_id}). |
|
|
58
58
|
| `qapu current` | Current synthesis parameters, grouped (RMS, imbalance, THD, crest factor, fundamental component) (GET /hermes/data/{device_id}). |
|
|
59
|
-
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days),
|
|
59
|
+
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days), an explicit --start/--end range, or a live-refreshed view (--watch) (GET /hermes/data/{device_id}). |
|
|
60
60
|
| `qapu energy` | One day's active/reactive energy breakdown per phase (GET /hermes/energy/{device_id}). |
|
|
61
61
|
| `qapu health` | Check CLI/server version, connectivity, and server status (GET /health, no auth required). |
|
|
62
62
|
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). `--watch` live-refreshes in place. |
|
|
@@ -319,6 +319,14 @@ qapu data <device_id> --start 2026-08-30 --end 2026-08-31 --days 5 # daily mi
|
|
|
319
319
|
|
|
320
320
|
**Refined again same day, per three more rounds of direct feedback**: (1) `--last`'s TIME column showed a coarse relative bucket ("1 gün önce") for every one of the N readings - useless once several readings from the same day all collapse to the same bucket. Added a new `_format_time()` helper (`tools/cli/qapu_cli/main.py`, next to the existing `_relative_time()`) that renders the actual data timestamp as `YYYY-MM-DD HH:MM:SS` instead - used only where each row needs its own real time (this per-reading table); `_relative_time()` itself (SIM last-connection, timeline, etc.) is untouched, still relative, since "how long ago" is the right framing there. (2) The per-variable group tables were initially borderless (`box=None`) for a lighter look - reverted to the same bordered `Table()` style every other CLI table already uses ("görsel kısmı güzelleştir tablolu yap"), since the borderless version read as less like a real table, not more. `--json` output is untouched either way ("--json diyince ai kendine göre okur onu" - raw JSON is for machine consumption, formatting choices don't need to accommodate it). (3) The `VARIABLE_ID — Description (Unit)` heading was originally a plain `console.print()` line sitting above each group's table, not visually part of it - moved onto the `Table` itself via Rich's `title=`/`title_justify="left"`/`title_style="bold"` params, so it now renders attached to the table's own border ("kısmını da tabloya dahil edelim tablo yapısı ile görünsün"). Live-tested against real prod data: `qapu data <id> --search VRMS --last 5` now shows 5 grouped, bordered, titled tables with real per-reading timestamps (e.g. `2026-09-01 03:12:14`).
|
|
321
321
|
|
|
322
|
+
**`qapu data --watch`/`-w` (added 2026-09-04)** - the same live-refresh idea `qapu pipeline --watch` uses (see that command's own entry further down for the underlying pattern), applied to a single device's latest readings instead of the fleet-wide pipeline summary, per a same-day follow-up ("aynı şekilde data izlerken de bir canlı uç açabilirmiyiz"):
|
|
323
|
+
```bash
|
|
324
|
+
qapu data <device_id> --watch # every variable, live
|
|
325
|
+
qapu data <device_id> --watch --search VRMS # combine with family/search filters, same as normal mode
|
|
326
|
+
qapu data <device_id> --watch --interval 2 # poll every 2s instead of the default 5s
|
|
327
|
+
```
|
|
328
|
+
Only valid for the default latest-value mode - refuses clearly if combined with `--last`/`--days`/`--start`/`--end`/`--json`. Filtering logic (`--energy`/`--gsm`/`--voltage`/`--current`/`--battery`/`--search`) was extracted into a shared `_apply_data_filters()` so the watch loop and the normal path can't drift apart. Each poll after the first shows a `DEĞİŞİM` column - green `▲ <diff>` if a variable rose since the last poll, red `▼ <diff>` if it fell, `·` if unchanged - and `ZAMAN` shows the absolute reading timestamp (`_format_time()`, not `_relative_time()` - a 5-second poll loop would show "0 dk önce" for every row regardless of whether it just changed, same reasoning already applied to `--last`'s per-reading table above). Verified the same way as `pipeline --watch`'s delta logic: `_build_data_watch_table()` called directly with two synthetic consecutive polls (one variable rising, one falling, one flat), confirmed correct `▲`/`▼`/`·` rendering - `rich.Live`'s actual screen-redraw behavior wasn't separately re-verified end to end here either, for the same non-TTY sandbox reason noted under `pipeline --watch`.
|
|
329
|
+
|
|
322
330
|
```bash
|
|
323
331
|
qapu trend <device_id> <variable_id> # GET /hermes/trend/{device_id}/{variable_id} - last 20 raw readings + trend
|
|
324
332
|
qapu trend <device_id> <variable_id> --last 50 # last N raw readings instead of the default 20
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: qapu-cli
|
|
3
|
-
Version: 0.7.3
|
|
4
|
-
Summary: CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm.
|
|
5
|
-
Author: OVOO Technology
|
|
6
|
-
Classifier: Programming Language :: Python :: 3
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: typer<1.0,>=0.12
|
|
10
|
-
Requires-Dist: httpx<1.0,>=0.27
|
|
11
|
-
Requires-Dist: rich<16.0,>=13.0
|
|
12
|
-
|
|
13
1
|
# Qapu CLI
|
|
14
2
|
|
|
15
3
|
A thin command-line client for the Qapu API (`api.ovoo.com.tr`), built for the Hermes agent (runs outside this Swarm, in a separate datacenter, and only ever talks to Qapu through this public API) - but usable by anything else that needs to script against Qapu from outside the private network.
|
|
@@ -56,7 +44,7 @@ Either way installs a `qapu` command (see `pyproject.toml`'s `[project.scripts]`
|
|
|
56
44
|
|---|---|
|
|
57
45
|
| `qapu ct-check` | Diagnose CT (current transformer) wiring per phase - reversed polarity vs. wrong phase order - from live PF_R/S/T/AE_R/S/T (GET /hermes/data/{device_id}). |
|
|
58
46
|
| `qapu current` | Current synthesis parameters, grouped (RMS, imbalance, THD, crest factor, fundamental component) (GET /hermes/data/{device_id}). |
|
|
59
|
-
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days),
|
|
47
|
+
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days), an explicit --start/--end range, or a live-refreshed view (--watch) (GET /hermes/data/{device_id}). |
|
|
60
48
|
| `qapu energy` | One day's active/reactive energy breakdown per phase (GET /hermes/energy/{device_id}). |
|
|
61
49
|
| `qapu health` | Check CLI/server version, connectivity, and server status (GET /health, no auth required). |
|
|
62
50
|
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). `--watch` live-refreshes in place. |
|
|
@@ -319,6 +307,14 @@ qapu data <device_id> --start 2026-08-30 --end 2026-08-31 --days 5 # daily mi
|
|
|
319
307
|
|
|
320
308
|
**Refined again same day, per three more rounds of direct feedback**: (1) `--last`'s TIME column showed a coarse relative bucket ("1 gün önce") for every one of the N readings - useless once several readings from the same day all collapse to the same bucket. Added a new `_format_time()` helper (`tools/cli/qapu_cli/main.py`, next to the existing `_relative_time()`) that renders the actual data timestamp as `YYYY-MM-DD HH:MM:SS` instead - used only where each row needs its own real time (this per-reading table); `_relative_time()` itself (SIM last-connection, timeline, etc.) is untouched, still relative, since "how long ago" is the right framing there. (2) The per-variable group tables were initially borderless (`box=None`) for a lighter look - reverted to the same bordered `Table()` style every other CLI table already uses ("görsel kısmı güzelleştir tablolu yap"), since the borderless version read as less like a real table, not more. `--json` output is untouched either way ("--json diyince ai kendine göre okur onu" - raw JSON is for machine consumption, formatting choices don't need to accommodate it). (3) The `VARIABLE_ID — Description (Unit)` heading was originally a plain `console.print()` line sitting above each group's table, not visually part of it - moved onto the `Table` itself via Rich's `title=`/`title_justify="left"`/`title_style="bold"` params, so it now renders attached to the table's own border ("kısmını da tabloya dahil edelim tablo yapısı ile görünsün"). Live-tested against real prod data: `qapu data <id> --search VRMS --last 5` now shows 5 grouped, bordered, titled tables with real per-reading timestamps (e.g. `2026-09-01 03:12:14`).
|
|
321
309
|
|
|
310
|
+
**`qapu data --watch`/`-w` (added 2026-09-04)** - the same live-refresh idea `qapu pipeline --watch` uses (see that command's own entry further down for the underlying pattern), applied to a single device's latest readings instead of the fleet-wide pipeline summary, per a same-day follow-up ("aynı şekilde data izlerken de bir canlı uç açabilirmiyiz"):
|
|
311
|
+
```bash
|
|
312
|
+
qapu data <device_id> --watch # every variable, live
|
|
313
|
+
qapu data <device_id> --watch --search VRMS # combine with family/search filters, same as normal mode
|
|
314
|
+
qapu data <device_id> --watch --interval 2 # poll every 2s instead of the default 5s
|
|
315
|
+
```
|
|
316
|
+
Only valid for the default latest-value mode - refuses clearly if combined with `--last`/`--days`/`--start`/`--end`/`--json`. Filtering logic (`--energy`/`--gsm`/`--voltage`/`--current`/`--battery`/`--search`) was extracted into a shared `_apply_data_filters()` so the watch loop and the normal path can't drift apart. Each poll after the first shows a `DEĞİŞİM` column - green `▲ <diff>` if a variable rose since the last poll, red `▼ <diff>` if it fell, `·` if unchanged - and `ZAMAN` shows the absolute reading timestamp (`_format_time()`, not `_relative_time()` - a 5-second poll loop would show "0 dk önce" for every row regardless of whether it just changed, same reasoning already applied to `--last`'s per-reading table above). Verified the same way as `pipeline --watch`'s delta logic: `_build_data_watch_table()` called directly with two synthetic consecutive polls (one variable rising, one falling, one flat), confirmed correct `▲`/`▼`/`·` rendering - `rich.Live`'s actual screen-redraw behavior wasn't separately re-verified end to end here either, for the same non-TTY sandbox reason noted under `pipeline --watch`.
|
|
317
|
+
|
|
322
318
|
```bash
|
|
323
319
|
qapu trend <device_id> <variable_id> # GET /hermes/trend/{device_id}/{variable_id} - last 20 raw readings + trend
|
|
324
320
|
qapu trend <device_id> <variable_id> --last 50 # last N raw readings instead of the default 20
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "qapu-cli"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.4"
|
|
4
4
|
description = "CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -804,42 +804,16 @@ def _print_data_daily_grouped(readings: list[dict[str, Any]]) -> None:
|
|
|
804
804
|
console.print(table)
|
|
805
805
|
|
|
806
806
|
|
|
807
|
-
#
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
gsm: bool = typer.Option(False, "--gsm", help="Only GSM segment variables (signal, network, etc)."),
|
|
813
|
-
voltage: bool = typer.Option(False, "--voltage", help="Only voltage variables (Unit == V - includes battery voltage)."),
|
|
814
|
-
current: bool = typer.Option(False, "--current", help="Only current variables (Unit == A)."),
|
|
815
|
-
battery: bool = typer.Option(False, "--battery", help="Only battery variables (Variable ID starting with B_)."),
|
|
816
|
-
search: Optional[str] = typer.Option(None, "--search", help="Case-insensitive substring match on the variable ID (e.g. --search vrms for VRMS_R/S/T, or --search AE for AE_R/AE_S/AE_T/AE_L/AE_G - drop the phase/direction suffix to pull the whole family)."),
|
|
817
|
-
last: Optional[int] = typer.Option(None, "--last", help="Show the last N buffered readings per variable instead of just the latest. Bounded by the buffer's own depth (~50 most recent packets) - not a time window."),
|
|
818
|
-
days: Optional[int] = typer.Option(None, "--days", help="Show daily min/avg/max over the last N days per variable instead of the latest value. Ignored if --start/--end is also given (the range wins)."),
|
|
819
|
-
start: Optional[str] = typer.Option(None, "--start", help="ISO date/datetime lower bound, inclusive (e.g. 2026-08-30 or 2026-08-30T14:00:00). Combine with --end for an explicit range - takes priority over --last/--days. Daily min/avg/max narrowed to the range if --days was also given, raw readings otherwise."),
|
|
820
|
-
end: Optional[str] = typer.Option(None, "--end", help="ISO date/datetime upper bound, inclusive."),
|
|
821
|
-
as_json: bool = typer.Option(False, "--json", help="Print raw JSON instead of a table."),
|
|
822
|
-
):
|
|
823
|
-
"""Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days), or an explicit --start/--end range (GET /hermes/data/{device_id})."""
|
|
824
|
-
|
|
825
|
-
# Fetch the Readings - Mode Depends on --last/--days/--start/--end,
|
|
826
|
-
# Already Enriched with Description/Unit/Segment Regardless of Mode
|
|
827
|
-
params: dict[str, Any] = {}
|
|
828
|
-
if days is not None:
|
|
829
|
-
params["days"] = days
|
|
830
|
-
elif last is not None:
|
|
831
|
-
params["last"] = last
|
|
832
|
-
if start is not None:
|
|
833
|
-
params["start"] = start
|
|
834
|
-
if end is not None:
|
|
835
|
-
params["end"] = end
|
|
836
|
-
readings: list[dict[str, Any]] = client.get(f"/hermes/data/{device_id}", params=params or None)
|
|
807
|
+
# Apply --energy/--gsm/--voltage/--current/--battery/--search Filters to a
|
|
808
|
+
# Readings List - Extracted 2026-09-04 So `data --watch`'s Poll Loop Can
|
|
809
|
+
# Reuse the Exact Same Filter Logic as the One-Shot Path, Rather Than
|
|
810
|
+
# Drifting into a Second Copy.
|
|
811
|
+
def _apply_data_filters(readings: list[dict[str, Any]], energy: bool, gsm: bool, voltage: bool, current: bool, battery: bool, search: Optional[str]) -> list[dict[str, Any]]:
|
|
837
812
|
|
|
838
813
|
# Apply Family Filters, if Any - Union (OR) Across Whichever Were Given -
|
|
839
814
|
# Same Filter Logic Regardless of Mode, Since Variable_ID/Unit/Segment
|
|
840
815
|
# are Present on Every Row in Every Mode
|
|
841
|
-
|
|
842
|
-
if filters_selected:
|
|
816
|
+
if energy or gsm or voltage or current or battery:
|
|
843
817
|
|
|
844
818
|
# Filter In Place
|
|
845
819
|
filtered: list[dict[str, Any]] = []
|
|
@@ -874,6 +848,112 @@ def device_data(
|
|
|
874
848
|
needle = search.strip().lower()
|
|
875
849
|
readings = [r for r in readings if needle in (r.get("Variable_ID") or "").lower()]
|
|
876
850
|
|
|
851
|
+
# Return the Filtered List, Sorted for Stable Output
|
|
852
|
+
return sorted(readings, key=lambda r: r.get("Variable_ID") or "")
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
# Build a Live-Watch Table for `data --watch` - VARIABLE/VALUE/UNIT/
|
|
856
|
+
# DEĞİŞİM (Changed-Since-Last-Poll Indicator)/TIME, Grouped Neither by
|
|
857
|
+
# Variable (There's Only Ever One Reading per Variable in "Latest" Mode,
|
|
858
|
+
# Unlike --last's Multi-Reading Groups) - Absolute Time (_format_time),
|
|
859
|
+
# not Relative, Since a 5-Second Poll Loop Would Show "0 dk önce" for
|
|
860
|
+
# Every Row Regardless of Whether it Actually Just Changed.
|
|
861
|
+
def _build_data_watch_table(device_id: str, readings: list[dict[str, Any]], previous: Optional[dict[str, Any]], as_of: str) -> Group:
|
|
862
|
+
|
|
863
|
+
header = f"[bold]{device_id}[/bold] - Canlı Veri [dim](güncellendi: {as_of})[/dim]"
|
|
864
|
+
|
|
865
|
+
table = Table(show_header=True, header_style="bold")
|
|
866
|
+
table.add_column("DEĞİŞKEN")
|
|
867
|
+
table.add_column("DEĞER", justify="right")
|
|
868
|
+
table.add_column("BİRİM")
|
|
869
|
+
if previous is not None:
|
|
870
|
+
table.add_column("DEĞİŞİM")
|
|
871
|
+
table.add_column("ZAMAN")
|
|
872
|
+
|
|
873
|
+
for r in readings:
|
|
874
|
+
variable_id = r.get("Variable_ID") or "-"
|
|
875
|
+
value = r.get("Value")
|
|
876
|
+
value_str = f"{value:g}" if isinstance(value, (int, float)) else "-"
|
|
877
|
+
unit = r.get("Unit") or "-"
|
|
878
|
+
reading_time = _format_time(r.get("Time"))
|
|
879
|
+
|
|
880
|
+
row = [variable_id, value_str, unit]
|
|
881
|
+
if previous is not None:
|
|
882
|
+
prev_value = previous.get(variable_id, {}).get("Value") if isinstance(previous.get(variable_id), dict) else None
|
|
883
|
+
if prev_value is None or not isinstance(value, (int, float)):
|
|
884
|
+
row.append("")
|
|
885
|
+
elif value > prev_value:
|
|
886
|
+
row.append(f"[green]▲ {value - prev_value:g}[/green]")
|
|
887
|
+
elif value < prev_value:
|
|
888
|
+
row.append(f"[red]▼ {prev_value - value:g}[/red]")
|
|
889
|
+
else:
|
|
890
|
+
row.append("·")
|
|
891
|
+
row.append(reading_time)
|
|
892
|
+
table.add_row(*row)
|
|
893
|
+
|
|
894
|
+
return Group(header, table)
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
# Get a Device's Readings per Variable, Optionally Filtered by Family/Time Window
|
|
898
|
+
@app.command("data")
|
|
899
|
+
def device_data(
|
|
900
|
+
device_id: str = typer.Argument(..., help="Device ID."),
|
|
901
|
+
energy: bool = typer.Option(False, "--energy", help="Only Energy segment variables."),
|
|
902
|
+
gsm: bool = typer.Option(False, "--gsm", help="Only GSM segment variables (signal, network, etc)."),
|
|
903
|
+
voltage: bool = typer.Option(False, "--voltage", help="Only voltage variables (Unit == V - includes battery voltage)."),
|
|
904
|
+
current: bool = typer.Option(False, "--current", help="Only current variables (Unit == A)."),
|
|
905
|
+
battery: bool = typer.Option(False, "--battery", help="Only battery variables (Variable ID starting with B_)."),
|
|
906
|
+
search: Optional[str] = typer.Option(None, "--search", help="Case-insensitive substring match on the variable ID (e.g. --search vrms for VRMS_R/S/T, or --search AE for AE_R/AE_S/AE_T/AE_L/AE_G - drop the phase/direction suffix to pull the whole family)."),
|
|
907
|
+
last: Optional[int] = typer.Option(None, "--last", help="Show the last N buffered readings per variable instead of just the latest. Bounded by the buffer's own depth (~50 most recent packets) - not a time window."),
|
|
908
|
+
days: Optional[int] = typer.Option(None, "--days", help="Show daily min/avg/max over the last N days per variable instead of the latest value. Ignored if --start/--end is also given (the range wins)."),
|
|
909
|
+
start: Optional[str] = typer.Option(None, "--start", help="ISO date/datetime lower bound, inclusive (e.g. 2026-08-30 or 2026-08-30T14:00:00). Combine with --end for an explicit range - takes priority over --last/--days. Daily min/avg/max narrowed to the range if --days was also given, raw readings otherwise."),
|
|
910
|
+
end: Optional[str] = typer.Option(None, "--end", help="ISO date/datetime upper bound, inclusive."),
|
|
911
|
+
watch: bool = typer.Option(False, "--watch", "-w", help="Live-refresh the latest-value table in place every --interval seconds (Ctrl+C to stop). Only compatible with the default latest-value mode - not combinable with --last/--days/--start/--end/--json."),
|
|
912
|
+
interval: float = typer.Option(5.0, "--interval", help="Seconds between refreshes in --watch mode."),
|
|
913
|
+
as_json: bool = typer.Option(False, "--json", help="Print raw JSON instead of a table."),
|
|
914
|
+
):
|
|
915
|
+
"""Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days), an explicit --start/--end range, or a live-refreshed view (--watch) (GET /hermes/data/{device_id})."""
|
|
916
|
+
|
|
917
|
+
# --watch: Only Meaningful for the Latest-Value Mode - Refuse Clearly
|
|
918
|
+
# Rather than Silently Ignoring --last/--days/--start/--end/--json
|
|
919
|
+
if watch:
|
|
920
|
+
if last is not None or days is not None or start is not None or end is not None or as_json:
|
|
921
|
+
typer.echo("Error: --watch sadece varsayılan (son değer) modla kullanılabilir, --last/--days/--start/--end/--json ile birlikte kullanılamaz.", err=True)
|
|
922
|
+
raise typer.Exit(code=1)
|
|
923
|
+
|
|
924
|
+
previous: Optional[dict[str, dict[str, Any]]] = None
|
|
925
|
+
try:
|
|
926
|
+
with Live(console=console, refresh_per_second=4) as live:
|
|
927
|
+
while True:
|
|
928
|
+
readings = client.get(f"/hermes/data/{device_id}")
|
|
929
|
+
readings = _apply_data_filters(readings, energy, gsm, voltage, current, battery, search)
|
|
930
|
+
as_of = datetime.now().strftime("%H:%M:%S")
|
|
931
|
+
if not readings:
|
|
932
|
+
live.update(Group(f"[bold]{device_id}[/bold] - Canlı Veri [dim](güncellendi: {as_of})[/dim]", "\n[yellow]Filtreye uyan veri yok.[/yellow]"))
|
|
933
|
+
else:
|
|
934
|
+
live.update(_build_data_watch_table(device_id, readings, previous, as_of))
|
|
935
|
+
previous = {r.get("Variable_ID"): r for r in readings if r.get("Variable_ID")}
|
|
936
|
+
time.sleep(interval)
|
|
937
|
+
except KeyboardInterrupt:
|
|
938
|
+
return
|
|
939
|
+
|
|
940
|
+
# Fetch the Readings - Mode Depends on --last/--days/--start/--end,
|
|
941
|
+
# Already Enriched with Description/Unit/Segment Regardless of Mode
|
|
942
|
+
params: dict[str, Any] = {}
|
|
943
|
+
if days is not None:
|
|
944
|
+
params["days"] = days
|
|
945
|
+
elif last is not None:
|
|
946
|
+
params["last"] = last
|
|
947
|
+
if start is not None:
|
|
948
|
+
params["start"] = start
|
|
949
|
+
if end is not None:
|
|
950
|
+
params["end"] = end
|
|
951
|
+
readings: list[dict[str, Any]] = client.get(f"/hermes/data/{device_id}", params=params or None)
|
|
952
|
+
|
|
953
|
+
# Apply Family/Search Filters, if Any
|
|
954
|
+
filters_selected = energy or gsm or voltage or current or battery
|
|
955
|
+
readings = _apply_data_filters(readings, energy, gsm, voltage, current, battery, search)
|
|
956
|
+
|
|
877
957
|
# JSON Output - Everything at Once, Never Paginated
|
|
878
958
|
if as_json:
|
|
879
959
|
|
|
@@ -886,11 +966,10 @@ def device_data(
|
|
|
886
966
|
typer.echo("Bu cihaz için henüz veri yok." if not (filters_selected or search) else "Filtreye uyan veri bulunamadı.")
|
|
887
967
|
return
|
|
888
968
|
|
|
889
|
-
#
|
|
890
|
-
#
|
|
891
|
-
#
|
|
892
|
-
#
|
|
893
|
-
readings = sorted(readings, key=lambda r: r.get("Variable_ID") or "")
|
|
969
|
+
# Print with the Table Shape the Mode Needs - the Response Shape
|
|
970
|
+
# (Daily/History/Value Keys) is What Actually Decides This, not the
|
|
971
|
+
# Request Params Directly, Since --start/--end Alone (no --days) Comes
|
|
972
|
+
# Back in the Same "History" Shape --last Uses
|
|
894
973
|
if days is not None:
|
|
895
974
|
_print_data_daily_grouped(readings)
|
|
896
975
|
elif last is not None or start is not None or end is not None:
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qapu-cli
|
|
3
|
+
Version: 0.7.4
|
|
4
|
+
Summary: CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm.
|
|
5
|
+
Author: OVOO Technology
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
10
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
11
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
12
|
+
|
|
1
13
|
# Qapu CLI
|
|
2
14
|
|
|
3
15
|
A thin command-line client for the Qapu API (`api.ovoo.com.tr`), built for the Hermes agent (runs outside this Swarm, in a separate datacenter, and only ever talks to Qapu through this public API) - but usable by anything else that needs to script against Qapu from outside the private network.
|
|
@@ -44,7 +56,7 @@ Either way installs a `qapu` command (see `pyproject.toml`'s `[project.scripts]`
|
|
|
44
56
|
|---|---|
|
|
45
57
|
| `qapu ct-check` | Diagnose CT (current transformer) wiring per phase - reversed polarity vs. wrong phase order - from live PF_R/S/T/AE_R/S/T (GET /hermes/data/{device_id}). |
|
|
46
58
|
| `qapu current` | Current synthesis parameters, grouped (RMS, imbalance, THD, crest factor, fundamental component) (GET /hermes/data/{device_id}). |
|
|
47
|
-
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days),
|
|
59
|
+
| `qapu data` | Readings per variable for a device - latest value, last N buffered readings (--last), daily min/avg/max (--days), an explicit --start/--end range, or a live-refreshed view (--watch) (GET /hermes/data/{device_id}). |
|
|
48
60
|
| `qapu energy` | One day's active/reactive energy breakdown per phase (GET /hermes/energy/{device_id}). |
|
|
49
61
|
| `qapu health` | Check CLI/server version, connectivity, and server status (GET /health, no auth required). |
|
|
50
62
|
| `qapu pipeline` | Fleet-wide pipeline throughput for a time window - ingest/data/rule/blockchain packet+event counts (GET /hermes/pipeline/stats). `--watch` live-refreshes in place. |
|
|
@@ -307,6 +319,14 @@ qapu data <device_id> --start 2026-08-30 --end 2026-08-31 --days 5 # daily mi
|
|
|
307
319
|
|
|
308
320
|
**Refined again same day, per three more rounds of direct feedback**: (1) `--last`'s TIME column showed a coarse relative bucket ("1 gün önce") for every one of the N readings - useless once several readings from the same day all collapse to the same bucket. Added a new `_format_time()` helper (`tools/cli/qapu_cli/main.py`, next to the existing `_relative_time()`) that renders the actual data timestamp as `YYYY-MM-DD HH:MM:SS` instead - used only where each row needs its own real time (this per-reading table); `_relative_time()` itself (SIM last-connection, timeline, etc.) is untouched, still relative, since "how long ago" is the right framing there. (2) The per-variable group tables were initially borderless (`box=None`) for a lighter look - reverted to the same bordered `Table()` style every other CLI table already uses ("görsel kısmı güzelleştir tablolu yap"), since the borderless version read as less like a real table, not more. `--json` output is untouched either way ("--json diyince ai kendine göre okur onu" - raw JSON is for machine consumption, formatting choices don't need to accommodate it). (3) The `VARIABLE_ID — Description (Unit)` heading was originally a plain `console.print()` line sitting above each group's table, not visually part of it - moved onto the `Table` itself via Rich's `title=`/`title_justify="left"`/`title_style="bold"` params, so it now renders attached to the table's own border ("kısmını da tabloya dahil edelim tablo yapısı ile görünsün"). Live-tested against real prod data: `qapu data <id> --search VRMS --last 5` now shows 5 grouped, bordered, titled tables with real per-reading timestamps (e.g. `2026-09-01 03:12:14`).
|
|
309
321
|
|
|
322
|
+
**`qapu data --watch`/`-w` (added 2026-09-04)** - the same live-refresh idea `qapu pipeline --watch` uses (see that command's own entry further down for the underlying pattern), applied to a single device's latest readings instead of the fleet-wide pipeline summary, per a same-day follow-up ("aynı şekilde data izlerken de bir canlı uç açabilirmiyiz"):
|
|
323
|
+
```bash
|
|
324
|
+
qapu data <device_id> --watch # every variable, live
|
|
325
|
+
qapu data <device_id> --watch --search VRMS # combine with family/search filters, same as normal mode
|
|
326
|
+
qapu data <device_id> --watch --interval 2 # poll every 2s instead of the default 5s
|
|
327
|
+
```
|
|
328
|
+
Only valid for the default latest-value mode - refuses clearly if combined with `--last`/`--days`/`--start`/`--end`/`--json`. Filtering logic (`--energy`/`--gsm`/`--voltage`/`--current`/`--battery`/`--search`) was extracted into a shared `_apply_data_filters()` so the watch loop and the normal path can't drift apart. Each poll after the first shows a `DEĞİŞİM` column - green `▲ <diff>` if a variable rose since the last poll, red `▼ <diff>` if it fell, `·` if unchanged - and `ZAMAN` shows the absolute reading timestamp (`_format_time()`, not `_relative_time()` - a 5-second poll loop would show "0 dk önce" for every row regardless of whether it just changed, same reasoning already applied to `--last`'s per-reading table above). Verified the same way as `pipeline --watch`'s delta logic: `_build_data_watch_table()` called directly with two synthetic consecutive polls (one variable rising, one falling, one flat), confirmed correct `▲`/`▼`/`·` rendering - `rich.Live`'s actual screen-redraw behavior wasn't separately re-verified end to end here either, for the same non-TTY sandbox reason noted under `pipeline --watch`.
|
|
329
|
+
|
|
310
330
|
```bash
|
|
311
331
|
qapu trend <device_id> <variable_id> # GET /hermes/trend/{device_id}/{variable_id} - last 20 raw readings + trend
|
|
312
332
|
qapu trend <device_id> <variable_id> --last 50 # last N raw readings instead of the default 20
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|