pyobs-gui 2.0.0.dev16__tar.gz → 2.0.0.dev18__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 (144) hide show
  1. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/PKG-INFO +1 -1
  2. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/acquisitionwidget.py +8 -5
  3. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/autofocuswidget.py +12 -9
  4. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/autoguidingwidget.py +12 -7
  5. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/base.py +28 -14
  6. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/camerawidget.py +14 -18
  7. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/coolingwidget.py +4 -3
  8. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/datadisplaywidget.py +0 -1
  9. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/filterwidget.py +7 -5
  10. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/focuswidget.py +6 -6
  11. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/roofwidget.py +12 -7
  12. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/spectrographwidget.py +8 -5
  13. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/statuswidget.py +6 -3
  14. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/telescopewidget.py +19 -17
  15. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/videowidget.py +13 -8
  16. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyproject.toml +1 -1
  17. pyobs_gui-2.0.0.dev18/specs/2026-08-20-gui-remote-call-error-handling.md +232 -0
  18. pyobs_gui-2.0.0.dev18/specs/index.md +45 -0
  19. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/tests/test_camerawidget.py +5 -3
  20. pyobs_gui-2.0.0.dev18/tests/test_videowidget.py +38 -0
  21. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/uv.lock +1 -1
  22. pyobs_gui-2.0.0.dev16/specs/index.md +0 -34
  23. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/.claude/skills/verify/SKILL.md +0 -0
  24. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/.github/dependabot.yml +0 -0
  25. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/.github/workflows/pypi.yml +0 -0
  26. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/.github/workflows/pyrefly.yml +0 -0
  27. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/.github/workflows/ruff.yml +0 -0
  28. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/.gitignore +0 -0
  29. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/.pre-commit-config.yaml +0 -0
  30. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/.readthedocs.yml +0 -0
  31. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/CHANGELOG.rst +0 -0
  32. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/LICENSE +0 -0
  33. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/README.md +0 -0
  34. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/bin/mypyuic5 +0 -0
  35. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/docs/Makefile +0 -0
  36. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/docs/make.bat +0 -0
  37. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/docs/requirements.txt +0 -0
  38. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/docs/source/_static/pyobs.gif +0 -0
  39. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/docs/source/conf.py +0 -0
  40. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/docs/source/index.rst +0 -0
  41. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/mypy.ini +0 -0
  42. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/__init__.py +0 -0
  43. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/__main__.py +0 -0
  44. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/_nuitka_astropy_patch.py +0 -0
  45. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/accounts.py +0 -0
  46. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/commandinputwidget.py +0 -0
  47. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/compassmovewidget.py +0 -0
  48. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/eventswidget.py +0 -0
  49. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/fitsheaderswidget.py +0 -0
  50. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/gui.py +0 -0
  51. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/login.py +0 -0
  52. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/loginwindow.py +0 -0
  53. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/logmodel.py +0 -0
  54. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/mainwindow.py +0 -0
  55. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/modewidget.py +0 -0
  56. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/modifiedmixin.py +0 -0
  57. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/modulegui.py +0 -0
  58. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/__init__.py +0 -0
  59. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/acquisitionwidget.ui +0 -0
  60. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/acquisitionwidget_ui.py +0 -0
  61. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/autofocuswidget.ui +0 -0
  62. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/autofocuswidget_ui.py +0 -0
  63. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/autoguidingwidget.ui +0 -0
  64. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/autoguidingwidget_ui.py +0 -0
  65. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/camerawidget.ui +0 -0
  66. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/camerawidget_ui.py +0 -0
  67. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/compassmoveplugin.py +0 -0
  68. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/compassmovewidget.py +0 -0
  69. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/compassmovewidget.ui +0 -0
  70. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/compassmovewidget_ui.py +0 -0
  71. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/compile.sh +0 -0
  72. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/coolingwidget.ui +0 -0
  73. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/coolingwidget_ui.py +0 -0
  74. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/datadisplayplugin.py +0 -0
  75. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/datadisplaywidget.ui +0 -0
  76. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/datadisplaywidget_ui.py +0 -0
  77. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/designer.sh +0 -0
  78. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/eventswidget.ui +0 -0
  79. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/eventswidget_ui.py +0 -0
  80. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/filterwidget.ui +0 -0
  81. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/filterwidget_ui.py +0 -0
  82. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/fitsheaderswidget.ui +0 -0
  83. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/fitsheaderswidget_ui.py +0 -0
  84. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/focuswidget.ui +0 -0
  85. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/focuswidget_ui.py +0 -0
  86. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/mainwindow.ui +0 -0
  87. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/mainwindow_ui.py +0 -0
  88. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/modewidget.ui +0 -0
  89. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/modewidget_ui.py +0 -0
  90. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/arrow-alt-circle-down-solid.svg +0 -0
  91. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/arrow-alt-circle-left-solid.svg +0 -0
  92. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/arrow-alt-circle-right-solid.svg +0 -0
  93. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/arrow-alt-circle-up-solid.svg +0 -0
  94. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/arrow-up-right-from-square-solid.svg +0 -0
  95. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/arrows-to-eye-solid.svg +0 -0
  96. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/chart-line-solid.svg +0 -0
  97. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/circle-question-solid.svg +0 -0
  98. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/cloud-sun-solid.svg +0 -0
  99. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/edit-solid.svg +0 -0
  100. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/search-solid.svg +0 -0
  101. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources/undo-solid.svg +0 -0
  102. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources.qrc +0 -0
  103. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/resources_rc.py +0 -0
  104. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/roofwidget.ui +0 -0
  105. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/roofwidget_ui.py +0 -0
  106. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/shellwidget.ui +0 -0
  107. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/shellwidget_ui.py +0 -0
  108. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/spectrographwidget.ui +0 -0
  109. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/spectrographwidget_ui.py +0 -0
  110. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/telescopewidget.ui +0 -0
  111. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/telescopewidget_ui.py +0 -0
  112. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/temperaturesplotwidget.ui +0 -0
  113. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/temperaturesplotwidget_ui.py +0 -0
  114. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/temperatureswidget.ui +0 -0
  115. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/temperatureswidget_ui.py +0 -0
  116. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/videowidget.ui +0 -0
  117. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/videowidget_ui.py +0 -0
  118. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/weatherwidget.ui +0 -0
  119. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/qt/weatherwidget_ui.py +0 -0
  120. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/shellwidget.py +0 -0
  121. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/temperaturesplotwidget.py +0 -0
  122. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/temperatureswidget.py +0 -0
  123. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/utils.py +0 -0
  124. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/watchedlabel.py +0 -0
  125. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pyobs_gui/weatherwidget.py +0 -0
  126. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/pysidedeploy.spec +0 -0
  127. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/spike_standalone/main.py +0 -0
  128. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/spike_standalone/pysidedeploy.spec +0 -0
  129. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/acquisition.yaml +0 -0
  130. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/autofocus.yaml +0 -0
  131. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/camera.yaml +0 -0
  132. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/full.yaml +0 -0
  133. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/guiding.yaml +0 -0
  134. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/mode.yaml +0 -0
  135. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/roof.yaml +0 -0
  136. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/spectrograph.yaml +0 -0
  137. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/telescope.yaml +0 -0
  138. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/telescope_acl.yaml +0 -0
  139. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/telescope_acl_denied.yaml +0 -0
  140. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/test/video.yaml +0 -0
  141. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/tests/__init__.py +0 -0
  142. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/tests/conftest.py +0 -0
  143. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/tests/test_accounts.py +0 -0
  144. {pyobs_gui-2.0.0.dev16 → pyobs_gui-2.0.0.dev18}/tests/test_loginwindow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyobs-gui
