rdsclock 1.1.0__tar.gz → 2.0.0__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.
- {rdsclock-1.1.0 → rdsclock-2.0.0}/PKG-INFO +32 -12
- {rdsclock-1.1.0 → rdsclock-2.0.0}/README.md +31 -11
- {rdsclock-1.1.0 → rdsclock-2.0.0}/pyproject.toml +1 -1
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/__init__.py +1 -1
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/cli.py +41 -10
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/dashboard.py +133 -49
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/gui.py +55 -30
- rdsclock-2.0.0/src/rdsclock/i18n.py +276 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/tui.py +46 -24
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/web.py +68 -12
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/webui/app.js +10 -8
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/PKG-INFO +32 -12
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/SOURCES.txt +3 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_dashboard.py +20 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_gui.py +77 -2
- rdsclock-2.0.0/tests/test_i18n.py +178 -0
- rdsclock-2.0.0/tests/test_release.py +196 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_tui.py +46 -4
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_web.py +34 -1
- {rdsclock-1.1.0 → rdsclock-2.0.0}/LICENSE +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/setup.cfg +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/__main__.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/audio.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/channelizer.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/decoder.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/dsp.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/plot.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/py.typed +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/rds_blocks.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/rds_clock.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/rds_groups.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/recon.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/rtl_tcp.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/synth.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/time_consensus.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/webui/app.css +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/webui/index.html +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/dependency_links.txt +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/entry_points.txt +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/requires.txt +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/top_level.txt +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_audio.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_benchmark.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_channelizer.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_cli.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_cli_with_fake_sdr.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_coverage_edges.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_coverage_extra.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_decoder_synthetic.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_dsp.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_measure_settle.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_pipeline_delay.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_plot.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_public_api.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_rds_blocks.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_rds_clock.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_rds_groups.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_real_iq_regression.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_real_recordings.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon_events.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon_hop.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon_offline.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon_wide.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_rtl_tcp.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_synth.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_time_consensus.py +0 -0
- {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_transport_fresh.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rdsclock
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Passive RDS Clock-Time receiver for FM via RTL-SDR, with multi-source consensus for GPS-denied environments
|
|
5
5
|
Author-email: Mateusz Klatt <mateusz@klatt.ie>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -225,6 +225,26 @@ gets tkinter from `apt install python3-tk`. With `--jsonl`, `recon` instead
|
|
|
225
225
|
writes one JSON event per line for other programs; the
|
|
226
226
|
[operator guide](docs/operator-quickstart.md) describes all these modes.
|
|
227
227
|
|
|
228
|
+
#### Dashboard language
|
|
229
|
+
|
|
230
|
+
All three dashboards support English, Polish and French in 2.0.0. Use
|
|
231
|
+
`--lang en`, `--lang pl` or `--lang fr` with `--tui`, `--web` or `--gui`;
|
|
232
|
+
English is the default. For example, to open a Polish browser dashboard:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
.venv/bin/python -m rdsclock recon --host 127.0.0.1 \
|
|
236
|
+
--wide 98.3,98.8 --gain 28 --dwell 75 --web --lang pl
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The selected language applies to controls, headings, status messages and the
|
|
240
|
+
operator log. Broadcast PS/PI/CT values and JSON Lines field names remain
|
|
241
|
+
unchanged. Each dashboard owns one receiver process; run one of these commands
|
|
242
|
+
at a time against a single RTL-TCP server. The live screenshots below show the
|
|
243
|
+
English interface; the Polish and French READMEs show their own.
|
|
244
|
+
|
|
245
|
+
[2.0.0 release notes — EN / PL / FR](docs/releases/2.0.0.md) ·
|
|
246
|
+
[Release procedure](docs/releasing.md)
|
|
247
|
+
|
|
228
248
|
#### WIDE: two stations from one capture
|
|
229
249
|
|
|
230
250
|
With `--wide`, `recon` records stations that fit in the receiver bandwidth
|
|
@@ -242,15 +262,15 @@ come from this command, with `--web` or `--gui` in place of `--tui`:
|
|
|
242
262
|
|
|
243
263
|
**Terminal** (`--tui`)
|
|
244
264
|
|
|
245
|
-

|
|
265
|
+

|
|
246
266
|
|
|
247
267
|
**Browser** (`--web`)
|
|
248
268
|
|
|
249
|
-

|
|
269
|
+

|
|
250
270
|
|
|
251
271
|
**Desktop window** (`--gui`)
|
|
252
272
|
|
|
253
|
-

|
|
273
|
+

|
|
254
274
|
|
|
255
275
|
#### HOP: three stations, one after another
|
|
256
276
|
|
|
@@ -260,8 +280,8 @@ each report a different minute, and all three agree (N=3). The receiver
|
|
|
260
280
|
assesses trust once per cycle, and the dashboards keep that label until the next
|
|
261
281
|
assessment. HIGH needs three confirmed stations whose median Clock-Time is under
|
|
262
282
|
two minutes old at that moment; MEDIUM needs two under three minutes. A cycle of
|
|
263
|
-
three 75-second captures can end on either side of the two-minute limit
|
|
264
|
-
|
|
283
|
+
three 75-second captures can end on either side of the two-minute limit; the
|
|
284
|
+
cycle in these screenshots ended at HIGH:
|
|
265
285
|
|
|
266
286
|
```bash
|
|
267
287
|
.venv/bin/python -m rdsclock recon --host 127.0.0.1 \
|
|
@@ -271,19 +291,19 @@ is why the window below shows MEDIUM:
|
|
|
271
291
|
|
|
272
292
|
**Terminal** (`--tui`)
|
|
273
293
|
|
|
274
|
-

|
|
294
|
+

|
|
275
295
|
|
|
276
296
|
**Browser** (`--web`)
|
|
277
297
|
|
|
278
|
-

|
|
298
|
+

|
|
279
299
|
|
|
280
300
|
**Desktop window** (`--gui`)
|
|
281
301
|
|
|
282
|
-

|
|
302
|
+

|
|
283
303
|
|
|
284
|
-
*Live reception in Warsaw on
|
|
285
|
-
|
|
286
|
-
the
|
|
304
|
+
*Live reception in Warsaw on 4 November 2026. Received UTC declarations
|
|
305
|
+
were approximately one hour ahead of host UTC. This is an observed disagreement;
|
|
306
|
+
source agreement does not establish the cause or calibrate absolute accuracy.*
|
|
287
307
|
|
|
288
308
|
## How it works
|
|
289
309
|
|
|
@@ -185,6 +185,26 @@ gets tkinter from `apt install python3-tk`. With `--jsonl`, `recon` instead
|
|
|
185
185
|
writes one JSON event per line for other programs; the
|
|
186
186
|
[operator guide](docs/operator-quickstart.md) describes all these modes.
|
|
187
187
|
|
|
188
|
+
#### Dashboard language
|
|
189
|
+
|
|
190
|
+
All three dashboards support English, Polish and French in 2.0.0. Use
|
|
191
|
+
`--lang en`, `--lang pl` or `--lang fr` with `--tui`, `--web` or `--gui`;
|
|
192
|
+
English is the default. For example, to open a Polish browser dashboard:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
.venv/bin/python -m rdsclock recon --host 127.0.0.1 \
|
|
196
|
+
--wide 98.3,98.8 --gain 28 --dwell 75 --web --lang pl
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The selected language applies to controls, headings, status messages and the
|
|
200
|
+
operator log. Broadcast PS/PI/CT values and JSON Lines field names remain
|
|
201
|
+
unchanged. Each dashboard owns one receiver process; run one of these commands
|
|
202
|
+
at a time against a single RTL-TCP server. The live screenshots below show the
|
|
203
|
+
English interface; the Polish and French READMEs show their own.
|
|
204
|
+
|
|
205
|
+
[2.0.0 release notes — EN / PL / FR](docs/releases/2.0.0.md) ·
|
|
206
|
+
[Release procedure](docs/releasing.md)
|
|
207
|
+
|
|
188
208
|
#### WIDE: two stations from one capture
|
|
189
209
|
|
|
190
210
|
With `--wide`, `recon` records stations that fit in the receiver bandwidth
|
|
@@ -202,15 +222,15 @@ come from this command, with `--web` or `--gui` in place of `--tui`:
|
|
|
202
222
|
|
|
203
223
|
**Terminal** (`--tui`)
|
|
204
224
|
|
|
205
|
-

|
|
225
|
+

|
|
206
226
|
|
|
207
227
|
**Browser** (`--web`)
|
|
208
228
|
|
|
209
|
-

|
|
229
|
+

|
|
210
230
|
|
|
211
231
|
**Desktop window** (`--gui`)
|
|
212
232
|
|
|
213
|
-

|
|
233
|
+

|
|
214
234
|
|
|
215
235
|
#### HOP: three stations, one after another
|
|
216
236
|
|
|
@@ -220,8 +240,8 @@ each report a different minute, and all three agree (N=3). The receiver
|
|
|
220
240
|
assesses trust once per cycle, and the dashboards keep that label until the next
|
|
221
241
|
assessment. HIGH needs three confirmed stations whose median Clock-Time is under
|
|
222
242
|
two minutes old at that moment; MEDIUM needs two under three minutes. A cycle of
|
|
223
|
-
three 75-second captures can end on either side of the two-minute limit
|
|
224
|
-
|
|
243
|
+
three 75-second captures can end on either side of the two-minute limit; the
|
|
244
|
+
cycle in these screenshots ended at HIGH:
|
|
225
245
|
|
|
226
246
|
```bash
|
|
227
247
|
.venv/bin/python -m rdsclock recon --host 127.0.0.1 \
|
|
@@ -231,19 +251,19 @@ is why the window below shows MEDIUM:
|
|
|
231
251
|
|
|
232
252
|
**Terminal** (`--tui`)
|
|
233
253
|
|
|
234
|
-

|
|
254
|
+

|
|
235
255
|
|
|
236
256
|
**Browser** (`--web`)
|
|
237
257
|
|
|
238
|
-

|
|
258
|
+

|
|
239
259
|
|
|
240
260
|
**Desktop window** (`--gui`)
|
|
241
261
|
|
|
242
|
-

|
|
262
|
+

|
|
243
263
|
|
|
244
|
-
*Live reception in Warsaw on
|
|
245
|
-
|
|
246
|
-
the
|
|
264
|
+
*Live reception in Warsaw on 4 November 2026. Received UTC declarations
|
|
265
|
+
were approximately one hour ahead of host UTC. This is an observed disagreement;
|
|
266
|
+
source agreement does not establish the cause or calibrate absolute accuracy.*
|
|
247
267
|
|
|
248
268
|
## How it works
|
|
249
269
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rdsclock"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
description = "Passive RDS Clock-Time receiver for FM via RTL-SDR, with multi-source consensus for GPS-denied environments"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -37,6 +37,7 @@ from .channelizer import (
|
|
|
37
37
|
required_bandwidth,
|
|
38
38
|
)
|
|
39
39
|
from .decoder import decode_file, decode_iq
|
|
40
|
+
from .i18n import SUPPORTED_LANGUAGES, translate
|
|
40
41
|
from .plot import plot_iq_waterfall, plot_mpx_spectrum
|
|
41
42
|
from .rds_groups import StationInfo, encode_group_4a, encode_ps_groups
|
|
42
43
|
from .recon import (
|
|
@@ -792,35 +793,55 @@ def _recon_child_argv(args: argparse.Namespace) -> list[str]:
|
|
|
792
793
|
def _recon_tui(args: argparse.Namespace) -> int:
|
|
793
794
|
"""Run the terminal dashboard, which reads a ``recon --jsonl`` child process."""
|
|
794
795
|
if args.from_dir:
|
|
795
|
-
print(
|
|
796
|
+
print(
|
|
797
|
+
translate(
|
|
798
|
+
args.lang, "error: {flag} applies to live reception, not --from-dir", flag="--tui"
|
|
799
|
+
),
|
|
800
|
+
file=sys.stderr,
|
|
801
|
+
)
|
|
796
802
|
return 2
|
|
797
803
|
if importlib.util.find_spec("textual") is None:
|
|
798
804
|
print(
|
|
799
|
-
|
|
800
|
-
|
|
805
|
+
translate(
|
|
806
|
+
args.lang,
|
|
807
|
+
"error: the dashboard requires the optional 'textual' dependency; "
|
|
808
|
+
"install with: pip install 'rdsclock[tui]'",
|
|
809
|
+
),
|
|
801
810
|
file=sys.stderr,
|
|
802
811
|
)
|
|
803
812
|
return 2
|
|
804
813
|
from .tui import run_dashboard
|
|
805
814
|
|
|
806
|
-
return run_dashboard(
|
|
815
|
+
return run_dashboard(
|
|
816
|
+
[sys.executable, "-m", "rdsclock", *_recon_child_argv(args)], language=args.lang
|
|
817
|
+
)
|
|
807
818
|
|
|
808
819
|
|
|
809
820
|
def _recon_web(args: argparse.Namespace) -> int:
|
|
810
821
|
"""Serve the browser dashboard, which reads a ``recon --jsonl`` child process."""
|
|
811
822
|
if args.from_dir:
|
|
812
|
-
print(
|
|
823
|
+
print(
|
|
824
|
+
translate(
|
|
825
|
+
args.lang, "error: {flag} applies to live reception, not --from-dir", flag="--web"
|
|
826
|
+
),
|
|
827
|
+
file=sys.stderr,
|
|
828
|
+
)
|
|
813
829
|
return 2
|
|
814
830
|
from .web import run_web
|
|
815
831
|
|
|
816
832
|
command = [sys.executable, "-m", "rdsclock", *_recon_child_argv(args)]
|
|
817
|
-
return run_web(command, args.web_host, args.web_port)
|
|
833
|
+
return run_web(command, args.web_host, args.web_port, language=args.lang)
|
|
818
834
|
|
|
819
835
|
|
|
820
836
|
def _recon_gui(args: argparse.Namespace) -> int:
|
|
821
837
|
"""Open the desktop window, which reads a ``recon --jsonl`` child process."""
|
|
822
838
|
if args.from_dir:
|
|
823
|
-
print(
|
|
839
|
+
print(
|
|
840
|
+
translate(
|
|
841
|
+
args.lang, "error: {flag} applies to live reception, not --from-dir", flag="--gui"
|
|
842
|
+
),
|
|
843
|
+
file=sys.stderr,
|
|
844
|
+
)
|
|
824
845
|
return 2
|
|
825
846
|
# Import rather than look the package up: tkinter can be present without
|
|
826
847
|
# its native _tkinter extension or the Tcl/Tk libraries it loads.
|
|
@@ -828,12 +849,16 @@ def _recon_gui(args: argparse.Namespace) -> int:
|
|
|
828
849
|
from .gui import run_gui
|
|
829
850
|
except ImportError as exc:
|
|
830
851
|
print(
|
|
831
|
-
|
|
832
|
-
|
|
852
|
+
translate(
|
|
853
|
+
args.lang,
|
|
854
|
+
"error: the desktop window requires tkinter ({error}); install your "
|
|
855
|
+
"Python's Tk support (for example: apt install python3-tk)",
|
|
856
|
+
error=exc,
|
|
857
|
+
),
|
|
833
858
|
file=sys.stderr,
|
|
834
859
|
)
|
|
835
860
|
return 2
|
|
836
|
-
return run_gui([sys.executable, "-m", "rdsclock", *_recon_child_argv(args)])
|
|
861
|
+
return run_gui([sys.executable, "-m", "rdsclock", *_recon_child_argv(args)], language=args.lang)
|
|
837
862
|
|
|
838
863
|
|
|
839
864
|
def _recon_config(args: argparse.Namespace) -> ReconConfig:
|
|
@@ -1174,6 +1199,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1174
1199
|
action="store_true",
|
|
1175
1200
|
help="Open a desktop window (tkinter) fed by a --jsonl child",
|
|
1176
1201
|
)
|
|
1202
|
+
pr.add_argument(
|
|
1203
|
+
"--lang",
|
|
1204
|
+
choices=SUPPORTED_LANGUAGES,
|
|
1205
|
+
default="en",
|
|
1206
|
+
help="Language of the terminal, browser or desktop dashboard (default: en)",
|
|
1207
|
+
)
|
|
1177
1208
|
pr.add_argument(
|
|
1178
1209
|
"--web-host",
|
|
1179
1210
|
default="127.0.0.1",
|