ndscope 0.22.0__tar.gz → 0.22.2__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 (123) hide show
  1. {ndscope-0.22.0 → ndscope-0.22.2}/CHANGELOG.md +32 -0
  2. {ndscope-0.22.0 → ndscope-0.22.2}/PKG-INFO +4 -2
  3. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/__main__.py +47 -26
  4. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/__version__.py +3 -3
  5. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/cache.py +62 -105
  6. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/channel_select.py +12 -6
  7. ndscope-0.22.2/ndscope/configuration/__init__.py +0 -0
  8. ndscope-0.22.2/ndscope/configuration/configuration.py +283 -0
  9. ndscope-0.22.2/ndscope/data_source_determination.py +60 -0
  10. ndscope-0.22.2/ndscope/markers.py +360 -0
  11. ndscope-0.22.2/ndscope/messages/__init__.py +0 -0
  12. ndscope-0.22.2/ndscope/messages/dtt_messages.py +41 -0
  13. ndscope-0.22.2/ndscope/messages/has_issues.py +106 -0
  14. ndscope-0.22.2/ndscope/messages/message_windows/__init__.py +0 -0
  15. ndscope-0.22.2/ndscope/messages/message_windows/log_list.py +46 -0
  16. ndscope-0.22.2/ndscope/messages/message_windows/message_window.py +42 -0
  17. ndscope-0.22.2/ndscope/messages/messages.py +207 -0
  18. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/plot.py +18 -11
  19. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/plotMenu.py +39 -0
  20. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/scope.py +277 -89
  21. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/scope.ui +92 -0
  22. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/test_util.py +22 -13
  23. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/util.py +0 -23
  24. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope.egg-info/PKG-INFO +4 -2
  25. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope.egg-info/SOURCES.txt +11 -0
  26. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope.egg-info/requires.txt +3 -1
  27. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope.egg-info/scm_file_list.json +11 -0
  28. ndscope-0.22.2/ndscope.egg-info/scm_version.json +8 -0
  29. {ndscope-0.22.0 → ndscope-0.22.2}/pyproject.toml +3 -1
  30. ndscope-0.22.0/ndscope.egg-info/scm_version.json +0 -8
  31. {ndscope-0.22.0 → ndscope-0.22.2}/COPYING +0 -0
  32. {ndscope-0.22.0 → ndscope-0.22.2}/COPYING-GPL-3 +0 -0
  33. {ndscope-0.22.0 → ndscope-0.22.2}/MANIFEST.in +0 -0
  34. {ndscope-0.22.0 → ndscope-0.22.2}/NEWS.md +0 -0
  35. {ndscope-0.22.0 → ndscope-0.22.2}/README.md +0 -0
  36. {ndscope-0.22.0 → ndscope-0.22.2}/RELEASE.md +0 -0
  37. {ndscope-0.22.0 → ndscope-0.22.2}/bad.sh +0 -0
  38. {ndscope-0.22.0 → ndscope-0.22.2}/bad2.yaml +0 -0
  39. {ndscope-0.22.0 → ndscope-0.22.2}/bad_legend.yaml +0 -0
  40. {ndscope-0.22.0 → ndscope-0.22.2}/bad_ndscope.yaml +0 -0
  41. {ndscope-0.22.0 → ndscope-0.22.2}/bad_times.yaml +0 -0
  42. {ndscope-0.22.0 → ndscope-0.22.2}/big.yaml +0 -0
  43. {ndscope-0.22.0 → ndscope-0.22.2}/biglive.yaml +0 -0
  44. {ndscope-0.22.0 → ndscope-0.22.2}/calib.sh +0 -0
  45. {ndscope-0.22.0 → ndscope-0.22.2}/calib.yaml +0 -0
  46. {ndscope-0.22.0 → ndscope-0.22.2}/calib_arrakis.sh +0 -0
  47. {ndscope-0.22.0 → ndscope-0.22.2}/calib_arrakis.yaml +0 -0
  48. {ndscope-0.22.0 → ndscope-0.22.2}/dev.sh +0 -0
  49. {ndscope-0.22.0 → ndscope-0.22.2}/dts1 +0 -0
  50. {ndscope-0.22.0 → ndscope-0.22.2}/install_wheel +0 -0
  51. {ndscope-0.22.0 → ndscope-0.22.2}/live.sh +0 -0
  52. {ndscope-0.22.0 → ndscope-0.22.2}/live.yaml +0 -0
  53. {ndscope-0.22.0 → ndscope-0.22.2}/manual_tests/manual_test.yaml +0 -0
  54. {ndscope-0.22.0 → ndscope-0.22.2}/manual_tests/manual_test_live.yaml +0 -0
  55. {ndscope-0.22.0 → ndscope-0.22.2}/manual_tests/ndscope-test.txt +0 -0
  56. {ndscope-0.22.0 → ndscope-0.22.2}/manual_tests/old_manual_test.yaml +0 -0
  57. {ndscope-0.22.0 → ndscope-0.22.2}/manual_tests/test_results_2025_10_23.txt +0 -0
  58. {ndscope-0.22.0 → ndscope-0.22.2}/maturin.sh +0 -0
  59. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/__init__.py +0 -0
  60. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/_qt.py +0 -0
  61. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/axis.py +0 -0
  62. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/axisCtrlTemplate.ui +0 -0
  63. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/base_plot.py +0 -0
  64. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/channel_config.ui +0 -0
  65. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/channel_config_dialog.ui +0 -0
  66. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/channel_list.ui +0 -0
  67. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/channel_select_dialog.ui +0 -0
  68. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/const.py +0 -0
  69. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/crosshair.py +0 -0
  70. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/cursors.py +0 -0
  71. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/data.py +0 -0
  72. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/dialog.py +0 -0
  73. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/dialog_nds_auth.ui +0 -0
  74. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/dialog_nds_ontape.ui +0 -0
  75. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/dtt/__init__.py +0 -0
  76. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/dtt/channel_trend_id.py +0 -0
  77. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/dtt/dtt.py +0 -0
  78. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/exceptions.py +0 -0
  79. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/export.py +0 -0
  80. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/kerberos.py +0 -0
  81. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/legend.py +0 -0
  82. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/math_config.py +0 -0
  83. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/math_config.ui +0 -0
  84. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/nds.py +0 -0
  85. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/primary_view.py +0 -0
  86. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/result.py +0 -0
  87. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/ruff.toml +0 -0
  88. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/scatterPlotMenu.py +0 -0
  89. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/scatter_plot.py +0 -0
  90. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/template.py +0 -0
  91. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/__init__.py +0 -0
  92. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test.yaml +0 -0
  93. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test0.yaml +0 -0
  94. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test1-1.yaml +0 -0
  95. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test1-2.yaml +0 -0
  96. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test1-3.yaml +0 -0
  97. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test1.yaml +0 -0
  98. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test2.stp +0 -0
  99. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test2.yaml +0 -0
  100. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test3.txt +0 -0
  101. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test3.yaml +0 -0
  102. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test4.xml +0 -0
  103. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/templates/test4.yaml +0 -0
  104. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/test_cache.py +0 -0
  105. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/test_cli.py +0 -0
  106. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/test_data_flow.py +0 -0
  107. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/test_layout.py +0 -0
  108. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/test_plot.py +0 -0
  109. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/test/test_window.py +0 -0
  110. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope/trigger.py +0 -0
  111. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope.egg-info/dependency_links.txt +0 -0
  112. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope.egg-info/entry_points.txt +0 -0
  113. {ndscope-0.22.0 → ndscope-0.22.2}/ndscope.egg-info/top_level.txt +0 -0
  114. {ndscope-0.22.0 → ndscope-0.22.2}/newdaq.sh +0 -0
  115. {ndscope-0.22.0 → ndscope-0.22.2}/past.sh +0 -0
  116. {ndscope-0.22.0 → ndscope-0.22.2}/past.yaml +0 -0
  117. {ndscope-0.22.0 → ndscope-0.22.2}/past2.yaml +0 -0
  118. {ndscope-0.22.0 → ndscope-0.22.2}/pidcheck.py +0 -0
  119. {ndscope-0.22.0 → ndscope-0.22.2}/setup.cfg +0 -0
  120. {ndscope-0.22.0 → ndscope-0.22.2}/teststand_arrakis.sh +0 -0
  121. {ndscope-0.22.0 → ndscope-0.22.2}/teststand_arrakis.yaml +0 -0
  122. {ndscope-0.22.0 → ndscope-0.22.2}/trend.sh +0 -0
  123. {ndscope-0.22.0 → ndscope-0.22.2}/trend.yaml +0 -0