3
- Version: 2.0.0.dev16
3
+ Version: 2.0.0.dev18
4
4
  Summary: A remote GUI for pyobs
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -2,7 +2,6 @@ import logging
2
2
  import os
3
3
  from typing import Any
4
4
 
5
- import qasync # type: ignore
6
5
  from PySide6 import QtCore, QtWidgets # type: ignore
7
6
 
8
7
  os.environ["QT_API"] = "PySide6"
@@ -147,13 +146,17 @@ class AcquisitionWidget(BaseWidget, Ui_AcquisitionWidget):
147
146
 
148
147
  self.canvas.draw()
149
148
 
150
- @qasync.asyncSlot() # type: ignore
151
- async def _acquire(self) -> None:
149
+ def _acquire(self) -> None:
150
+ self.run_background(self._acquire_target)
151
+
152
+ async def _acquire_target(self) -> None:
152
153
  async with self.comm.proxy(self.module, IAcquisition) as proxy:
153
154
  await proxy.acquire_target()
154
155
 
155
- @qasync.asyncSlot() # type: ignore
156
- async def _abort(self) -> None:
156
+ def _abort(self) -> None:
157
+ self.run_background(self._abort_acquisition)
158
+
159
+ async def _abort_acquisition(self) -> None:
157
160
  async with self.comm.proxy(self.module, IAcquisition) as proxy:
158
161
  await proxy.abort()
159
162
 
@@ -2,7 +2,6 @@ import logging
2
2
  import os
3
3
  from typing import Any
4
4
 
5
- import qasync # type: ignore
6
5
  from PySide6 import QtCore, QtWidgets # type: ignore
7
6
 
8
7
  os.environ["QT_API"] = "PySide6"
