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.
Files changed (68) hide show
  1. {rdsclock-1.1.0 → rdsclock-2.0.0}/PKG-INFO +32 -12
  2. {rdsclock-1.1.0 → rdsclock-2.0.0}/README.md +31 -11
  3. {rdsclock-1.1.0 → rdsclock-2.0.0}/pyproject.toml +1 -1
  4. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/__init__.py +1 -1
  5. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/cli.py +41 -10
  6. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/dashboard.py +133 -49
  7. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/gui.py +55 -30
  8. rdsclock-2.0.0/src/rdsclock/i18n.py +276 -0
  9. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/tui.py +46 -24
  10. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/web.py +68 -12
  11. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/webui/app.js +10 -8
  12. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/PKG-INFO +32 -12
  13. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/SOURCES.txt +3 -0
  14. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_dashboard.py +20 -0
  15. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_gui.py +77 -2
  16. rdsclock-2.0.0/tests/test_i18n.py +178 -0
  17. rdsclock-2.0.0/tests/test_release.py +196 -0
  18. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_tui.py +46 -4
  19. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_web.py +34 -1
  20. {rdsclock-1.1.0 → rdsclock-2.0.0}/LICENSE +0 -0
  21. {rdsclock-1.1.0 → rdsclock-2.0.0}/setup.cfg +0 -0
  22. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/__main__.py +0 -0
  23. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/audio.py +0 -0
  24. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/channelizer.py +0 -0
  25. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/decoder.py +0 -0
  26. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/dsp.py +0 -0
  27. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/plot.py +0 -0
  28. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/py.typed +0 -0
  29. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/rds_blocks.py +0 -0
  30. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/rds_clock.py +0 -0
  31. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/rds_groups.py +0 -0
  32. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/recon.py +0 -0
  33. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/rtl_tcp.py +0 -0
  34. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/synth.py +0 -0
  35. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/time_consensus.py +0 -0
  36. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/webui/app.css +0 -0
  37. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock/webui/index.html +0 -0
  38. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/dependency_links.txt +0 -0
  39. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/entry_points.txt +0 -0
  40. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/requires.txt +0 -0
  41. {rdsclock-1.1.0 → rdsclock-2.0.0}/src/rdsclock.egg-info/top_level.txt +0 -0
  42. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_audio.py +0 -0
  43. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_benchmark.py +0 -0
  44. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_channelizer.py +0 -0
  45. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_cli.py +0 -0
  46. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_cli_with_fake_sdr.py +0 -0
  47. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_coverage_edges.py +0 -0
  48. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_coverage_extra.py +0 -0
  49. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_decoder_synthetic.py +0 -0
  50. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_dsp.py +0 -0
  51. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_measure_settle.py +0 -0
  52. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_pipeline_delay.py +0 -0
  53. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_plot.py +0 -0
  54. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_public_api.py +0 -0
  55. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_rds_blocks.py +0 -0
  56. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_rds_clock.py +0 -0
  57. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_rds_groups.py +0 -0
  58. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_real_iq_regression.py +0 -0
  59. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_real_recordings.py +0 -0
  60. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon.py +0 -0
  61. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon_events.py +0 -0
  62. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon_hop.py +0 -0
  63. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon_offline.py +0 -0
  64. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_recon_wide.py +0 -0
  65. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_rtl_tcp.py +0 -0
  66. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_synth.py +0 -0
  67. {rdsclock-1.1.0 → rdsclock-2.0.0}/tests/test_time_consensus.py +0 -0
  68. {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: 1.1.0
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
- ![Terminal dashboard during live WIDE reception: trust MEDIUM from two stations in one capture, both with Clock-Time](docs/images/dashboard-wide-tui.png)
265
+ ![Terminal dashboard during live WIDE reception: trust MEDIUM from two stations in one capture, both with Clock-Time](docs/images/dashboard-wide-tui-en.png)
246
266
 
247
267
  **Browser** (`--web`)
248
268
 
249
- ![Browser dashboard during live WIDE reception of RMF Classic and PR Trójka, with Rescan and Pause view buttons](docs/images/dashboard-wide-web.png)
269
+ ![Browser dashboard during live WIDE reception of RMF Classic and PR Trójka, with Rescan and Pause view buttons](docs/images/dashboard-wide-web-en.png)
250
270
 
251
271
  **Desktop window** (`--gui`)
252
272
 
253
- ![Desktop window during live WIDE reception of RMF Classic and PR Trójka, with Rescan, Pause view and Quit buttons](docs/images/dashboard-wide-gui.png)
273
+ ![Desktop window during live WIDE reception of RMF Classic and PR Trójka, with Rescan, Pause view and Quit buttons](docs/images/dashboard-wide-gui-en.png)
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, which
264
- is why the window below shows MEDIUM:
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
- ![Terminal dashboard during live HOP reception: trust HIGH from three stations, each with Clock-Time](docs/images/dashboard-hop-tui.png)
294
+ ![Terminal dashboard during live HOP reception: trust HIGH from three stations, each with Clock-Time](docs/images/dashboard-hop-tui-en.png)
275
295
 
276
296
  **Browser** (`--web`)
277
297
 
278
- ![Browser dashboard during live HOP reception of Radio ZET, PR Jedynka and PR Trójka](docs/images/dashboard-hop-web.png)
298
+ ![Browser dashboard during live HOP reception of Radio ZET, PR Jedynka and PR Trójka](docs/images/dashboard-hop-web-en.png)
279
299
 
280
300
  **Desktop window** (`--gui`)
281
301
 
282
- ![Desktop window during live HOP reception of Radio ZET, PR Jedynka and PR Trójka](docs/images/dashboard-hop-gui.png)
302
+ ![Desktop window during live HOP reception of Radio ZET, PR Jedynka and PR Trójka](docs/images/dashboard-hop-gui-en.png)
283
303
 
284
- *Live reception in Warsaw on 3 and 4 November 2026. These stations send Polish
285
- local time in the UTC field, which is why the consensus runs an hour ahead of
286
- the host clock.*
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
- ![Terminal dashboard during live WIDE reception: trust MEDIUM from two stations in one capture, both with Clock-Time](docs/images/dashboard-wide-tui.png)
225
+ ![Terminal dashboard during live WIDE reception: trust MEDIUM from two stations in one capture, both with Clock-Time](docs/images/dashboard-wide-tui-en.png)
206
226
 
207
227
  **Browser** (`--web`)
208
228
 
209
- ![Browser dashboard during live WIDE reception of RMF Classic and PR Trójka, with Rescan and Pause view buttons](docs/images/dashboard-wide-web.png)
229
+ ![Browser dashboard during live WIDE reception of RMF Classic and PR Trójka, with Rescan and Pause view buttons](docs/images/dashboard-wide-web-en.png)
210
230
 
211
231
  **Desktop window** (`--gui`)
212
232
 
213
- ![Desktop window during live WIDE reception of RMF Classic and PR Trójka, with Rescan, Pause view and Quit buttons](docs/images/dashboard-wide-gui.png)
233
+ ![Desktop window during live WIDE reception of RMF Classic and PR Trójka, with Rescan, Pause view and Quit buttons](docs/images/dashboard-wide-gui-en.png)
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, which
224
- is why the window below shows MEDIUM:
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
- ![Terminal dashboard during live HOP reception: trust HIGH from three stations, each with Clock-Time](docs/images/dashboard-hop-tui.png)
254
+ ![Terminal dashboard during live HOP reception: trust HIGH from three stations, each with Clock-Time](docs/images/dashboard-hop-tui-en.png)
235
255
 
236
256
  **Browser** (`--web`)
237
257
 
238
- ![Browser dashboard during live HOP reception of Radio ZET, PR Jedynka and PR Trójka](docs/images/dashboard-hop-web.png)
258
+ ![Browser dashboard during live HOP reception of Radio ZET, PR Jedynka and PR Trójka](docs/images/dashboard-hop-web-en.png)
239
259
 
240
260
  **Desktop window** (`--gui`)
241
261
 
242
- ![Desktop window during live HOP reception of Radio ZET, PR Jedynka and PR Trójka](docs/images/dashboard-hop-gui.png)
262
+ ![Desktop window during live HOP reception of Radio ZET, PR Jedynka and PR Trójka](docs/images/dashboard-hop-gui-en.png)
243
263
 
244
- *Live reception in Warsaw on 3 and 4 November 2026. These stations send Polish
245
- local time in the UTC field, which is why the consensus runs an hour ahead of
246
- the host clock.*
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 = "1.1.0"
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"
@@ -28,7 +28,7 @@ from .rds_clock import ClockTime
28
28
  from .rds_groups import StationInfo
29
29
  from .time_consensus import SubSecondEstimate, TimeConsensus
30
30
 
31
- __version__ = "1.1.0"
31
+ __version__ = "2.0.0"
32
32
 
33
33
  __all__ = [
34
34
  "__version__",
@@ -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("error: --tui applies to live reception, not --from-dir", file=sys.stderr)
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
- "error: the dashboard requires the optional 'textual' dependency; "
800
- "install with: pip install 'rdsclock[tui]'",
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([sys.executable, "-m", "rdsclock", *_recon_child_argv(args)])
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("error: --web applies to live reception, not --from-dir", file=sys.stderr)
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("error: --gui applies to live reception, not --from-dir", file=sys.stderr)
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
- f"error: the desktop window requires tkinter ({exc}); install your "
832
- "Python's Tk support (for example: apt install python3-tk)",
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",