@@ -1,4 +1,36 @@
1
1
  # Changelog
2
+
3
+ ## NEXT
4
+
5
+ - New "latched" user message system
6
+ - Improved error messages
7
+ - Gaps are more visible
8
+ - Fixed bug where Y axis label could overlap tick marks
9
+ - Fixed bug where --single-shot data exports, e.g. hdf5, would never complete
10
+ - Added system to place markers on plots, then jump between markers.
11
+ - Fix bug where arrakis data could be timestamped wrong
12
+ - Add --arrakis (choose default Arrakis data source) and --arrakis-server (specify an arrakis server) options.
13
+
14
+ ## 0.22.0 - 2026-08-24
15
+
16
+ - Add buttons to enable/disable Y cursors on all plots
17
+ - Y axis autoscale preserved during time-axis-only actions
18
+ - Use shift <mouse gesture> to constrain a mouse action to the time axis
19
+ - Use ctrl <mouse gesture> to constrain a mouse action to the Y axis
20
+ - Fixed downsampling so that zooming in on fast channels will show real resolution
21
+ - Fixed exception when cancelling a Kerberos login
22
+ - Allow full precision when entering scale and offsets for a channel
23
+ - Set curved width now used when adding new plots
24
+ - Use --replay-id and other new options to access Arrakis replay feature
25
+ - LIGO_DATA_URL environment variable dropped.
26
+ - NDSSERVER environment variable takes precedence over ARRAKIS_SERVER
27
+
28
+ ## 0.21.0 - 2026-03-12
29
+
30
+ - Updated color scheme to pick colors with more contrast
31
+ - Add scatter plot
32
+ - Improved HDF5 export
33
+
2
34
  ## [0.20.7] - 2026-01-29