@@ -78,9 +77,7 @@ class AutoFocusWidget(BaseWidget, Ui_AutoFocusWidget):
78
77
  self.labelStatus.setText(
79
78
  "Running..."
80
79
  if self._running
81
- else f"Focus: {self._last_result[0]:.3f} ± {self._last_result[1]:.3f} mm"
82
- if self._last_result
83
- else "Idle"
80
+ else f"Focus: {self._last_result[0]:.3f} ± {self._last_result[1]:.3f} mm" if self._last_result else "Idle"
84
81
  )
85
82
 
86
83
  self.ax.clear()
@@ -95,13 +92,19 @@ class AutoFocusWidget(BaseWidget, Ui_AutoFocusWidget):
95
92
  self.ax.set_axisbelow(True)
96
93
  self.canvas.draw()
97
94
 
98
- @qasync.asyncSlot() # type: ignore
99
- async def _run_auto_focus(self) -> None:
95
+ def _run_auto_focus(self) -> None:
96
+ self.run_background(
97
+ self._auto_focus, self.spinCount.value(), self.spinStep.value(), self.spinExposureTime.value()
98
+ )
99
+
100
+ async def _auto_focus(self, count: int, step: float, exposure_time: float) -> None:
100
101
  async with self.comm.proxy(self.module, IAutoFocus) as proxy:
101
- await proxy.auto_focus(self.spinCount.value(), self.spinStep.value(), self.spinExposureTime.value())
102
+ await proxy.auto_focus(count, step, exposure_time)
103
+
104
+ def _abort(self) -> None:
105
+ self.run_background(self._abort_focus)
102
106
 
103
- @qasync.asyncSlot() # type: ignore
104
- async def _abort(self) -> None:
107
+ async def _abort_focus(self) -> None:
105
108
  async with self.comm.proxy(self.module, IAutoFocus) as proxy:
106
109
  await proxy.abort()
107
110
 
@@ -4,7 +4,6 @@ import os
4
4
  from collections import deque
5
5
  from typing import Any
6
6
 
7
- import qasync # type: ignore
8
7
  from PySide6 import QtCore, QtWidgets # type: ignore
9
8
 
10
9
  os.environ["QT_API"] = "PySide6"
@@ -130,19 +129,25 @@ class AutoGuidingWidget(BaseWidget, Ui_AutoGuidingWidget):
130
129
  self.figure.tight_layout()
131
130
  self.canvas.draw()
132
131
 
133
- @qasync.asyncSlot() # type: ignore
134
- async def _start(self) -> None:
132
+ def _start(self) -> None:
133
+ self.run_background(self._start_guiding)
134
+
135
+ async def _start_guiding(self) -> None:
135
136
  async with self.comm.proxy(self.module, IAutoGuiding) as proxy:
136
137
  await proxy.start()
137
138
 
138
- @qasync.asyncSlot() # type: ignore
139
- async def _stop(self) -> None:
139
+ def _stop(self) -> None:
140
+ self.run_background(self._stop_guiding)
141
+
142
+ async def _stop_guiding(self) -> None:
140
143
  async with self.comm.proxy(self.module, IAutoGuiding) as proxy:
141
144
  await proxy.stop()
142
145
 
143
- @qasync.asyncSlot() # type: ignore
144
- async def _set_exposure_time(self) -> None:
146
+ def _set_exposure_time(self) -> None:
145
147
  value = self.spinExposureTime.value()
148
+ self.run_background(self._set_exposure_time_async, value)
149
+
150
+ async def _set_exposure_time_async(self, value: float) -> None:
146
151
  async with self.comm.proxy(self.module, IExposureTime) as proxy:
147
152
  await proxy.set_exposure_time(value)
148
153
 
@@ -26,6 +26,24 @@ log = logging.getLogger(__name__)
26
26
  WidgetClass = TypeVar("WidgetClass", bound="BaseWidget")
27
27
 
28
28
 
29
+ async def show_remote_error(parent: QtWidgets.QWidget, exception: Exception) -> None:
30
+ """Show a remote-call failure to the user.
31
+
32
+ Single shared path for surfacing exceptions from remote method calls: `PyobsError`
33
+ (domain errors like `MoveError`, and transport errors like `RemoteError`) shows a warning
34
+ box titled with the exception class; any other `Exception` is logged and shown as a plain
35
+ warning. Used by `BaseWidget._background_task()` (everything routed through
36
+ `run_background()`) and by non-`BaseWidget` callers like `StatusItem`.
37
+ """
38
+ if isinstance(exception, exc.PyobsError):
39
+ # PyobsError.__str__ already prefixes the class name ("<MoveError> msg"); the class name
40
+ # is the dialog title, so show only the message itself
41
+ await QAsyncMessageBox.warning(parent, type(exception).__name__, exception.message or str(exception))
42
+ else:
43
+ log.exception("An error occurred.")
44
+ await QAsyncMessageBox.warning(parent, "Error", str(exception))
45
+
46
+
29
47
  class BaseWindow:
