pyobs-core 2.0.0.dev21__tar.gz → 2.0.0.dev24__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 (444) hide show
  1. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/PKG-INFO +1 -1
  2. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/comm.py +31 -9
  3. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/proxy.py +29 -1
  4. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xmppclient.py +1 -1
  5. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xmppcomm.py +25 -11
  6. pyobs_core-2.0.0.dev24/pyobs/interfaces/IDataSequence.py +51 -0
  7. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/__init__.py +3 -0
  8. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/motionstatus.py +13 -1
  9. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/basecamera.py +66 -2
  10. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/basespectrograph.py +11 -1
  11. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/pipelinecamera.py +11 -2
  12. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/flatfield/flatfield.py +6 -0
  13. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/focus/focusmodel.py +14 -3
  14. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/autoguiding.py +5 -0
  15. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/scienceframeguiding.py +5 -0
  16. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/roof/baseroof.py +1 -14
  17. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/telescope/_dummytelescopebase.py +12 -0
  18. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/telescope/dummysolartelescope.py +12 -0
  19. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/dummymode.py +1 -2
  20. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/flatfielder.py +7 -1
  21. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyproject.toml +1 -1
  22. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/.gitignore +0 -0
  23. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/LICENSE +0 -0
  24. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/__init__.py +0 -0
  25. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/application.py +0 -0
  26. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/background_task.py +0 -0
  27. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/cli/__init__.py +0 -0
  28. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/cli/_cli.py +0 -0
  29. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/cli/pyobs.py +0 -0
  30. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/cli/pyobsd.py +0 -0
  31. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/cli/pyobsw.py +0 -0
  32. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/__init__.py +0 -0
  33. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/commlogging.py +0 -0
  34. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/dummy/__init__.py +0 -0
  35. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/dummy/dummycomm.py +0 -0
  36. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/local/__init__.py +0 -0
  37. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/local/localcomm.py +0 -0
  38. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/local/localnetwork.py +0 -0
  39. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/__init__.py +0 -0
  40. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/rpc.py +0 -0
  41. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/serializer.py +0 -0
  42. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  43. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  44. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  45. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  46. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  47. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  48. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  49. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/__init__.py +0 -0
  50. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/badweather.py +0 -0
  51. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/event.py +0 -0
  52. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/exposurestatuschanged.py +0 -0
  53. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/filterchanged.py +0 -0
  54. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/focusfound.py +0 -0
  55. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/goodweather.py +0 -0
  56. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/log.py +0 -0
  57. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/modechanged.py +0 -0
  58. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/moduleclosed.py +0 -0
  59. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/moduleopened.py +0 -0
  60. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/motionstatuschanged.py +0 -0
  61. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/move.py +0 -0
  62. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/newimage.py +0 -0
  63. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/newspectrum.py +0 -0
  64. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/offsets.py +0 -0
  65. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/roofclosing.py +0 -0
  66. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/roofopened.py +0 -0
  67. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/taskfailed.py +0 -0
  68. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/taskfinished.py +0 -0
  69. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/taskstarted.py +0 -0
  70. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/events/testevent.py +0 -0
  71. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/__init__.py +0 -0
  72. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/image.py +0 -0
  73. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/meta/__init__.py +0 -0
  74. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/meta/altazoffsets.py +0 -0
  75. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/meta/exptime.py +0 -0
  76. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/meta/genericoffset.py +0 -0
  77. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/meta/onskydistance.py +0 -0
  78. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/meta/pixeloffsets.py +0 -0
  79. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/meta/radecoffsets.py +0 -0
  80. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/meta/skyoffsets.py +0 -0
  81. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processor.py +0 -0
  82. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/__init__.py +0 -0
  83. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  84. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/annotation/__init__.py +0 -0
  85. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/annotation/_pillow.py +0 -0
  86. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/annotation/circle.py +0 -0
  87. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/annotation/crosshair.py +0 -0
  88. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/annotation/text.py +0 -0
  89. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/astrometry/__init__.py +0 -0
  90. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  91. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  92. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  93. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  94. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  95. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  96. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/calibration/__init__.py +0 -0
  97. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  98. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  99. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/calibration/calibration.py +0 -0
  100. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/detection/__init__.py +0 -0
  101. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  102. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  103. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/detection/daophot.py +0 -0
  104. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/detection/pysep.py +0 -0
  105. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/detection/simpledisk.py +0 -0
  106. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  107. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/exptime/__init__.py +0 -0
  108. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/exptime/exptime.py +0 -0
  109. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/exptime/star.py +0 -0
  110. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/__init__.py +0 -0
  111. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  112. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/download.py +0 -0
  113. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/flip.py +0 -0
  114. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/grayscale.py +0 -0
  115. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/httpserver.py +0 -0
  116. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/normalize.py +0 -0
  117. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/save.py +0 -0
  118. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/saveimage.py +0 -0
  119. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/smooth.py +0 -0
  120. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/image/softbin.py +0 -0
  121. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/misc/__init__.py +0 -0
  122. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/misc/addmask.py +0 -0
  123. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  124. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/misc/circularmask.py +0 -0
  125. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/misc/createfilename.py +0 -0
  126. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  127. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/misc/removebackground.py +0 -0
  128. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/modules/__init__.py +0 -0
  129. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
  130. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/__init__.py +0 -0
  131. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/astrometry.py +0 -0
  132. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  133. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  134. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  135. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  136. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  137. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/offsets.py +0 -0
  138. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  139. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/projected.py +0 -0
  140. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/offsets/spilled_light.py +0 -0
  141. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/photometry/__init__.py +0 -0
  142. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  143. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  144. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  145. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  146. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/photometry/photometry.py +0 -0
  147. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/photometry/photutil.py +0 -0
  148. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/photometry/pysep.py +0 -0
  149. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/wcs/__init__.py +0 -0
  150. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  151. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IAbortable.py +0 -0
  152. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IAcquisition.py +0 -0
  153. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IAutoFocus.py +0 -0
  154. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IAutoGuiding.py +0 -0
  155. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IAutonomous.py +0 -0
  156. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IBinning.py +0 -0
  157. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ICalibrate.py +0 -0
  158. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ICamera.py +0 -0
  159. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IConfig.py +0 -0
  160. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ICooling.py +0 -0
  161. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IData.py +0 -0
  162. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IDome.py +0 -0
  163. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IExposure.py +0 -0
  164. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IExposureTime.py +0 -0
  165. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IFilters.py +0 -0
  166. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  167. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  168. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IFlatField.py +0 -0
  169. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IFocusModel.py +0 -0
  170. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IFocuser.py +0 -0
  171. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IGain.py +0 -0
  172. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IImageFormat.py +0 -0
  173. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IImageType.py +0 -0
  174. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IMode.py +0 -0
  175. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IModule.py +0 -0
  176. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IMotion.py +0 -0
  177. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IMultiFiber.py +0 -0
  178. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
  179. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
  180. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IPointingAltAz.py +0 -0
  181. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IPointingBody.py +0 -0
  182. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
  183. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
  184. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
  185. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
  186. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IPointingRaDec.py +0 -0
  187. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IPointingSeries.py +0 -0
  188. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IReady.py +0 -0
  189. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IRoof.py +0 -0
  190. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IRotation.py +0 -0
  191. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IRunnable.py +0 -0
  192. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IRunning.py +0 -0
  193. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IScriptRunner.py +0 -0
  194. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ISpectrograph.py +0 -0
  195. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IStartStop.py +0 -0
  196. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IStructuredConfig.py +0 -0
  197. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ISyncTarget.py +0 -0
  198. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ITelescope.py +0 -0
  199. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ITemperatures.py +0 -0
  200. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ITrackingMode.py +0 -0
  201. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/ITrackingRate.py +0 -0
  202. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IVideo.py +0 -0
  203. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IWeather.py +0 -0
  204. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/IWindow.py +0 -0
  205. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/interfaces/interface.py +0 -0
  206. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/__init__.py +0 -0
  207. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/camerasettings.py +0 -0
  208. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/fitsheader.py +0 -0
  209. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/fitsnamespace.py +0 -0
  210. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/follow.py +0 -0
  211. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/pipeline.py +0 -0
  212. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/waitformotion.py +0 -0
  213. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/mixins/weatheraware.py +0 -0
  214. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/__init__.py +0 -0
  215. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/__init__.py +0 -0
  216. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/adaptive.py +0 -0
  217. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/basevideo.py +0 -0
  218. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/dummycamera.py +0 -0
  219. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  220. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/camera/dummyvideo.py +0 -0
  221. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/flatfield/__init__.py +0 -0
  222. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/flatfield/pointing.py +0 -0
  223. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/flatfield/scheduler.py +0 -0
  224. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/focus/__init__.py +0 -0
  225. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/focus/dummyautofocus.py +0 -0
  226. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/focus/focusseries.py +0 -0
  227. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/image/__init__.py +0 -0
  228. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/image/imagewatcher.py +0 -0
  229. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/image/imagewriter.py +0 -0
  230. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/image/pipeline.py +0 -0
  231. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/image/seeing.py +0 -0
  232. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/module.py +0 -0
  233. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/__init__.py +0 -0
  234. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/_base.py +0 -0
  235. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/_baseguiding.py +0 -0
  236. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/acquisition.py +0 -0
  237. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  238. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/dummyguiding.py +0 -0
  239. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  240. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  241. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  242. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  243. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  244. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/robotic/__init__.py +0 -0
  245. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/robotic/mastermind.py +0 -0
  246. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/robotic/pointing.py +0 -0
  247. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/robotic/scheduler.py +0 -0
  248. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/robotic/scriptrunner.py +0 -0
  249. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/roof/__init__.py +0 -0
  250. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/roof/basedome.py +0 -0
  251. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/roof/dummyroof.py +0 -0
  252. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/telescope/__init__.py +0 -0
  253. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/telescope/basetelescope.py +0 -0
  254. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
  255. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
  256. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/test/__init__.py +0 -0
  257. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/test/standalone.py +0 -0
  258. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/__init__.py +0 -0
  259. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/fluentlogger.py +0 -0
  260. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/httpfilecache.py +0 -0
  261. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/kiosk.py +0 -0
  262. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/matrix.py +0 -0
  263. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/stellarium.py +0 -0
  264. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/telegram.py +0 -0
  265. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/utils/trigger.py +0 -0
  266. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/weather/__init__.py +0 -0
  267. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/weather/mockweather.py +0 -0
  268. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/weather/weather.py +0 -0
  269. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/weather/weather_api.py +0 -0
  270. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/modules/weather/weather_state.py +0 -0
  271. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/object.py +0 -0
  272. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/py.typed +0 -0
  273. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/__init__.py +0 -0
  274. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/observation.py +0 -0
  275. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/__init__.py +0 -0
  276. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  277. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  278. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  279. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
  280. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  281. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  282. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  283. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  284. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  285. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  286. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  287. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  288. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  289. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  290. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  291. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  292. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  293. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/random.py +0 -0
  294. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  295. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  296. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  297. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  298. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
  299. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  300. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
  301. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
  302. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  303. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  304. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  305. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  306. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/targets/target.py +0 -0
  307. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  308. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/__init__.py +0 -0
  309. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  310. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
  311. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
  312. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
  313. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/control/__init__.py +0 -0
  314. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/control/cases.py +0 -0
  315. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/control/conditional.py +0 -0
  316. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/control/parallel.py +0 -0
  317. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/control/selector.py +0 -0
  318. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/control/sequential.py +0 -0
  319. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  320. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
  321. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
  322. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  323. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/script.py +0 -0
  324. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  325. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
  326. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  327. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/scripts/utils/log.py +0 -0
  328. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/__init__.py +0 -0
  329. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/backend/__init__.py +0 -0
  330. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/backend/observationarchive.py +0 -0
  331. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/backend/taskarchive.py +0 -0
  332. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  333. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  334. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  335. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/__init__.py +0 -0
  336. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/_portal.py +0 -0
  337. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  338. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  339. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/configdb.py +0 -0
  340. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  341. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  342. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  343. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
  344. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
  345. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  346. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/task.py +0 -0
  347. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  348. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  349. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/memory/__init__.py +0 -0
  350. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  351. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  352. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/observationarchive.py +0 -0
  353. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/storage/taskarchive.py +0 -0
  354. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/task.py +0 -0
  355. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/taskrunner.py +0 -0
  356. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/__init__.py +0 -0
  357. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/archive/__init__.py +0 -0
  358. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/archive/archive.py +0 -0
  359. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  360. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  361. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  362. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  363. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
  364. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  365. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  366. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  367. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  368. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  369. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  370. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  371. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  372. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  373. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  374. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/__init__.py +0 -0
  375. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/average.py +0 -0
  376. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/cache.py +0 -0
  377. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/config.py +0 -0
  378. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/config_schema.py +0 -0
  379. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/coordinates.py +0 -0
  380. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/curvefit.py +0 -0
  381. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/enums.py +0 -0
  382. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/exceptions.py +0 -0
  383. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/fits.py +0 -0
  384. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/focusseries/__init__.py +0 -0
  385. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/focusseries/base.py +0 -0
  386. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/focusseries/photometry.py +0 -0
  387. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/focusseries/projection.py +0 -0
  388. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/grids/__init__.py +0 -0
  389. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/grids/filters.py +0 -0
  390. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/grids/grid.py +0 -0
  391. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/grids/gridnode.py +0 -0
  392. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/grids/pipeline.py +0 -0
  393. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/__init__.py +0 -0
  394. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/camera/__init__.py +0 -0
  395. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  396. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  397. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  398. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  399. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  400. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  401. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  402. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/http.py +0 -0
  403. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/influxdb.py +0 -0
  404. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/logger.py +0 -0
  405. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/logging/__init__.py +0 -0
  406. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/logging/context.py +0 -0
  407. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/logging/resolvableerror.py +0 -0
  408. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/modulegui.py +0 -0
  409. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/offsets/__init__.py +0 -0
  410. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  411. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/offsets/applyoffsets.py +0 -0
  412. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  413. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/parallel.py +0 -0
  414. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/pipeline/__init__.py +0 -0
  415. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/pipeline/night.py +0 -0
  416. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/pipeline/pipeline.py +0 -0
  417. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/publisher/__init__.py +0 -0
  418. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/publisher/csv.py +0 -0
  419. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/publisher/http.py +0 -0
  420. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/publisher/log.py +0 -0
  421. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/publisher/multi.py +0 -0
  422. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/publisher/publisher.py +0 -0
  423. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/serialization.py +0 -0
  424. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/shellcommand.py +0 -0
  425. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/threads/__init__.py +0 -0
  426. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/threads/lockwithabort.py +0 -0
  427. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/time.py +0 -0
  428. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/utils/units.py +0 -0
  429. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/version.py +0 -0
  430. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/__init__.py +0 -0
  431. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/archivefile.py +0 -0
  432. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/bufferedfile.py +0 -0
  433. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/file.py +0 -0
  434. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/filelists/__init__.py +0 -0
  435. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/filelists/filelist.py +0 -0
  436. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/filelists/testing.py +0 -0
  437. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/httpfile.py +0 -0
  438. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/localfile.py +0 -0
  439. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/memfile.py +0 -0
  440. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/sftpfile.py +0 -0
  441. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/smbfile.py +0 -0
  442. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/sshfile.py +0 -0
  443. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/tempfile.py +0 -0
  444. {pyobs_core-2.0.0.dev21 → pyobs_core-2.0.0.dev24}/pyobs/vfs/vfs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-core