3
35
  (changes since 0.19)
4
36
  - Interface
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ndscope
3
- Version: 0.22.0
3
+ Version: 0.22.2
4
4
  Summary: Next-generation NDS time series plotting
5
5
  Author-email: Jameson Graef Rollins <jameson.rollins@ligo.org>, Patrick Thomas <patrick.thomas@ligo.org>, Erik von Reis <erik.vonreis@ligo.org>
6
6
  Maintainer-email: Jameson Graef Rollins <jameson.rollins@ligo.org>, Patrick Thomas <patrick.thomas@ligo.org>, Erik von Reis <erik.vonreis@ligo.org>
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
18
18
  License-File: COPYING
19
19
  License-File: COPYING-GPL-3
20
20
  Requires-Dist: cairosvg
21
- Requires-Dist: dttlib>=0.11.0
21
+ Requires-Dist: dttlib>=0.11.3
22
22
  Requires-Dist: gpstime
23
23
  Requires-Dist: h5py
24
24
  Requires-Dist: matplotlib
@@ -32,6 +32,8 @@ Requires-Dist: rapidfuzz
32
32
  Requires-Dist: scipy
33
33
  Requires-Dist: setproctitle
34
34
  Requires-Dist: setuptools_scm
35
+ Requires-Dist: tomlkit
36
+ Requires-Dist: platformdirs
35
37
  Dynamic: license-file
36
38
 
37
39
  # Next-generation NDS oscilloscope
@@ -1,14 +1,24 @@
1
1
  from __future__ import division, unicode_literals
2
- from argparse import Namespace
3
- import os
4
- import sys
5
- import signal
6
- import logging
2
+
7
3
  import argparse
8
4
  import asyncio
5
+ import logging
6
+ import os
7
+ import signal
8
+ import sys
9
+ from argparse import Namespace
10
+ from typing import Any, Dict
11
+
9
12
  import pyqtgraph as pg