30
48
  def __init__(self) -> None:
31
49
  """Base class for MainWindow and all widgets."""
@@ -120,7 +138,6 @@ class BaseWindow:
120
138
 
121
139
 
122
140
  class BaseWidget(BaseWindow, QtWidgets.QWidget): # type: ignore
123
- _show_error = QtCore.Signal(str)
124
141
  _enable_buttons = QtCore.Signal(list, bool)
125
142
 
126
143
  def __init__(
@@ -134,7 +151,6 @@ class BaseWidget(BaseWindow, QtWidgets.QWidget): # type: ignore
134
151
  QtWidgets.QWidget.__init__(self)
135
152
 
136
153
  # signals
137
- self._show_error.connect(self.show_error)
138
154
  self._enable_buttons.connect(self.enable_buttons)
139
155
 
140
156
  # update
@@ -271,6 +287,7 @@ class BaseWidget(BaseWindow, QtWidgets.QWidget): # type: ignore
271
287
  self._update_task = None
272
288
 
273
289
  async def _update_loop(self) -> None:
290
+ consecutive_failures = 0
274
291
  while True:
275
292
  try:
276
293
  # get module state
@@ -288,9 +305,14 @@ class BaseWidget(BaseWindow, QtWidgets.QWidget): # type: ignore
288
305
 
289
306
  # sleep a little
290
307
  await asyncio.sleep(1)
291
-
292
- except (exc.PyobsError, IndexError):
293
- # ignore these and sleep a little
308
+ consecutive_failures = 0
309
+
310
+ except (exc.PyobsError, IndexError) as e:
311
+ # background polling failures must not pop a dialog per failed poll -- log the
312
+ # first failure, then every 60th (~once a minute at the 1s poll interval)
313
+ consecutive_failures += 1
314
+ if consecutive_failures % 60 == 1:
315
+ log.warning("Update of %s failed: %s", self.module, e)
294
316
  await asyncio.sleep(1)
295
317
 
296
318
  def run_background(self, method: Callable[..., Coroutine[Any, Any, None]], *args: Any, **kwargs: Any) -> None:
@@ -308,20 +330,12 @@ class BaseWidget(BaseWindow, QtWidgets.QWidget): # type: ignore
308
330
  # call method
309
331
  try:
310
332
  await method(*args, **kwargs)
311
- except exc.PyobsError as e:
312
- await self.show_error(e)
313
333
  except Exception as e:
314
- log.exception("An error occurred.")
315
- await QAsyncMessageBox.warning(self, "Error", str(e))
334
+ await show_remote_error(self, e)
316
335
  finally:
317
336
  # enable widgets
318
337
  self._enable_buttons.emit(disable, True)
319
338
 
320
- async def show_error(self, exception: exc.PyobsError) -> None:
321
- err = str(exception)
322
- title, message = err.split(":") if ":" in err else ("Error", err)
323
- await QAsyncMessageBox.warning(self, title, message)
324
-
325
339
  def enable_buttons(self, widgets: list[QtWidgets.QWidget], enable: bool) -> None:
326
340
  for w in widgets:
327
341
  w.setEnabled(enable)
@@ -1,6 +1,5 @@
1
1
  import logging
2
2
  from typing import Any
3
- import qasync # type: ignore
4
3
  from PySide6 import QtWidgets, QtCore # type: ignore
5
4
  from astroplan import Observer
6
5
 
@@ -225,8 +224,10 @@ class CameraWidget(BaseWidget, Ui_CameraWidget):
225
224
  self.exposures_left = state.count_left
226
225
  self.update_gui()
227
226
 
228
- @qasync.asyncSlot() # type: ignore
229
- async def set_full_frame(self) -> None:
227
+ def set_full_frame(self) -> None:
228
+ self.run_background(self._do_set_full_frame)
229
+
230
+ async def _do_set_full_frame(self) -> None:
230
231
  caps = await self.comm.get_capabilities(self.module, IWindow)
231
232
  if caps is not None:
232
233
  # get binning
@@ -268,18 +269,15 @@ class CameraWidget(BaseWidget, Ui_CameraWidget):
268
269
  else:
269
270
  self.spinExpTime.setEnabled(True)
270
271
 
271
- @qasync.asyncSlot() # type: ignore
272
- async def expose(self) -> None:
272
+ def expose(self) -> None:
273
+ self.run_background(self._do_expose)
274
+
275
+ async def _do_expose(self) -> None:
273
276
  # set binning
274
277
  async with self.comm.safe_proxy(self.module, IBinning) as proxy:
275
278
  if proxy is not None:
276
279
  binning = int(self.comboBinning.currentText()[0])
277
- try:
278
- await proxy.set_binning(binning, binning)
279
- except Exception:
280
- log.exception("bla")
281
- QtWidgets.QMessageBox.information(self, "Error", "Could not set binning.")
282
- return
280
+ await proxy.set_binning(binning, binning)
283
281
  else:
284
282
  binning = 1
285
283
 
@@ -288,11 +286,7 @@ class CameraWidget(BaseWidget, Ui_CameraWidget):
288
286
  if proxy is not None:
289
287
  left, top = self.spinWindowLeft.value(), self.spinWindowTop.value()
290
288
  width, height = self.spinWindowWidth.value(), self.spinWindowHeight.value()
291
- try:
292
- await proxy.set_window(left, top, width * binning, height * binning)
293
- except Exception:
294
- QtWidgets.QMessageBox.information(self, "Error", "Could not set window.")
295
- return
289
+ await proxy.set_window(left, top, width * binning, height * binning)
296
290
 
297
291
  # set image format
298
292
  async with self.comm.safe_proxy(self.module, IImageFormat) as proxy:
@@ -348,13 +342,15 @@ class CameraWidget(BaseWidget, Ui_CameraWidget):
348
342
  self.exposures_left -= 1
349
343
  self.signal_update_gui.emit()
350
344
 
351
- @qasync.asyncSlot() # type: ignore
352
- async def abort(self) -> None:
345
+ def abort(self) -> None:
353
346
  """Abort exposure."""
354
347
  # do we have a running exposure?
355
348
  if self.exposures_left == 0:
356
349
  return
357
350
 
351
+ self.run_background(self._do_abort)
352
+
353
+ async def _do_abort(self) -> None:
358
354
  # got exposures left?
359
355
  if self.exposures_left > 1:
360
356
  # soft-stop the sequence server-side (current grab finishes normally), if
@@ -1,4 +1,3 @@
1
- import qasync
2
1
  import logging
3
2
  from typing import Any
4
3
  from PySide6 import QtCore # type: ignore
@@ -47,9 +46,11 @@ class CoolingWidget(BaseWidget, Ui_CoolingWidget):
47
46
  def checkEnabled_toggled(self, enabled: bool) -> None:
48
47
  self.spinSetPoint.setEnabled(enabled)
49
48
 
50
- @qasync.asyncSlot() # type: ignore
51
- async def buttonApply_clicked(self) -> None:
49
+ def buttonApply_clicked(self) -> None:
52
50
  enabled = self.checkEnabled.isChecked()
53
51
  temp = self.spinSetPoint.value()
52
+ self.run_background(self._apply_cooling, enabled, temp)
53
+
54
+ async def _apply_cooling(self, enabled: bool, temp: float) -> None:
54
55
  async with self.comm.proxy(self.module, ICooling) as proxy:
55
56
  await proxy.set_cooling(enabled, temp)
@@ -20,7 +20,6 @@ from .qt.datadisplaywidget_ui import Ui_DataDisplayWidget
20
20
 
21
21
  if TYPE_CHECKING:
22
22
  from matplotlib.figure import Figure
23
- from pyobs.comm import Proxy
24
23
  from matplotlib.axes import Axes
25
24
  from astropy.io import fits
26
25
 
@@ -1,6 +1,5 @@
1
1
  from typing import Any
2
2
 
3
- import qasync
4
3
  from PySide6 import QtWidgets, QtCore # type: ignore
5
4
 
6
5
  from pyobs.interfaces import IFilters, FilterState, IMotion, MotionState
@@ -61,12 +60,15 @@ class FilterWidget(BaseWidget, Ui_FilterWidget):
61
60
  ]