3
- Version: 2.0.0.dev21
3
+ Version: 2.0.0.dev24
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -133,16 +133,14 @@ class Comm:
133
133
  except IndexError:
134
134
  return None
135
135
 
136
- # collect capabilities (fixed at proxy construction time)
137
- capabilities: dict[type[Interface], Any] = {}
136
+ # create new proxy
137
+ proxy = Proxy(self, client, interfaces)
138
+
139
+ # fetch capabilities in the background, so a peer that isn't fully up yet doesn't block
140
+ # proxy construction -- Proxy.wait_for_capabilities() can be used to wait for the result
138
141
  for interface in interfaces:
139
142
  if interface.capabilities is not None:
140
- cap = await self._get_capabilities(client, interface)
141
- if cap is not None:
142
- capabilities[interface] = cap
143
-
144
- # create new proxy
145
- proxy = Proxy(self, client, interfaces, capabilities)
143
+ asyncio.create_task(self._fetch_and_update_capabilities(client, interface, proxy))
146
144
 
147
145
  # subscribe to state
148
146
  for interface in interfaces:
@@ -151,9 +149,14 @@ class Comm:
151
149
 
152
150
  self._proxies[client] = proxy
153
151
 
154
- # return proxy
155
152
  return self._proxies[client]
156
153
 