13
+ from dttlib import PipInstant, Replay
14
+
10
15
  from .cache import DataCache
11
- from dttlib import Replay, PipInstant
16
+ from .configuration.configuration import load_config, get_config
17
+ from .data_source_determination import (
18
+ add_data_source_args,
19
+ parse_data_source_args,
20
+ get_first_data_source,
21
+ )
12
22
 
13
23
  try:
14
24
  import yaml
@@ -22,16 +32,13 @@ except ImportError:
22
32
  pass
23
33
 
24
34
 
25
- from gpstime import gpstime, GPSTimeException
35
+ from gpstime import GPSTimeException, gpstime
26
36
 
27
- from . import __version__
28
- from . import const
37
+ from . import __version__, const
29
38
  from . import template as _template
30
- from .util import resolve_data_source
31
39
  from ._qt import create_app
32
- from .scope import NDScope
33
40
  from .exceptions import UnknownChannelError
34
-
41
+ from .scope import NDScope
35
42
 
36
43
  logging.addLevelName(5, "DATA")
37
44
  logging.basicConfig(
@@ -41,7 +48,6 @@ logging.basicConfig(
41
48
 
42
49
  logger = logging.getLogger("MAIN")
43
50
 
44
-
45
51
  ##################################################
46
52
 
47
53
 
@@ -146,16 +152,19 @@ specifying the file as argument:
146
152
 
147
153
  ndscope foobar.hdf5
148
154
 
149
- The data source url can be provided in the ARRAKIS_SERVER environment
150
- variable. The URL should in the form grpc://server:port for arrakis servers
151
- or nds://server:port for nds servers. If NDSSERVER is defined, it will
152
- override ARRAKIS_SERVER, then NDSSERVER=server:port will be used
155
+ The data source url can be provided in the ARRAKIS_SERVER environment
156
+ variable. The URL should in the form grpc://server:port for arrakis servers
157
+ or nds://server:port for nds servers. If NDSSERVER is defined, it will
158
+ override ARRAKIS_SERVER, then NDSSERVER=server:port will be used
153
159
  (NDS servers only).
154
160
 
155
- The data source can be set with the --url option for either
161
+ Use the --arrakis option to use the ARRAKIS_SERVER environment
162
+ variable instead of the NDSSERVER variable.
163
+
164
+ The data source can be set with the --arrakis-server or --url option for either
156
165
  Arrakis servers or nds servers
157
166
 
158
- ndscope --url=grpc://ngdd.ligo-la.caltech.edu:31206 L1:FOO
167
+ ndscope --arrakis-server=grpc://ngdd.ligo-la.caltech.edu:31206 L1:FOO
159
168
 
160
169
  ndscope --url=nds://nds.ligo-la.caltech.edu:31200 L1:FOO
161
170
 
@@ -212,8 +221,7 @@ parser.add_argument(
212
221
  dest="time_window",
213
222
  help="time window scalar or tuple, in seconds",
214
223
  )
215
- tgroup = parser.add_mutually_exclusive_group()
216
- tgroup.add_argument(
224
+ parser.add_argument(
217
225
  "--trend",
218
226
  choices=["auto", "raw", "sec", "min"],
219
227
  help="specify trend type, 'auto' auto-selects best trend for time span [default]",
@@ -317,10 +325,9 @@ parser.add_argument(
317
325
  help="specify initial window size",
318
326
  )
319
327
  parser.add_argument("--expand", action="store_true", help="expand control bar")
320
- parser.add_argument("--url", metavar="URL:PORT", help=f"Data source URL")
321
- parser.add_argument(
322
- "--nds", metavar="HOST[:PORT]", help=f"NDS server [{const.NDSSERVER}]"
323
- )
328
+
329
+ add_data_source_args(parser)
330
+
324
331
  parser.add_argument(
325
332
  "--gen-template",
326
333
  action="store_true",
@@ -378,6 +385,10 @@ def main():
378
385
  signal.signal(signal.SIGINT, signal.SIG_DFL)
379
386
  setproctitle(" ".join([PROG] + sys.argv[1:]))
380
387
 
388
+ # these are configuration values determined by arguments
389
+ # to be passed to the load_config function
390
+ args_conf = {}
391
+
381
392
  args = parser.parse_args()
382
393
 
383
394
  if args.usage:
@@ -489,6 +500,15 @@ def main():
489
500
  template["t0"] = t0
490
501
  template["time-window"] = window
491
502
 
503
+ ##########
504
+ # Parse data sources
505
+ args_conf.update(parse_data_source_args(args))
506
+
507
+ ##########
508
+ # args fully parsed. Load configuration
509
+
510
+ load_config(args_conf)
511
+
492
512
  ##########
493
513
  # launch app
494
514
 
@@ -496,7 +516,8 @@ def main():
496
516
  print(yaml.safe_dump(template, default_style=False))
497
517
  sys.exit()
498
518
 
499
- url_text = resolve_data_source(args.nds, args.url)
519
+ # only the first data source for now
520
+ url_text = get_first_data_source()
500
521
  logger.debug(f"url = {url_text}")
501
522
 
502
523
  # create cache here so we can start running channel searches
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.22.0'
22
- __version_tuple__ = version_tuple = (0, 22, 0)
21
+ __version__ = version = '0.22.2'
22
+ __version_tuple__ = version_tuple = (0, 22, 2)
23
23
 
24
- __commit_id__ = commit_id = 'ga7ac0d3a6'
24
+ __commit_id__ = commit_id = 'g86e3ad200'
@@ -1,22 +1,22 @@
1
1
  from enum import Enum
2
2
  import logging
3
- import collections
4
- import traceback
5
3
  import dttlib
6
4
  from threading import Lock
7
5
  import asyncio
8
6
 
9
7
  from qtpy import QtCore
10
- from qtpy.QtCore import Signal, Slot, QTimer # type: ignore
8
+ from qtpy.QtCore import Signal, Slot, QTimer
11
9
 
12
10
  from rapidfuzz import process, fuzz
13
11
 
14
- from .dtt.channel_trend_id import ChannelTrendId # type: ignore
12
+ from .dtt.channel_trend_id import ChannelTrendId
15
13
 
16
14
  from . import nds
17
15
  from .data import DataBuffer, DataBufferDict
18
16
  from .exceptions import UnknownChannelError
19
17
  from .dtt import DTT, AnalysisResult
18
+ from .messages.dtt_messages import handle_dtt_messages
19
+ from .messages.messages import set_error, clear_message
20
20
 
21
21
  from typing import TYPE_CHECKING, Set, Union, Optional, Tuple, Dict, List
22
22
 
@@ -47,6 +47,9 @@ class AtomicViewId(object):
47
47
  return old_val
48
48
 
49
49
 
50
+ primed_cache_logger = logging.getLogger("PRIMEDCACHE")
51
+
52
+
50
53
  class PrimedCache(object):
51
54
  """Use with PrimedCache(DataCache):
52
55
  to ensure a DataCache is primed at the end of a block.
@@ -69,18 +72,30 @@ class CacheReadyState(Enum):
69
72
 
70
73
 
71
74
  class CacheReady(QtCore.QObject):
72
- """A statemachine that attempts to meter data flow into the Qt main thread"""
75
+ """
76
+ A statemachine that attempts to meter data flow into the Qt main thread
77
+ """
73
78
 
74
79
  def __init__(self, delay_ms: int, cache: "DataCache"):
75
80
  """
76
- param delay_sec: delay to introduce between re-priming and actually priing the cache to send data
81
+ param delay_sec: delay to introduce between re-priming and actually
82
+ firing the cache to send data
77
83
  """
78
84
  self.delay_ms = delay_ms
79
85
  self.cache = cache
80
- self.state = CacheReadyState.Primed
86
+ self._state = CacheReadyState.Primed
81
87
  self.state_lock = Lock()
82
88
  self.commands: List[str] = []
83
89
 
90
+ @property
91
+ def state(self):
92
+ return self._state
93
+
94
+ @state.setter
95
+ def state(self, new_state: CacheReadyState):
96
+ primed_cache_logger.debug("Transitioning from %s to %s", self.state, new_state)
97
+ self._state = new_state
98
+
84
99
  def _add_command(self, command: str):
85
100
  if len(self.commands) == 0:
86
101
  self.commands = [command]
@@ -145,6 +160,7 @@ class CacheReady(QtCore.QObject):
145
160
  """
146
161
  if len(self.commands) > 0:
147
162
  c = self.commands.pop()
163
+ logger.debug("CacheReady firing")
148
164
  self.cache.signal_data.emit(c)
149
165
 
150
166
  def _start_delay(self):
@@ -189,7 +205,9 @@ class ViewParams:
189
205
  self.end,
190
206
  )
191
207
 
192
- def __eq__(self, other: "ViewParams") -> bool:
208
+ def __eq__(self, other: object) -> bool:
209
+ if not isinstance(other, ViewParams):
210
+ return False
193
211
  return (
194
212
  (not self.force)
195
213
  and self.online == other.online
@@ -248,11 +266,15 @@ class DataCacheContext(object):
248
266
  raise ValueError("cache context exited more times than it entered.")
249
267
  if self.cache.next_view_params == self.cache.prev_view_params:
250
268
  return
251
- if self.cache.next_view_params.needs_new_view(self.cache.prev_view_params):
252
- self.cache._new_scope_view()
253
- else:
254
- self.cache._set_scope_view()
255
- self.cache.signal_channel_change.emit(None)
269
+ try:
270
+ if self.cache.next_view_params.needs_new_view(self.cache.prev_view_params):
271
+ self.cache._new_scope_view()
272
+ else:
273
+ self.cache._set_scope_view()
274
+ self.cache.signal_channel_change.emit(None)
275
+ clear_message("cache_context")
276
+ except UnknownChannelError as e:
277
+ set_error("cache_context", str(e))
256
278
 
257
279
  def __getattr__(self, name):
258
280
  return self.cache.__getattribute__(name)
@@ -281,12 +303,6 @@ class DataCache(QtCore.QObject):
281
303
  # payload a TrendType
282
304
  signal_trend_change = Signal("PyQt_PyObject")
283
305
 
284
- # status and error message from cache data stream
285
- # payload is a single string
286
- signal_stream_status = Signal("PyQt_PyObject")
287
- signal_online_status = Signal("PyQt_PyObject")
288
- signal_stream_error = Signal("PyQt_PyObject")
289
-
290
306
  # data buffer in response to data request
291
307
  # payload: tuple of
292
308
  # trend type string
@@ -300,6 +316,7 @@ class DataCache(QtCore.QObject):
300
316
 
301
317
  def __init__(self, scope: "NDScope", url_text: str, replay: dttlib.Replay) -> None:
302
318
  super().__init__()
319
+
303
320
  # needed to get the channel list
304
321
  self.scope = scope
305
322
  self.next_view_params = ViewParams(
@@ -337,7 +354,7 @@ class DataCache(QtCore.QObject):
337
354
  url_text=url_text,
338
355
  replay=replay,
339
356
  scope_data_handler=self.handle_scope_result,
340
- message_handler=self._handle_messages,
357
+ message_handler=self._handle_dtt_messages,
341
358
  scope_view_done_handler=self._handle_view_done,
342
359
  )
343
360
 
@@ -384,7 +401,6 @@ class DataCache(QtCore.QObject):
384
401
  # f"{k}: start = {d.gps_start} len = {len(d.data)} rate = {d.channel.rate_hz}"
385
402
  # )
386
403
 
387
- fire = False
388
404
  with self.data_lock:
389
405
  self.data_buffers.add_buffer(new_buffer)
390
406
  self.saved_data.add_buffer(new_buffer)
@@ -581,10 +597,8 @@ class DataCache(QtCore.QObject):
581
597
  )
582
598
 
583
599
  if self.next_view_params.online:
584
- msg = f"Retrieving {self._command_description('online', self.next_view_params.trend)}..."
585
600
  self.view = self.dtt.new_online_scope_view(view_set, span_pip)
586
601
  else:
587
- msg = f"Retrieving {self._command_description('', self.next_view_params.trend)}..."
588
602
  if self.next_view_params.snapshot:
589
603
  self.view = self.dtt.new_snapshot_scope_view(
590
604
  view_set, start_pip, end_pip
@@ -734,22 +748,6 @@ class DataCache(QtCore.QObject):
734
748
 
735
749
  ##########
736
750
 
737
- def _command_description(self, cmd, trend: dttlib.TrendType):
738
- if cmd == "online":
739
- desc = "online "
740
- else:
741
- desc = ""
742
- if trend == "sec":
743
- desc += "second trend data"
744
- elif trend == "min":
745
- desc += "minute trend data"
746
- elif trend == "raw":
747
- desc += "raw data"
748
- return desc
749
-
750
- def remote_cmd(self, cmd, **kwargs):
751
- raise Exception("no longer used")
752
-
753
751
  def remote_recv_channels(self, channels: Union[str, Dict[str, "dttlib.Channel"]]):
754
752
  if isinstance(channels, str):
755
753
  # error message
@@ -760,7 +758,11 @@ class DataCache(QtCore.QObject):
760
758
  self.available_channels = channels
761
759
  self.signal_channel_list_ready.emit()
762
760
  if self.prime_invalid_recheck:
763
- self._post_stream_message("Invalid channel")
761
+ user_message = dttlib.UserMessage(
762
+ dttlib.Severity.Error, "Invalid channel"
763
+ )
764
+ msg_job = dttlib.MessageJob.SetMessage("stream_error", user_message)
765
+ self.handle_invalid_channels(msg_job)
764
766
 
765
767
  @Slot()
766
768
  def remote_done(self):
@@ -784,51 +786,6 @@ class DataCache(QtCore.QObject):
784
786
  await asyncio.gather(*tasks, return_exceptions=True)
785
787
  return set([task.result() for task in tasks if task.result()])
786
788
 
787
- ##################################################################
788
-
789
- def _handle_messages(self, msg: dttlib.MessageJob) -> None:
790
- """
791
- Handle incoming user messages from dttlib
792
- """
793
- logger.debug("msg=%s", str(msg))
794
- if isinstance(msg, dttlib.MessageJob.SetMessage):
795
- self.messages[msg.tag] = msg.msg
796
- elif isinstance(msg, dttlib.MessageJob.ClearMessage):
797
- if msg.tag in self.messages:
798
- del self.messages[msg.tag]
799
- else:
800
- logger.info("Unknown message type %s", msg)
801
- tag = msg.get_tag()
802
- if tag == "stream_status":
803
- self._handle_stream_status()
804
- elif tag == "stream_error" or tag == "REQSIZE":
805
- if (
806
- not isinstance(msg, dttlib.MessageJob.SetMessage)
807
- or msg.msg.severity >= dttlib.Severity.Error
808
- ):
809
- self._handle_stream_error()
810
- elif tag == "online_status":
811
- self._handle_online_status()
812
- elif tag == "find_channels":
813
- if "Request SASL authentication protocol" in str(msg):
814
- self.signal_request_sasl.emit()
815
- else:
816
- logger.info("got unhandled message tag %s", tag)
817
-
818
- def _handle_stream_status(self):
819
- """ """
820
- if "stream_status" in self.messages:
821
- self._set_stream_status(self.messages["stream_status"].message)
822
- else:
823
- self._set_stream_status("")
824
-
825
- def _handle_online_status(self):
826
- """ """
827
- if "online_status" in self.messages:
828
- self._set_online_status(self.messages["online_status"].message)
829
- else:
830
- self._set_online_status("")
831
-
832
789
  def get_most_similar_channel(self, channel: str) -> Optional[str]:
833
790
  """Return the channel in available_channels that's string-wise most similar
834
791
 
@@ -902,25 +859,25 @@ class DataCache(QtCore.QObject):
902
859
  msg = f"'{suspect}' is not valid for {trend}{online}"
903
860
  return msg
904
861
 
905
- def _post_stream_message(self, msg):
906
- self.prime_invalid_recheck = False
907
- msg = self.guess_invalid_channel(msg)
908
- self._set_stream_error(msg)
909
-
910
- def _handle_stream_error(self):
911
- """ """
912
- if "stream_error" in self.messages:
913
- self._post_stream_message(self.messages["stream_error"].message)
914
- elif "REQSIZE" in self.messages:
915
- self._set_stream_error(self.messages["REQSIZE"].message)
916
- else:
917
- self._set_stream_error("")
862
+ def _handle_dtt_messages(self, msg_job: dttlib.MessageJob):
863
+ if msg_job.get_tag() == "stream_error":
864
+ if isinstance(msg_job, dttlib.MessageJob.SetMessage):
865
+ msg_job = self.handle_invalid_channels(msg_job)
866
+ handle_dtt_messages(msg_job)
918
867
 
919
- def _set_stream_status(self, status: str) -> None:
920
- self.signal_stream_status.emit(status)
921
-
922
- def _set_online_status(self, status: str) -> None:
923
- self.signal_online_status.emit(status)
868
+ def handle_invalid_channels(
869
+ self, msg_job: dttlib.MessageJob.SetMessage
870
+ ) -> dttlib.MessageJob:
871
+ """
872
+ re-writes and re-sets an invalid channel message to include a guess at the correct channel
873
+ all other messages are passed through (and reset) unchanged.
924
874
 
925
- def _set_stream_error(self, err: str) -> None:
926
- self.signal_stream_error.emit(err)
875
+ This could potentially lead to a race where a message gets cleared and set very quickly.
876
+ """
877
+ self.prime_invalid_recheck = False
878
+ new_msg = self.guess_invalid_channel(msg_job.msg.message)
879
+ if new_msg != msg_job.msg.message:
880
+ new_msg = dttlib.UserMessage(msg_job.msg.severity, new_msg)
881
+ new_msg_job = dttlib.MessageJob.SetMessage(msg_job.tag, new_msg)
882
+ return new_msg_job
883
+ return msg_job
@@ -13,6 +13,12 @@ from qtpy.QtCore import Signal # type: ignore
13
13
  from ndscope.math_config import MathConfigDialog
14
14
 
15
15
  from ._qt import load_ui
16
+ from .configuration.configuration import load_config
17
+ from .data_source_determination import (
18
+ parse_data_source_args,
19
+ add_data_source_args,
20
+ get_first_data_source,
21
+ )
16
22
  from .plot import NDScopePlotChannel
17
23
 
18
24
  from typing import TYPE_CHECKING, Set, List, Dict
@@ -1415,10 +1421,7 @@ def main():
1415
1421
  description=DESCRIPTION,
1416
1422
  formatter_class=argparse.RawDescriptionHelpFormatter,
1417
1423
  )
1418
- parser.add_argument("--url", metavar="URL:PORT", help=f"Data source URL")
1419
- parser.add_argument(
1420
- "--nds", metavar="HOST[:PORT]", help=f"NDS server [{const.NDSSERVER}]"
1421
- )
1424
+ add_data_source_args(parser)
1422
1425
  parser.add_argument(
1423
1426
  "glob",
1424
1427
  nargs="?",
@@ -1428,7 +1431,10 @@ def main():
1428
1431
 
1429
1432
  args = parser.parse_args()
1430
1433
 
1431
- url_text = util.resolve_data_source(args.nds, args.url)
1434
+ args_conf = parse_data_source_args(args)
1435
+ load_config(args_conf)
1436
+
1437
+ url_text = get_first_data_source()
1432
1438
 
1433
1439
  server, server_formatted = util.format_nds_server_string(url_text)
1434
1440
 
@@ -1447,7 +1453,7 @@ def main():
1447
1453
  logger.debug("done transform")
1448
1454
 
1449
1455
  dtt = dttlib.DTT(chans_callback)
1450
- cache = dttlib.NDS2Cache(1 << 30, "").as_ref()
1456
+ cache = dttlib.NDS2Cache(1 << 30, "", url_text, dttlib.Replay.NoReplay()).as_ref()
1451
1457
  dtt.set_data_source(cache)
1452
1458
  logger.debug(
1453
1459
  f"Fetching channel list from {server} (channel glob: '{args.glob}')... "
File without changes