62
61
  self.buttonSetFilter.setEnabled(initialized and self.permitted("set_filter"))
63
62
 
64
- @qasync.asyncSlot() # type: ignore
65
- async def set_filter(self) -> None:
63
+ def set_filter(self) -> None:
66
64
  new_value, ok = QtWidgets.QInputDialog.getItem(self, "Set filter", "New filter", self._filters, 0, False)
67
65
  if ok:
68
- async with self.comm.proxy(self.module, IFilters) as proxy:
69
- self.run_background(proxy.set_filter, new_value)
66
+
67
+ async def _do_set_filter() -> None:
68
+ async with self.comm.proxy(self.module, IFilters) as proxy:
69
+ await proxy.set_filter(new_value)
70
+
71
+ self.run_background(_do_set_filter)
70
72
 
71
73
 
72
74
  __all__ = ["FilterWidget"]
@@ -1,8 +1,6 @@
1
- import asyncio
2
1
  import logging
3
2
  from typing import Any
4
3
 
5
- import qasync # type: ignore
6
4
  from PySide6 import QtWidgets, QtCore # type: ignore
7
5
 
8
6
  from pyobs.interfaces import IFocuser, FocuserState, IMotion, MotionState
@@ -71,7 +69,7 @@ class FocusWidget(BaseWidget, Ui_FocusWidget):
71
69
  value = self._focus or 0.0