154
+ async def _fetch_and_update_capabilities(self, client: str, interface: type[Interface], proxy: Proxy) -> None:
155
+ """Fetch capabilities for a single interface and push them into the given proxy once available."""
156
+ cap = await self._get_capabilities(client, interface)
157
+ if cap is not None:
158
+ proxy.update_capabilities(interface, cap)
159
+
157
160
  async def _resolve_proxy(
158
161
  self, name_or_object: str | object, obj_type: type[ProxyType] | None = None
159
162
  ) -> Any | ProxyType:
@@ -445,6 +448,25 @@ class Comm:
445
448
  if not event_class.local:
446
449
  await self._register_events(event_classes, handler)
447
450
 
451
+ async def unregister_event(
452
+ self, event_class: type[Event], handler: Callable[[Event, str], Coroutine[Any, Any, bool]]
453
+ ) -> None:
454
+ """Remove a handler previously added via register_event().
455
+
456
+ Leaves the event type itself registered (other handlers may still be interested in it,
457
+ and the module may still want to advertise/receive it), only stops calling this handler.
458
+
459
+ Args:
460
+ event_class: Class of event that was registered.
461
+ handler: The exact handler that was passed to register_event().
462
+ """
463
+
464
+ # same derived-events expansion as register_event(), so this mirrors exactly what was added
465
+ for ev in self._get_derived_events(event_class):
466
+ handlers = self._event_handlers.get(ev)
467
+ if handlers is not None and handler in handlers:
468
+ handlers.remove(handler)
469
+
448
470
  async def _register_events(
449
471
  self, events: list[type[Event]], handler: Callable[[Event, str], Coroutine[Any, Any, bool]] | None = None
450
472
  ) -> None:
@@ -62,6 +62,7 @@ class Proxy:
62
62
  # store state and capabilities
63
63
  self._state: dict[type[Interface], Any] = {}
64
64
  self._capabilities: dict[type[Interface], Any] = capabilities if capabilities is not None else {}
65
+ self._capabilities_events: dict[type[Interface], asyncio.Event] = {}
65
66
 
66
67
  @property
67
68
  def name(self) -> str:
@@ -175,8 +176,18 @@ class Proxy:
175
176
  """Latest known state for the given interface, or None if nothing has arrived yet."""
176
177
  return self._state.get(interface)
177
178
 
179
+ def update_capabilities(self, interface: type[Interface], capabilities: Any) -> None:
180
+ """Called by Comm once capabilities for an interface arrive.
181
+
182
+ Not intended to be called directly by module code.
183
+ """
184
+ self._capabilities[interface] = capabilities
185
+ event = self._capabilities_events.get(interface)
186
+ if event is not None:
187
+ event.set()
188
+
178
189
  def get_capabilities(self, interface: type[Interface]) -> Any | None:
179
- """Capabilities for the given interface, populated once at Proxy construction."""
190
+ """Capabilities for the given interface, or None if not (yet) fetched."""
180
191
  return self._capabilities.get(interface)
181
192
 