72
70
  new_value, ok = QtWidgets.QInputDialog.getDouble(self, "Focus", "New value", value, 0, 100, 2)
73
71
  if ok:
74
- asyncio.ensure_future(self._set_focus_base_async(new_value))
72
+ self.run_background(self._set_focus_base_async, new_value)
75
73
 
76
74
  async def _set_focus_base_async(self, new_value: float) -> None:
77
75
  async with self.comm.proxy(self.module, IFocuser) as proxy:
@@ -81,14 +79,16 @@ class FocusWidget(BaseWidget, Ui_FocusWidget):
81
79
  value = self._focus_offset or 0.0
82
80
  new_value, ok = QtWidgets.QInputDialog.getDouble(self, "Focus offset", "New value", value, -5, 5, 2)
83
81
  if ok:
84
- asyncio.ensure_future(self._set_focus_offset_async(new_value))
82
+ self.run_background(self._set_focus_offset_async, new_value)
85
83
 
86
84
  async def _set_focus_offset_async(self, new_value: float) -> None:
87
85
  async with self.comm.proxy(self.module, IFocuser) as proxy:
88
86
  await proxy.set_focus_offset(new_value)
89
87
 
90
- @qasync.asyncSlot() # type: ignore
91
- async def _reset_focus_offset(self) -> None:
88
+ def _reset_focus_offset(self) -> None:
89
+ self.run_background(self._do_reset_focus_offset)
90
+
91
+ async def _do_reset_focus_offset(self) -> None:
92
92
  async with self.comm.proxy(self.module, IFocuser) as proxy:
93
93
  await proxy.set_focus_offset(0.0)
94
94
 
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing import Any, TYPE_CHECKING
4
- import qasync # type: ignore
5
4
  from PySide6 import QtCore # type: ignore
6
5
 
7
6
  from pyobs.interfaces import IMotion, MotionState, IPointingAltAz, AltAzState
@@ -53,17 +52,23 @@ class RoofWidget(BaseWidget, Ui_RoofWidget):
53
52
  else:
54
53
  self.labelAzimuth.setText(f"{self._azimuth:.1f}°")
55
54
 
56
- @qasync.asyncSlot() # type: ignore
57
- async def open_roof(self) -> None:
55
+ def open_roof(self) -> None:
56
+ self.run_background(self._open_roof)
57
+
58
+ async def _open_roof(self) -> None:
58
59
  async with self.comm.proxy(self.module, IMotion) as proxy:
59
60
  await proxy.init()
60
61
 
61
- @qasync.asyncSlot() # type: ignore
62
- async def close_roof(self) -> None:
62
+ def close_roof(self) -> None:
63
+ self.run_background(self._close_roof)
64
+
65
+ async def _close_roof(self) -> None:
63
66
  async with self.comm.proxy(self.module, IMotion) as proxy:
64
67
  await proxy.park()
65
68
 
66
- @qasync.asyncSlot() # type: ignore
67
- async def stop_roof(self) -> None:
69
+ def stop_roof(self) -> None:
70
+ self.run_background(self._stop_roof)
71
+
72
+ async def _stop_roof(self) -> None:
68
73
  async with self.comm.proxy(self.module, IMotion) as proxy:
69
74
  await proxy.stop_motion()
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  from typing import Any, TYPE_CHECKING
5
- import qasync # type: ignore
6
5
  from PySide6 import QtCore # type: ignore
7
6
 
8
7
  from pyobs.interfaces import IAbortable, IExposure, ExposureState
@@ -59,8 +58,10 @@ class SpectrographWidget(BaseWidget, Ui_SpectrographWidget):
59
58
  self.exposure_status = state.status
60
59
  self.signal_update_gui.emit()
61
60
 
62
- @qasync.asyncSlot() # type: ignore
63
- async def grab_spectrum(self) -> None:
61
+ def grab_spectrum(self) -> None:
62
+ self.run_background(self._grab_spectra)
63
+
64
+ async def _grab_spectra(self) -> None:
64
65
  broadcast = self.checkBroadcast.isChecked()
65
66
 
66
67
  # take the requested number of spectra; an exception (e.g. from a failed exposure,
@@ -72,10 +73,12 @@ class SpectrographWidget(BaseWidget, Ui_SpectrographWidget):
72
73
  self.exposures_left -= 1