182
193
  async def wait_for_state(
@@ -203,6 +214,23 @@ class Proxy:
203
214
 
204
215
  return self._state.get(interface)
205
216
 
217
+ async def wait_for_capabilities(
218
+ self,
219
+ interface: type[Interface],
220
+ timeout: float = 10.0,
221
+ ) -> Any | None:
222
+ """Return capabilities immediately if available, otherwise wait for the background fetch to complete."""
223
+ if self._capabilities.get(interface) is not None:
224
+ return self._capabilities[interface]
225
+
226
+ event = self._capabilities_events.setdefault(interface, asyncio.Event())
227
+ try:
228
+ await asyncio.wait_for(event.wait(), timeout=timeout)
229
+ except TimeoutError:
230
+ pass
231
+
232
+ return self._capabilities.get(interface)
233
+
206
234
 
207
235
  class _ProxyContext(Generic[ProxyType]):
208
236
  """Returned by Comm.proxy() / Object.proxy() / Comm.safe_proxy(). Must be used as:
@@ -48,7 +48,7 @@ class XmppClient(slixmpp.ClientXMPP):
48
48
  self.register_plugin("xep_0199") # XMPP Ping
49
49
 
50
50
  # enable keep alive pings
51
- self["xep_0199"].enable_keepalive()
51
+ self.plugin["xep_0199"].enable_keepalive()
52
52
 
53
53
  # handle session_start and message events
54
54
  self.add_event_handler("session_start", self.session_start)
@@ -909,20 +909,34 @@ class XmppComm(Comm):
909
909
  return self._capabilities.get(interface)
910
910
 
911
911
  async def _get_capabilities(self, module: str, interface: type[Interface]) -> Any | None:
912
- """Fetch and deserialize capabilities for a remote module's interface."""
912
+ """Fetch and deserialize capabilities for a remote module's interface.
913
+
914
+ Retries a few times, since this may run right as the peer is still starting up and hasn't
915
+ published its capabilities yet.
916
+ """
913
917
  if interface.capabilities is None:
914
918
  return None
915
- # Use full JID (with resource) if we know it — bare JID may not route correctly
916
- full_jid = next(
917
- (jid for jid in self._online_clients if jid.startswith(f"{module}@")),
918
- f"{module}@{self._domain}",
919
- )
920
- jid = full_jid
921
919
  ns = f"urn:pyobs:capabilities:{interface.__name__}:{interface.version}"
922
- try:
923
- result = await asyncio.wait_for(self.client.plugin["xep_0030"].get_info(jid=JID(jid)), timeout=10.0)
924
- except (TimeoutError, Exception) as e:
925
- log.warning("Failed to get capabilities for %s from %s: %s", interface.__name__, module, e)
920
+
921
+ result = None
922
+ last_error: BaseException | None = None
923
+ for attempt in range(3):
924
+ # Use full JID (with resource) if we know it — bare JID may not route correctly
925
+ full_jid = next(
926
+ (jid for jid in self._online_clients if jid.startswith(f"{module}@")),
927
+ f"{module}@{self._domain}",
928
+ )
929
+ try:
930
+ result = await asyncio.wait_for(
931
+ self.client.plugin["xep_0030"].get_info(jid=JID(full_jid)), timeout=10.0
932
+ )
933
+ break
934
+ except (TimeoutError, Exception) as e:
935
+ last_error = e
936
+ if attempt < 2:
937
+ await asyncio.sleep(2)
938
+ else:
939
+ log.warning("Failed to get capabilities for %s from %s: %s", interface.__name__, module, last_error)
926
940
  return None
927
941
  log.debug("get_capabilities disco result XML: %s", ET.tostring(result.xml).decode()[:500])
928
942
  # result.xml is the <iq> — the <query> is its child, capabilities are grandchildren
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABCMeta, abstractmethod
4
+ from dataclasses import dataclass, field
5
+ from typing import Any
6
+
7
+ from ..utils.time import Time
8
+ from .IAbortable import IAbortable
9
+
10
+
11
+ @dataclass
12
+ class DataSequenceState:
13
+ count_total: int # 0 when idle / no sequence running
14
+ count_left: int
15
+ time: Time = field(default_factory=Time.now)
16
+
17
+
18
+ class IDataSequence(IAbortable, metaclass=ABCMeta):
19
+ """The module can grab a counted sequence of data (images, spectra, ...)."""
20
+
21
+ __module__ = "pyobs.interfaces"
22
+
23
+ state = DataSequenceState
24
+
25
+ @abstractmethod
26
+ async def grab_sequence(self, count: int, broadcast: bool = True, **kwargs: Any) -> None:
27
+ """Start a sequence of `count` grabs. Returns immediately; progress is available via
28
+ the pushed DataSequenceState.
29
+
30
+ Args:
31
+ count: Number of grabs to take.
32
+ broadcast: Broadcast existence of each grab.
33
+
34
+ Raises:
35
+ GrabImageError: If the device is already busy (exposing or already running a
36
+ sequence).
37
+ """
38
+ ...
39
+
40
+ @abstractmethod
41
+ async def abort_sequence(self, **kwargs: Any) -> None:
42
+ """Stop the sequence after the current grab. The grab currently in progress, if any,
43
+ finishes normally; no further grabs in the sequence are started.
44
+
45
+ This is the graceful counterpart to IAbortable.abort(), which remains the hard-stop
46
+ path: it cancels the running grab immediately *and* the remaining sequence count.
47
+ """
48
+ ...
49
+
50
+
51
+ __all__ = ["IDataSequence", "DataSequenceState"]
@@ -23,6 +23,7 @@ from .ICamera import ICamera
23
23
  from .IConfig import ConfigCapabilities, ConfigScalar, ConfigValue, IConfig
24
24
  from .ICooling import CoolingState, ICooling
25
25
  from .IData import IData
26
+ from .IDataSequence import DataSequenceState, IDataSequence
26
27
  from .IDome import IDome
27
28
  from .IExposure import ExposureState, IExposure
28
29
  from .IExposureTime import ExposureTimeState, IExposureTime
@@ -94,6 +95,8 @@ __all__ = [
94
95
  "ConfigValue",
95
96
  "ICooling",
96
97
  "CoolingState",
98
+ "IDataSequence",
99
+ "DataSequenceState",
97
100
  "IDome",
98
101
  "IExposure",
99
102
  "ExposureState",
@@ -4,7 +4,7 @@ import logging
4
4
  from typing import Any
5
5
 
6
6
  from pyobs.events import MotionStatusChangedEvent
7
- from pyobs.interfaces import IMotion, MotionState
7
+ from pyobs.interfaces import IMotion, IReady, MotionState, ReadyState
8
8
  from pyobs.modules import Module
9
9
  from pyobs.utils.enums import MotionStatus
10
10
 
@@ -31,6 +31,7 @@ class MotionStatusMixin:
31
31
  if isinstance(self, Module) and self._comm:
32
32
  await self.comm.register_event(MotionStatusChangedEvent)
33
33
  await self.comm.set_state(IMotion, MotionState(status=self.__motion_status))
34
+ await self.comm.set_state(IReady, ReadyState(ready=self._is_ready()))
34
35
 
35
36
  async def _change_motion_status(self, status: MotionStatus, interface: str | None = None) -> None:
36
37
  """Change motion status and send event,
@@ -88,6 +89,7 @@ class MotionStatusMixin:
88
89
  MotionStatusChangedEvent(status=this.__motion_status, interfaces=this.__motion_status_single)
89
90
  )
90
91
  await self.comm.set_state(IMotion, MotionState(status=this.__motion_status))
92
+ await self.comm.set_state(IReady, ReadyState(ready=self._is_ready()))
91
93
 
92
94
  def _combine_motion_status(self) -> MotionStatus:
93
95
  """Method for combining motion statuses for individual interfaces into the global one. Can be overriden."""
@@ -111,6 +113,16 @@ class MotionStatusMixin:
111
113
  # otherwise just take status of first interface
112
114
  return self.__motion_status_single[self.__motion_status_interfaces[0]]
113
115
 
116
+ def _is_ready(self) -> bool:
117
+ """Whether the device is ready, for publishing as `IReady` state. Override for device-specific criteria."""
118
+ return self.motion_status() not in [
119
+ MotionStatus.PARKED,
120
+ MotionStatus.INITIALIZING,
121
+ MotionStatus.PARKING,
122
+ MotionStatus.ERROR,
123
+ MotionStatus.UNKNOWN,
124
+ ]
125
+
114
126
  def motion_status(self, device: str | None = None) -> MotionStatus:
115
127
  """Returns current motion status (synchronous, for internal use).
116
128
 
@@ -9,12 +9,14 @@ from typing import Any, NamedTuple
9
9
  import numpy as np
10
10
  from astropy.io import fits
11
11
 
12
- from pyobs.events import ExposureStatusChangedEvent, NewImageEvent
12
+ from pyobs.events import BadWeatherEvent, Event, ExposureStatusChangedEvent, NewImageEvent
13
13
  from pyobs.images import Image
14
14
  from pyobs.interfaces import (
15
+ DataSequenceState,
15
16
  ExposureState,
16
17
  ExposureTimeState,
17
18
  ICamera,
19
+ IDataSequence,
18
20
  IExposure,
19
21
  IExposureTime,
20
22
  IImageType,
@@ -44,7 +46,7 @@ async def calc_expose_timeout(camera: BaseCamera, *args: Any, **kwargs: Any) ->
44
46
  return camera._exposure_time + 30
45
47
 
46
48
 
47
- class BaseCamera(Module, ImageFitsHeaderMixin, ICamera, IExposureTime, IImageType, metaclass=ABCMeta):
49
+ class BaseCamera(Module, ImageFitsHeaderMixin, ICamera, IExposureTime, IImageType, IDataSequence, metaclass=ABCMeta):
48
50
  """Base class for all camera modules."""
49
51
 
50
52
  __module__ = "pyobs.modules.camera"
@@ -102,6 +104,11 @@ class BaseCamera(Module, ImageFitsHeaderMixin, ICamera, IExposureTime, IImageTyp
102
104
  # multi-threading
103
105
  self.expose_abort = asyncio.Event()
104
106
 
107
+ # grab_sequence() state -- count_left > 0 means a sequence is currently running
108
+ # (also between individual grabs, while _camera_status is briefly IDLE again)
109
+ self._sequence_count_left = 0
110
+ self._sequence_task: asyncio.Task[None] | None = None
111
+
105
112
  # register exception
106
113
  exc.register_exception(exc.GrabImageError, 3, timespan=600, callback=self._default_remote_error_callback)
107
114
 
@@ -113,6 +120,7 @@ class BaseCamera(Module, ImageFitsHeaderMixin, ICamera, IExposureTime, IImageTyp
113
120
  if self._comm:
114
121
  await self.comm.register_event(NewImageEvent)
115
122
  await self.comm.register_event(ExposureStatusChangedEvent)
123
+ await self.comm.register_event(BadWeatherEvent, self._abort_weather)
116
124
 
117
125
  # publish initial states
118
126
  await self.comm.set_state(IExposureTime, ExposureTimeState(exposure_time=self._exposure_time))
@@ -125,6 +133,7 @@ class BaseCamera(Module, ImageFitsHeaderMixin, ICamera, IExposureTime, IImageTyp
125
133
  exposure_time_left=0.0,
126
134
  ),
127
135
  )
136
+ await self.comm.set_state(IDataSequence, DataSequenceState(count_total=0, count_left=0))
128
137
 
129
138
  async def set_exposure_time(self, exposure_time: float, **kwargs: Any) -> None:
130
139
  """Set the exposure time in seconds.
@@ -361,6 +370,55 @@ class BaseCamera(Module, ImageFitsHeaderMixin, ICamera, IExposureTime, IImageTyp
361
370
  # return filename
362
371
  return filename
363
372
 
373
+ async def grab_sequence(self, count: int, broadcast: bool = True, **kwargs: Any) -> None:
374
+ """Start a sequence of `count` images. Returns immediately; progress is available via
375
+ the pushed DataSequenceState.
376
+
377
+ Args:
378
+ count: Number of images to take.
379
+ broadcast: Broadcast existence of each image.
380
+
381
+ Raises:
382
+ CameraException: If camera is already busy (exposing or already running a sequence).
383
+ """
384
+ if count < 1:
385
+ raise ValueError("count must be >= 1.")
386
+
387
+ # already running a sequence, or mid-exposure outside of one?
388
+ if self._sequence_count_left > 0 or self._camera_status != ExposureStatus.IDLE:
389
+ raise CameraException("Cannot start new sequence because camera is not idle.")
390
+
391
+ log.info("Starting sequence of %d images...", count)
392
+ self._sequence_count_left = count
393
+ await self.comm.set_state(IDataSequence, DataSequenceState(count_total=count, count_left=count))
394
+ self._sequence_task = asyncio.create_task(self._run_sequence(count, broadcast))
395
+
396
+ async def _run_sequence(self, count_total: int, broadcast: bool) -> None:
397
+ """Runs a sequence of grab_data() calls, started by grab_sequence()."""
398
+ try:
399
+ while self._sequence_count_left > 0:
400
+ try:
401
+ await self.grab_data(broadcast=broadcast)
402
+ except exc.PyObsError:
403
+ log.exception("Grab failed during sequence, aborting sequence.")
404
+ break
405
+ self._sequence_count_left -= 1
406
+ await self.comm.set_state(
407
+ IDataSequence, DataSequenceState(count_total=count_total, count_left=self._sequence_count_left)
408
+ )
409
+ finally:
410
+ log.info("Finished sequence.")
411
+ self._sequence_count_left = 0
412
+ self._sequence_task = None
413
+ await self.comm.set_state(IDataSequence, DataSequenceState(count_total=0, count_left=0))
414
+
415
+ async def abort_sequence(self, **kwargs: Any) -> None:
416
+ """Stop the sequence after the current image. The image currently exposing, if any,
417
+ finishes normally; no further images in the sequence are started.
418
+ """
419
+ log.info("Aborting sequence after current image...")
420
+ self._sequence_count_left = 0
421
+
364
422
  async def _abort_exposure(self) -> None:
365
423
  """Abort the running exposure. Should be implemented by derived class.
366
424
 
@@ -379,6 +437,7 @@ class BaseCamera(Module, ImageFitsHeaderMixin, ICamera, IExposureTime, IImageTyp
379
437
  # set abort event
380
438
  log.info("Aborting current image and sequence...")
381
439
  self.expose_abort.set()
440
+ self._sequence_count_left = 0
382
441
 
383
442
  # do camera-specific abort
384
443
  await self._abort_exposure()
@@ -387,6 +446,11 @@ class BaseCamera(Module, ImageFitsHeaderMixin, ICamera, IExposureTime, IImageTyp
387
446
  while self._camera_status == ExposureStatus.EXPOSING:
388
447
  await asyncio.sleep(0.1)
389
448
 
449
+ async def _abort_weather(self, event: Event, sender: str, **kwargs: Any) -> bool:
450
+ """Abort on bad weather."""
451
+ await self.abort()
452
+ return True
453
+
390
454
  @staticmethod
391
455
  def set_biassec_trimsec(hdr: fits.Header, left: int, top: int, width: int, height: int) -> None:
392
456
  """Calculates and sets the BIASSEC and TRIMSEC areas.
@@ -9,7 +9,7 @@ from typing import Any, NamedTuple
9
9
  from astropy.io import fits
10
10
 
11
11
  from pyobs.events import ExposureStatusChangedEvent, NewSpectrumEvent
12
- from pyobs.interfaces import ISpectrograph
12
+ from pyobs.interfaces import ExposureState, IExposure, ISpectrograph
13
13
  from pyobs.mixins.fitsheader import SpectrumFitsHeaderMixin
14
14
  from pyobs.modules import Module, timeout
15
15
  from pyobs.utils.enums import ExposureStatus
@@ -60,6 +60,15 @@ class BaseSpectrograph(Module, SpectrumFitsHeaderMixin, ISpectrograph, metaclass
60
60
  if self._comm is None:
61
61
  log.warning("No comm module given, will not be able to signal new images!")
62
62
 
63
+ async def open(self) -> None:
64
+ """Open module."""
65
+ await Module.open(self)
66
+
67
+ # publish initial state
68
+ await self.comm.set_state(
69
+ IExposure, ExposureState(status=self._spectrograph_status, progress=0.0, exposure_time_left=0.0)
70
+ )
71
+
63
72
  @abstractmethod
64
73
  async def _expose(self, abort_event: asyncio.Event) -> fits.HDUList:
65
74
  """Actually do the exposure, should be implemented by derived classes.
@@ -192,6 +201,7 @@ class BaseSpectrograph(Module, SpectrumFitsHeaderMixin, ISpectrograph, metaclass
192
201
 
193
202
  # set it
194
203
  self._spectrograph_status = status
204
+ await self.comm.set_state(IExposure, ExposureState(status=status, progress=0.0, exposure_time_left=0.0))
195
205
 
196
206
  async def abort(self, **kwargs: Any) -> None:
197
207
  """Aborts the current exposure.
@@ -3,11 +3,11 @@ from typing import Any
3
3
 
4
4
  from pyobs.events import NewImageEvent
5
5
  from pyobs.images import Image
6
- from pyobs.interfaces import ICamera
6
+ from pyobs.interfaces import ExposureState, ICamera, IExposure
7
7
  from pyobs.mixins import ImageFitsHeaderMixin
8
8
  from pyobs.mixins.pipeline import PipelineMixin
9
9
  from pyobs.modules import Module
10
- from pyobs.utils.enums import ImageType
10
+ from pyobs.utils.enums import ExposureStatus, ImageType
11
11
  from pyobs.utils.exceptions import GrabImageError
12
12
 
13
13
  log = logging.getLogger(__name__)
@@ -24,6 +24,15 @@ class PipelineCamera(Module, ICamera, ImageFitsHeaderMixin, PipelineMixin):
24
24
  PipelineMixin.__init__(self, **kwargs)
25
25
  ImageFitsHeaderMixin.__init__(self, **kwargs)
26
26
 
27
+ async def open(self) -> None:
28
+ """Open module."""
29
+ await Module.open(self)
30
+
31
+ # publish initial state
32
+ await self.comm.set_state(
33
+ IExposure, ExposureState(status=ExposureStatus.IDLE, progress=0.0, exposure_time_left=0.0)
34
+ )
35
+
27
36
  async def grab_data(self, broadcast: bool = True, **kwargs: Any) -> str:
28
37
  """Grabs an image and returns reference.
29
38
 
@@ -11,13 +11,16 @@ from pyobs.interfaces import (
11
11
  ICamera,
12
12
  IFilters,
13
13
  IFlatField,
14
+ IMotion,
14
15
  IReady,
15
16
  ITelescope,
17
+ MotionState,
16
18
  ReadyState,
17
19
  )
18
20
  from pyobs.modules import Module, timeout
19
21
  from pyobs.robotic.utils.skyflats import FlatFielder
20
22
  from pyobs.utils import exceptions as exc
23
+ from pyobs.utils.enums import MotionStatus
21
24
  from pyobs.utils.publisher import CsvPublisher
22
25
 
23
26
  log = logging.getLogger(__name__)
@@ -121,6 +124,9 @@ class FlatField(Module, IFlatField, IBinning, IFilters):
121
124
  if self._filter is not None:
122
125
  await self.comm.set_state(IFilters, FilterState(filter=self._filter))
123
126
  await self.comm.set_state(IReady, ReadyState(ready=True))
127
+ # IFilters extends IMotion, but FlatField isn't itself a moving device -- publish a
128
+ # static state so the pubsub node exists for anything that subscribes to it
129
+ await self.comm.set_state(IMotion, MotionState(status=MotionStatus.IDLE))
124
130
 
125
131
  async def close(self) -> None:
126
132
  """Close module."""
@@ -156,7 +156,10 @@ class FocusModel(Module, IFocusModel):
156
156
  await self._calc_focus_model()
157
157
 
158
158
  # publish initial states
159
- await self.comm.set_state(IFocusModel, OptimalFocusState(focus=await self._get_optimal_focus()))
159
+ try:
160
+ await self.comm.set_state(IFocusModel, OptimalFocusState(focus=await self._get_optimal_focus()))
161
+ except ValueError as e:
162
+ log.warning("Could not publish initial focus model state: %s", e)
160
163
 
161
164
  async def _update(self) -> None:
162
165
  # wait a little
@@ -170,7 +173,12 @@ class FocusModel(Module, IFocusModel):
170
173
  continue
171
174
 
172
175
  # update states
173
- await self.comm.set_state(IFocusModel, OptimalFocusState(focus=await self._get_optimal_focus()))
176
+ try:
177
+ await self.comm.set_state(IFocusModel, OptimalFocusState(focus=await self._get_optimal_focus()))
178
+ except ValueError as e:
179
+ log.warning("Could not update focus model state: %s", e)
180
+ await asyncio.sleep(self._interval)
181
+ continue
174
182
 
175
183
  # get focuser
176
184
  if not await self.has_proxy(self._focuser, IFocuser):
@@ -282,7 +290,10 @@ class FocusModel(Module, IFocusModel):
282
290
 
283
291
  # get temperatures
284
292
  async with self.proxy(cfg["module"], ITemperatures) as proxy:
285
- temp_state = proxy.get_state(ITemperatures)
293
+ try:
294
+ temp_state = await proxy.wait_for_state(ITemperatures)
295
+ except TimeoutError:
296
+ raise ValueError(f"Timeout waiting for temperatures from module {cfg['module']}.")
286
297
  module_temps[cfg["module"]] = (
287
298
  {r.name: r.value for r in temp_state.readings} if temp_state is not None else {}
288
299
  )
@@ -41,6 +41,11 @@ class AutoGuiding(BaseGuiding, CameraSettingsMixin):
41
41
  # add thread func
42
42
  self.add_background_task(self._auto_guiding)
43
43
 
44
+ async def open(self) -> None:
45
+ """Open module."""
46
+ await BaseGuiding.open(self)
47
+ await self.comm.set_state(IExposureTime, ExposureTimeState(exposure_time=self._default_exposure_time))
48
+
44
49
  async def set_exposure_time(self, exposure_time: float, **kwargs: Any) -> None:
45
50
  """Set the exposure time in seconds.
46
51
 
@@ -4,6 +4,7 @@ from typing import Any
4
4
 
5
5
  from pyobs.events import Event, NewImageEvent
6
6
  from pyobs.images import Image
7
+ from pyobs.interfaces import ExposureTimeState, IExposureTime
7
8
 
8
9
  from ._baseguiding import BaseGuiding
9
10
 
@@ -33,6 +34,10 @@ class ScienceFrameAutoGuiding(BaseGuiding):
33
34
  log.info("Subscribing to new image events...")
34
35
  await self.comm.register_event(NewImageEvent, self.add_image)
35
36
 
37
+ # exposure time is dictated by incoming science frames, not settable -- but IAutoGuiding
38
+ # requires IExposureTime, so publish a placeholder state for the pubsub node to exist
39
+ await self.comm.set_state(IExposureTime, ExposureTimeState(exposure_time=0.0))
40
+
36
41
  async def set_exposure_time(self, exposure_time: float, **kwargs: Any) -> None:
37
42
  """Set the exposure time for the auto-guider.
38
43
 
@@ -4,7 +4,7 @@ import logging
4
4
  from abc import ABCMeta
5
5
  from typing import Any
6
6
 
7
- from pyobs.interfaces import FitsHeaderEntry, IFitsHeaderBefore, IReady, IRoof, ReadyState
7
+ from pyobs.interfaces import FitsHeaderEntry, IFitsHeaderBefore, IRoof
8
8
  from pyobs.mixins import MotionStatusMixin, WeatherAwareMixin
9
9
  from pyobs.modules import Module
10
10
  from pyobs.utils.enums import MotionStatus
@@ -32,9 +32,6 @@ class BaseRoof(WeatherAwareMixin, MotionStatusMixin, IRoof, IFitsHeaderBefore, M
32
32
  await WeatherAwareMixin.open(self)
33
33
  await MotionStatusMixin.open(self)
34
34
 
35
- # publish initial ready state
36
- await self.comm.set_state(IReady, ReadyState(ready=self._is_ready()))
37
-
38
35
  async def get_fits_header_before(
39
36
  self, namespaces: list[str] | None = None, **kwargs: Any
40
37
  ) -> dict[str, FitsHeaderEntry]:
@@ -53,15 +50,5 @@ class BaseRoof(WeatherAwareMixin, MotionStatusMixin, IRoof, IFitsHeaderBefore, M
53
50
  )
54
51
  }
55
52
 
56
- def _is_ready(self) -> bool:
57
- """The roof is ready if it is open (not parked, initializing, parking, error, or unknown)."""
58
- return self.motion_status() not in [
59
- MotionStatus.PARKED,
60
- MotionStatus.INITIALIZING,
61
- MotionStatus.PARKING,
62
- MotionStatus.ERROR,
63
- MotionStatus.UNKNOWN,
64
- ]
65
-
66
53
 
67
54
  __all__ = ["BaseRoof"]
@@ -12,6 +12,7 @@ from astropy.coordinates import SkyCoord
12
12
 
13
13
  from pyobs.events import FilterChangedEvent
14
14
  from pyobs.interfaces import (
15
+ AltAzState,
15
16
  FiltersCapabilities,
16
17
  FilterState,
17
18
  FitsHeaderEntry,
@@ -137,6 +138,14 @@ class _DummyTelescopeBase(
137
138
  def _position_radec(self) -> tuple[float, float] | None:
138
139
  return float(self._position.ra.degree), float(self._position.dec.degree)
139
140
 
141
+ async def _publish_altaz(self) -> None:
142
+ """Publish the current position as IPointingAltAz state, derived from RA/Dec and the observer location."""
143
+ if self._observer is not None:
144
+ altaz = self.observer.altaz(Time.now(), self._position)
145
+ await self.comm.set_state(
146
+ IPointingAltAz, AltAzState(alt=float(altaz.alt.degree), az=float(altaz.az.degree))
147
+ )
148
+
140
149
  async def _sim_change_status(self, status: MotionStatus) -> None:
141
150
  if status != self._sim_status:
142
151
  self._sim_status = status
@@ -168,6 +177,7 @@ class _DummyTelescopeBase(
168
177
  dec=float(self._position.dec.degree),
169
178
  ),
170
179
  )
180
+ await self._publish_altaz()
171
181
  else:
172
182
  dra = vra / length * self._speed / np.cos(np.radians(self._position.dec.degree)) * u.deg
173
183
  ddec = vdec / length * self._speed * u.deg
@@ -182,6 +192,7 @@ class _DummyTelescopeBase(
182
192
  IPointingRaDec,
183
193
  RaDecState(ra=float(self._position.ra.degree), dec=float(self._position.dec.degree)),
184
194
  )
195
+ await self._publish_altaz()
185
196
 
186
197
  drift_ra = random.gauss(self._drift_rate[0], max(self._drift_rate[0] / 10.0, 1e-9))
187
198
  drift_dec = random.gauss(self._drift_rate[1], max(self._drift_rate[1] / 10.0, 1e-9))
@@ -224,6 +235,7 @@ class _DummyTelescopeBase(
224
235
  dec=float(self._position.dec.degree),
225
236
  ),
226
237
  )
238
+ await self._publish_altaz()
227
239
  await self.comm.set_capabilities(ITrackingMode, TrackingModeCapabilities(modes=self._tracking_modes))
228
240
  await self.comm.set_capabilities(ITrackingRate, TrackingRateCapabilities(min_update_interval=0.0))
229
241
  await self.comm.set_state(ITrackingMode, TrackingModeState(mode=self._tracking_mode))