73
74
  self.signal_update_gui.emit()
74
75
 
75
- @qasync.asyncSlot() # type: ignore
76
- async def abort(self) -> None:
76
+ def abort(self) -> None:
77
77
  # stop the sequence after the current spectrum finishes aborting
78
78
  self.exposures_left = 0
79
+ self.run_background(self._abort_sequence)
80
+
81
+ async def _abort_sequence(self) -> None:
79
82
  async with self.comm.proxy(self.module, IAbortable) as proxy:
80
83
  await proxy.abort()
81
84
 
@@ -13,7 +13,7 @@ from pyobs.events import Event, ModuleOpenedEvent, ModuleClosedEvent
13
13
  from pyobs.interfaces import IModule, Interface
14
14
  from pyobs.utils.enums import ModuleState
15
15
  from pyobs.vfs import VirtualFileSystem
16
- from pyobs_gui.base import BaseWidget
16
+ from pyobs_gui.base import BaseWidget, show_remote_error
17
17
 
18
18
  log = logging.getLogger(__name__)
19
19
 
@@ -131,8 +131,11 @@ class StatusItem(QtWidgets.QWidget):
131
131
 
132
132
  @qasync.asyncSlot() # type: ignore
133
133
  async def button_clicked(self) -> None:
134
- async with self.comm.proxy(self.name, IModule) as proxy:
135
- await proxy.reset_error()
134
+ try:
135
+ async with self.comm.proxy(self.name, IModule) as proxy:
136
+ await proxy.reset_error()
137
+ except Exception as e:
138
+ await show_remote_error(self, e)
136
139
 
137
140
 
138
141
  class StateItem(QtCore.QObject):
@@ -1,9 +1,7 @@
1
- import asyncio
2
1
  from enum import Enum
3
2
  from typing import Any
4
3
 
5
4
  import numpy as np
6
- import qasync # type: ignore
7
5
  from PySide6 import QtWidgets, QtCore # type: ignore
8
6
  from astropy.coordinates import SkyCoord, ICRS, AltAz, get_sun, EarthLocation
9
7
  from astroplan import Observer
@@ -473,9 +471,7 @@ class TelescopeWidget(BaseWidget, Ui_TelescopeWidget):
473
471
 
474
472
  if IPointingHelioprojective in self._interfaces and self.permitted("move_helioprojective"):
475
473
  self.run_background(self._do_move_helioprojective, heliproj.Tx.degree, heliproj.Ty.degree)
476
- elif IPointingHeliographicStonyhurst in self._interfaces and self.permitted(
477
- "move_heliographic_stonyhurst"
478
- ):
474
+ elif IPointingHeliographicStonyhurst in self._interfaces and self.permitted("move_heliographic_stonyhurst"):
479
475
  stony = heliproj.transform_to(HeliographicStonyhurst)
480
476
  # pyrefly: ignore [missing-attribute]
481
477
  lon, lat = float(stony.lon.to(u.degree).value), float(stony.lat.to(u.degree).value)
@@ -517,18 +513,24 @@ class TelescopeWidget(BaseWidget, Ui_TelescopeWidget):
517
513
  # Motion control slots
518
514
  # -------------------------------------------------------------------------
519
515
 
520
- @qasync.asyncSlot() # type: ignore
521
- async def _init_telescope(self) -> None:
516
+ def _init_telescope(self) -> None:
517
+ self.run_background(self._do_init)
518
+
519
+ async def _do_init(self) -> None:
522
520
  async with self.comm.proxy(self.module, IMotion) as proxy:
523
521
  await proxy.init()
524
522
 
525
- @qasync.asyncSlot() # type: ignore
526
- async def _park_telescope(self) -> None:
523
+ def _park_telescope(self) -> None:
524
+ self.run_background(self._do_park)
525
+
526
+ async def _do_park(self) -> None:
527
527
  async with self.comm.proxy(self.module, IMotion) as proxy:
528
528
  await proxy.park()
529
529
 
530
- @qasync.asyncSlot() # type: ignore
531
- async def _stop_telescope(self) -> None:
530
+ def _stop_telescope(self) -> None:
531
+ self.run_background(self._do_stop)
532
+
533
+ async def _do_stop(self) -> None:
532
534
  async with self.comm.proxy(self.module, IMotion) as proxy:
533
535
  await proxy.stop_motion("ITelescope")
534
536
 
@@ -539,20 +541,20 @@ class TelescopeWidget(BaseWidget, Ui_TelescopeWidget):
539
541
  @QtCore.Slot() # type: ignore
540
542
  def _set_offset(self) -> None:
541
543
  if self.sender() == self.buttonResetHorizontalOffsets:
542
- asyncio.create_task(self._do_set_offsets_altaz(0.0, 0.0))
544
+ self.run_background(self._do_set_offsets_altaz, 0.0, 0.0)
543
545
  elif self.sender() == self.buttonResetEquatorialOffsets:
544
- asyncio.create_task(self._do_set_offsets_radec(0.0, 0.0))
546
+ self.run_background(self._do_set_offsets_radec, 0.0, 0.0)
545
547
  else:
546
548
  new_value, ok = QtWidgets.QInputDialog.getDouble(self, "Set offset", 'New offset ["]', 0, -9999, 9999)
547
549
  if ok:
548
550
  if self.sender() == self.buttonSetAltOffset:
549
- asyncio.create_task(self._do_set_offsets_altaz(new_value / 3600.0, self._off_az or 0.0))
551
+ self.run_background(self._do_set_offsets_altaz, new_value / 3600.0, self._off_az or 0.0)
550
552
  elif self.sender() == self.buttonSetAzOffset:
551
- asyncio.create_task(self._do_set_offsets_altaz(self._off_alt or 0.0, new_value / 3600.0))
553
+ self.run_background(self._do_set_offsets_altaz, self._off_alt or 0.0, new_value / 3600.0)
552
554
  elif self.sender() == self.buttonSetRaOffset:
553
- asyncio.create_task(self._do_set_offsets_radec(new_value / 3600.0, self._off_dec or 0.0))
555
+ self.run_background(self._do_set_offsets_radec, new_value / 3600.0, self._off_dec or 0.0)
554
556
  elif self.sender() == self.buttonSetDecOffset:
555
- asyncio.create_task(self._do_set_offsets_radec(self._off_ra or 0.0, new_value / 3600.0))
557
+ self.run_background(self._do_set_offsets_radec, self._off_ra or 0.0, new_value / 3600.0)
556
558
 
557
559
  async def _do_set_offsets_altaz(self, alt: float, az: float) -> None:
558
560
  async with self.comm.proxy(self.module, IOffsetsAltAz) as proxy:
@@ -3,7 +3,6 @@ import logging
3
3
  from typing import Any
4
4
  from urllib.parse import urlparse
5
5
 
6
- import qasync # type: ignore
7
6
  from astroplan import Observer
8
7
  from pyobs.comm import Comm
9
8
  from pyobs.interfaces import (
@@ -224,8 +223,10 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
224
223
  qp.loadFromData(image_data)
225
224
  self.widgetLiveView.setPixmap(qp)
226
225
 
227
- @qasync.asyncSlot() # type: ignore
228
- async def grab_image(self) -> None:
226
+ def grab_image(self) -> None:
227
+ self.run_background(self._grab_image)
228
+
229
+ async def _grab_image(self) -> None:
229
230
  # set image format
230
231
  if IImageFormat in self._interfaces:
231
232
  image_format = ImageFormat[self.comboImageFormat.currentText()] # type: ignore[attr-defined]
@@ -239,7 +240,7 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
239
240
  self.signal_update_gui.emit()
240
241
 
241
242
  # start exposures
242
- asyncio.create_task(self._expose_task_func())
243
+ await self._expose_task_func()
243
244
 
244
245
  async def _expose_task_func(self) -> None:
245
246
  # get image type
@@ -266,22 +267,26 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
266
267
  def abort_sequence(self) -> None:
267
268
  self.exposures_left = 0
268
269
 
269
- @qasync.asyncSlot() # type: ignore
270
- async def exposure_time_changed(self) -> None:
270
+ def exposure_time_changed(self) -> None:
271
271
  # get exp_time
272
272
  exp_time = self.spinExpTime.value()
273
273
 
274
274
  # set it
275
+ self.run_background(self._set_exposure_time, exp_time)
276
+
277
+ async def _set_exposure_time(self, exp_time: float) -> None:
275
278
  if IExposureTime in self._interfaces:
276
279
  async with self.comm.proxy(self.module, IExposureTime) as proxy:
277
280
  await proxy.set_exposure_time(exp_time)
278
281
 
279
- @qasync.asyncSlot() # type: ignore
280
- async def gain_changed(self) -> None:
282
+ def gain_changed(self) -> None:
281
283
  # get gain
282
284
  gain = self.spinGain.value()
283
285
 
284
286
  # set it
287
+ self.run_background(self._set_gain, gain)
288
+
289
+ async def _set_gain(self, gain: float) -> None:
285
290
  if IGain in self._interfaces:
286
291
  async with self.comm.proxy(self.module, IGain) as proxy:
287
292
  await proxy.set_gain(gain)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-gui"
3
- version = "2.0.0.dev16"
3
+ version = "2.0.0.dev18"
4
4
  description = "A remote GUI for pyobs"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"