pyobs-core 2.0.0.dev20__tar.gz → 2.0.0.dev23__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.dev20 → pyobs_core-2.0.0.dev23}/PKG-INFO +1 -1
  2. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/comm.py +19 -0
  3. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xmppcomm.py +10 -2
  4. pyobs_core-2.0.0.dev23/pyobs/interfaces/IDataSequence.py +51 -0
  5. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/__init__.py +3 -0
  6. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/motionstatus.py +13 -1
  7. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/basecamera.py +66 -2
  8. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/basespectrograph.py +11 -1
  9. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/pipelinecamera.py +11 -2
  10. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/flatfield/flatfield.py +6 -0
  11. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/focus/focusmodel.py +14 -3
  12. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/autoguiding.py +5 -0
  13. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/scienceframeguiding.py +5 -0
  14. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/roof/baseroof.py +1 -14
  15. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/telescope/_dummytelescopebase.py +12 -0
  16. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/telescope/dummysolartelescope.py +12 -0
  17. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/dummymode.py +1 -2
  18. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/flatfielder.py +7 -1
  19. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyproject.toml +1 -1
  20. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/.gitignore +0 -0
  21. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/LICENSE +0 -0
  22. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/__init__.py +0 -0
  23. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/application.py +0 -0
  24. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/background_task.py +0 -0
  25. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/cli/__init__.py +0 -0
  26. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/cli/_cli.py +0 -0
  27. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/cli/pyobs.py +0 -0
  28. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/cli/pyobsd.py +0 -0
  29. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/cli/pyobsw.py +0 -0
  30. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/__init__.py +0 -0
  31. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/commlogging.py +0 -0
  32. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/dummy/__init__.py +0 -0
  33. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/dummy/dummycomm.py +0 -0
  34. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/local/__init__.py +0 -0
  35. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/local/localcomm.py +0 -0
  36. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/local/localnetwork.py +0 -0
  37. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/proxy.py +0 -0
  38. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/__init__.py +0 -0
  39. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/rpc.py +0 -0
  40. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/serializer.py +0 -0
  41. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  42. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  43. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  44. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  45. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  46. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  47. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  48. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/comm/xmpp/xmppclient.py +0 -0
  49. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/__init__.py +0 -0
  50. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/badweather.py +0 -0
  51. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/event.py +0 -0
  52. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/exposurestatuschanged.py +0 -0
  53. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/filterchanged.py +0 -0
  54. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/focusfound.py +0 -0
  55. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/goodweather.py +0 -0
  56. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/log.py +0 -0
  57. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/modechanged.py +0 -0
  58. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/moduleclosed.py +0 -0
  59. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/moduleopened.py +0 -0
  60. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/motionstatuschanged.py +0 -0
  61. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/move.py +0 -0
  62. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/newimage.py +0 -0
  63. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/newspectrum.py +0 -0
  64. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/offsets.py +0 -0
  65. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/roofclosing.py +0 -0
  66. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/roofopened.py +0 -0
  67. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/taskfailed.py +0 -0
  68. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/taskfinished.py +0 -0
  69. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/taskstarted.py +0 -0
  70. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/events/testevent.py +0 -0
  71. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/__init__.py +0 -0
  72. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/image.py +0 -0
  73. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/meta/__init__.py +0 -0
  74. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/meta/altazoffsets.py +0 -0
  75. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/meta/exptime.py +0 -0
  76. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/meta/genericoffset.py +0 -0
  77. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/meta/onskydistance.py +0 -0
  78. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/meta/pixeloffsets.py +0 -0
  79. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/meta/radecoffsets.py +0 -0
  80. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/meta/skyoffsets.py +0 -0
  81. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processor.py +0 -0
  82. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/__init__.py +0 -0
  83. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  84. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/annotation/__init__.py +0 -0
  85. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/annotation/_pillow.py +0 -0
  86. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/annotation/circle.py +0 -0
  87. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/annotation/crosshair.py +0 -0
  88. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/annotation/text.py +0 -0
  89. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/astrometry/__init__.py +0 -0
  90. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  91. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  92. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  93. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  94. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  95. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  96. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/calibration/__init__.py +0 -0
  97. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  98. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  99. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/calibration/calibration.py +0 -0
  100. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/detection/__init__.py +0 -0
  101. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  102. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  103. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/detection/daophot.py +0 -0
  104. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/detection/pysep.py +0 -0
  105. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/detection/simpledisk.py +0 -0
  106. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  107. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/exptime/__init__.py +0 -0
  108. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/exptime/exptime.py +0 -0
  109. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/exptime/star.py +0 -0
  110. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/__init__.py +0 -0
  111. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  112. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/download.py +0 -0
  113. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/flip.py +0 -0
  114. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/grayscale.py +0 -0
  115. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/httpserver.py +0 -0
  116. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/normalize.py +0 -0
  117. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/save.py +0 -0
  118. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/saveimage.py +0 -0
  119. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/smooth.py +0 -0
  120. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/image/softbin.py +0 -0
  121. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/misc/__init__.py +0 -0
  122. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/misc/addmask.py +0 -0
  123. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  124. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/misc/circularmask.py +0 -0
  125. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/misc/createfilename.py +0 -0
  126. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  127. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/misc/removebackground.py +0 -0
  128. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/modules/__init__.py +0 -0
  129. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
  130. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/__init__.py +0 -0
  131. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/astrometry.py +0 -0
  132. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  133. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  134. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  135. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  136. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  137. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/offsets.py +0 -0
  138. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  139. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/projected.py +0 -0
  140. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/offsets/spilled_light.py +0 -0
  141. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/photometry/__init__.py +0 -0
  142. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  143. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  144. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  145. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  146. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/photometry/photometry.py +0 -0
  147. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/photometry/photutil.py +0 -0
  148. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/photometry/pysep.py +0 -0
  149. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/wcs/__init__.py +0 -0
  150. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  151. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IAbortable.py +0 -0
  152. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IAcquisition.py +0 -0
  153. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IAutoFocus.py +0 -0
  154. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IAutoGuiding.py +0 -0
  155. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IAutonomous.py +0 -0
  156. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IBinning.py +0 -0
  157. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ICalibrate.py +0 -0
  158. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ICamera.py +0 -0
  159. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IConfig.py +0 -0
  160. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ICooling.py +0 -0
  161. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IData.py +0 -0
  162. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IDome.py +0 -0
  163. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IExposure.py +0 -0
  164. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IExposureTime.py +0 -0
  165. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IFilters.py +0 -0
  166. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  167. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  168. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IFlatField.py +0 -0
  169. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IFocusModel.py +0 -0
  170. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IFocuser.py +0 -0
  171. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IGain.py +0 -0
  172. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IImageFormat.py +0 -0
  173. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IImageType.py +0 -0
  174. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IMode.py +0 -0
  175. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IModule.py +0 -0
  176. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IMotion.py +0 -0
  177. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IMultiFiber.py +0 -0
  178. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
  179. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
  180. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IPointingAltAz.py +0 -0
  181. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IPointingBody.py +0 -0
  182. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
  183. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
  184. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
  185. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
  186. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IPointingRaDec.py +0 -0
  187. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IPointingSeries.py +0 -0
  188. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IReady.py +0 -0
  189. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IRoof.py +0 -0
  190. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IRotation.py +0 -0
  191. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IRunnable.py +0 -0
  192. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IRunning.py +0 -0
  193. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IScriptRunner.py +0 -0
  194. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ISpectrograph.py +0 -0
  195. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IStartStop.py +0 -0
  196. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IStructuredConfig.py +0 -0
  197. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ISyncTarget.py +0 -0
  198. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ITelescope.py +0 -0
  199. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ITemperatures.py +0 -0
  200. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ITrackingMode.py +0 -0
  201. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/ITrackingRate.py +0 -0
  202. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IVideo.py +0 -0
  203. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IWeather.py +0 -0
  204. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/IWindow.py +0 -0
  205. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/interfaces/interface.py +0 -0
  206. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/__init__.py +0 -0
  207. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/camerasettings.py +0 -0
  208. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/fitsheader.py +0 -0
  209. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/fitsnamespace.py +0 -0
  210. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/follow.py +0 -0
  211. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/pipeline.py +0 -0
  212. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/waitformotion.py +0 -0
  213. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/mixins/weatheraware.py +0 -0
  214. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/__init__.py +0 -0
  215. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/__init__.py +0 -0
  216. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/adaptive.py +0 -0
  217. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/basevideo.py +0 -0
  218. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/dummycamera.py +0 -0
  219. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  220. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/camera/dummyvideo.py +0 -0
  221. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/flatfield/__init__.py +0 -0
  222. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/flatfield/pointing.py +0 -0
  223. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/flatfield/scheduler.py +0 -0
  224. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/focus/__init__.py +0 -0
  225. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/focus/dummyautofocus.py +0 -0
  226. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/focus/focusseries.py +0 -0
  227. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/image/__init__.py +0 -0
  228. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/image/imagewatcher.py +0 -0
  229. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/image/imagewriter.py +0 -0
  230. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/image/pipeline.py +0 -0
  231. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/image/seeing.py +0 -0
  232. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/module.py +0 -0
  233. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/__init__.py +0 -0
  234. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/_base.py +0 -0
  235. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/_baseguiding.py +0 -0
  236. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/acquisition.py +0 -0
  237. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  238. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/dummyguiding.py +0 -0
  239. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  240. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  241. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  242. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  243. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  244. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/robotic/__init__.py +0 -0
  245. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/robotic/mastermind.py +0 -0
  246. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/robotic/pointing.py +0 -0
  247. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/robotic/scheduler.py +0 -0
  248. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/robotic/scriptrunner.py +0 -0
  249. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/roof/__init__.py +0 -0
  250. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/roof/basedome.py +0 -0
  251. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/roof/dummyroof.py +0 -0
  252. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/telescope/__init__.py +0 -0
  253. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/telescope/basetelescope.py +0 -0
  254. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
  255. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
  256. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/test/__init__.py +0 -0
  257. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/test/standalone.py +0 -0
  258. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/__init__.py +0 -0
  259. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/fluentlogger.py +0 -0
  260. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/httpfilecache.py +0 -0
  261. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/kiosk.py +0 -0
  262. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/matrix.py +0 -0
  263. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/stellarium.py +0 -0
  264. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/telegram.py +0 -0
  265. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/utils/trigger.py +0 -0
  266. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/weather/__init__.py +0 -0
  267. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/weather/mockweather.py +0 -0
  268. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/weather/weather.py +0 -0
  269. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/weather/weather_api.py +0 -0
  270. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/modules/weather/weather_state.py +0 -0
  271. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/object.py +0 -0
  272. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/py.typed +0 -0
  273. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/__init__.py +0 -0
  274. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/observation.py +0 -0
  275. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/__init__.py +0 -0
  276. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  277. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  278. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  279. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
  280. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  281. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  282. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  283. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  284. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  285. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  286. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  287. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  288. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  289. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  290. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  291. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  292. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  293. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/random.py +0 -0
  294. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  295. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  296. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  297. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  298. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
  299. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  300. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
  301. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
  302. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  303. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  304. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  305. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  306. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/targets/target.py +0 -0
  307. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  308. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/__init__.py +0 -0
  309. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  310. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
  311. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
  312. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
  313. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/control/__init__.py +0 -0
  314. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/control/cases.py +0 -0
  315. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/control/conditional.py +0 -0
  316. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/control/parallel.py +0 -0
  317. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/control/selector.py +0 -0
  318. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/control/sequential.py +0 -0
  319. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  320. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
  321. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
  322. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  323. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/script.py +0 -0
  324. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  325. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
  326. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  327. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/scripts/utils/log.py +0 -0
  328. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/__init__.py +0 -0
  329. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/backend/__init__.py +0 -0
  330. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/backend/observationarchive.py +0 -0
  331. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/backend/taskarchive.py +0 -0
  332. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  333. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  334. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  335. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/__init__.py +0 -0
  336. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/_portal.py +0 -0
  337. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  338. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  339. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/configdb.py +0 -0
  340. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  341. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  342. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  343. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
  344. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
  345. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  346. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/task.py +0 -0
  347. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  348. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  349. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/memory/__init__.py +0 -0
  350. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  351. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  352. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/observationarchive.py +0 -0
  353. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/storage/taskarchive.py +0 -0
  354. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/task.py +0 -0
  355. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/taskrunner.py +0 -0
  356. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/__init__.py +0 -0
  357. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/archive/__init__.py +0 -0
  358. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/archive/archive.py +0 -0
  359. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  360. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  361. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  362. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  363. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
  364. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  365. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  366. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  367. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  368. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  369. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  370. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  371. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  372. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  373. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  374. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/__init__.py +0 -0
  375. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/average.py +0 -0
  376. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/cache.py +0 -0
  377. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/config.py +0 -0
  378. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/config_schema.py +0 -0
  379. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/coordinates.py +0 -0
  380. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/curvefit.py +0 -0
  381. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/enums.py +0 -0
  382. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/exceptions.py +0 -0
  383. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/fits.py +0 -0
  384. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/focusseries/__init__.py +0 -0
  385. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/focusseries/base.py +0 -0
  386. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/focusseries/photometry.py +0 -0
  387. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/focusseries/projection.py +0 -0
  388. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/grids/__init__.py +0 -0
  389. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/grids/filters.py +0 -0
  390. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/grids/grid.py +0 -0
  391. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/grids/gridnode.py +0 -0
  392. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/grids/pipeline.py +0 -0
  393. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/__init__.py +0 -0
  394. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/camera/__init__.py +0 -0
  395. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  396. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  397. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  398. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  399. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  400. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  401. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  402. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/http.py +0 -0
  403. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/influxdb.py +0 -0
  404. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/logger.py +0 -0
  405. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/logging/__init__.py +0 -0
  406. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/logging/context.py +0 -0
  407. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/logging/resolvableerror.py +0 -0
  408. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/modulegui.py +0 -0
  409. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/offsets/__init__.py +0 -0
  410. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  411. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/offsets/applyoffsets.py +0 -0
  412. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  413. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/parallel.py +0 -0
  414. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/pipeline/__init__.py +0 -0
  415. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/pipeline/night.py +0 -0
  416. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/pipeline/pipeline.py +0 -0
  417. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/publisher/__init__.py +0 -0
  418. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/publisher/csv.py +0 -0
  419. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/publisher/http.py +0 -0
  420. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/publisher/log.py +0 -0
  421. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/publisher/multi.py +0 -0
  422. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/publisher/publisher.py +0 -0
  423. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/serialization.py +0 -0
  424. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/shellcommand.py +0 -0
  425. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/threads/__init__.py +0 -0
  426. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/threads/lockwithabort.py +0 -0
  427. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/time.py +0 -0
  428. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/utils/units.py +0 -0
  429. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/version.py +0 -0
  430. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/__init__.py +0 -0
  431. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/archivefile.py +0 -0
  432. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/bufferedfile.py +0 -0
  433. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/file.py +0 -0
  434. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/filelists/__init__.py +0 -0
  435. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/filelists/filelist.py +0 -0
  436. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/filelists/testing.py +0 -0
  437. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/httpfile.py +0 -0
  438. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/localfile.py +0 -0
  439. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/memfile.py +0 -0
  440. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/sftpfile.py +0 -0
  441. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/smbfile.py +0 -0
  442. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/sshfile.py +0 -0
  443. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/pyobs/vfs/tempfile.py +0 -0
  444. {pyobs_core-2.0.0.dev20 → pyobs_core-2.0.0.dev23}/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.dev20
3
+ Version: 2.0.0.dev23
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -445,6 +445,25 @@ class Comm:
445
445
  if not event_class.local:
446
446
  await self._register_events(event_classes, handler)
447
447
 
448
+ async def unregister_event(
449
+ self, event_class: type[Event], handler: Callable[[Event, str], Coroutine[Any, Any, bool]]
450
+ ) -> None:
451
+ """Remove a handler previously added via register_event().
452
+
453
+ Leaves the event type itself registered (other handlers may still be interested in it,
454
+ and the module may still want to advertise/receive it), only stops calling this handler.
455
+
456
+ Args:
457
+ event_class: Class of event that was registered.
458
+ handler: The exact handler that was passed to register_event().
459
+ """
460
+
461
+ # same derived-events expansion as register_event(), so this mirrors exactly what was added
462
+ for ev in self._get_derived_events(event_class):
463
+ handlers = self._event_handlers.get(ev)
464
+ if handlers is not None and handler in handlers:
465
+ handlers.remove(handler)
466
+
448
467
  async def _register_events(
449
468
  self, events: list[type[Event]], handler: Callable[[Event, str], Coroutine[Any, Any, bool]] | None = None
450
469
  ) -> None:
@@ -143,6 +143,7 @@ class XmppComm(Comm):
143
143
  self._loop = asyncio.get_event_loop()
144
144
  self._safe_send_attempts = 5
145
145
  self._safe_send_wait = 1
146
+ self._safe_send_timeout = 15.0
146
147
 
147
148
  # build jid
148
149
  if jid:
@@ -799,14 +800,21 @@ class XmppComm(Comm):
799
800
  iq = None
800
801
  for i in range(self._safe_send_attempts):
801
802
  try:
802
- # execute method and return result
803
- return await method(*args, **kwargs)
803
+ # execute method and return result, but never wait longer than our own
804
+ # timeout -- some XMPP servers/slixmpp's own IQ timeout can fail to fire,
805
+ # which would otherwise hang the caller (and, if called from open(), the
806
+ # whole module) indefinitely
807
+ return await asyncio.wait_for(method(*args, **kwargs), timeout=self._safe_send_timeout)
804
808
 
805
809
  except slixmpp.exceptions.IqTimeout as timeout:
806
810
  # timeout occurred, try again after some wait
807
811
  iq = timeout.iq
808
812
  await asyncio.sleep(self._safe_send_wait)
809
813
 
814
+ except TimeoutError:
815
+ # our own timeout fired instead of slixmpp's, try again after some wait
816
+ await asyncio.sleep(self._safe_send_wait)
817
+
810
818
  # never should reach this
811
819
  raise slixmpp.exceptions.IqTimeout(iq)
812
820
 
@@ -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))
@@ -51,6 +51,17 @@ class DummySolarTelescope(
51
51
 
52
52
  self.add_background_task(self._solar_follow_task)
53
53
 
54
+ async def open(self) -> None:
55
+ """Open module."""
56
+ await _DummyTelescopeBase.open(self)
57
+
58
+ # no solar target is being followed yet -- publish the disk centre as a placeholder so
59
+ # the pubsub nodes exist (real values get set once a move_heliocentric_polar/
60
+ # move_heliographic_stonyhurst/move_helioprojective call comes in)
61
+ await self.comm.set_state(IPointingHeliocentricPolar, HeliocentricPolarState(mu=1.0, psi=0.0))
62
+ await self.comm.set_state(IPointingHeliographicStonyhurst, HeliographicStonyhurstState(lon=0.0, lat=0.0))
63
+ await self.comm.set_state(IPointingHelioprojective, HelioprojectiveState(theta_x=0.0, theta_y=0.0))
64
+
54
65
  @staticmethod
55
66
  def _heliocentric_polar_to_radec(mu: float, psi: float, time: Time) -> tuple[float, float]:
56
67
  """Converts Heliocentric Polar (mu, psi) to (ra, dec) in degrees, ICRS. Mirrors
@@ -142,6 +153,7 @@ class DummySolarTelescope(
142
153
  ra, dec = self._helioprojective_to_radec(a, b, now)
143
154
  self._position = SkyCoord(ra=ra * u.deg, dec=dec * u.deg, frame="icrs")
144
155
  await self.comm.set_state(IPointingRaDec, RaDecState(ra=ra, dec=dec))
156
+ await self._publish_altaz()
145
157
 
146
158
  await asyncio.sleep(self._SOLAR_FOLLOW_INTERVAL_SECONDS)
147
159
 
@@ -5,7 +5,7 @@ import logging
5
5
  from typing import Any
6
6
 
7
7
  from pyobs.events import ModeChangedEvent
8
- from pyobs.interfaces import IMode, IMotion, IReady, ModeCapabilities, ModeState, ReadyState
8
+ from pyobs.interfaces import IMode, IMotion, ModeCapabilities, ModeState
9
9
  from pyobs.mixins import MotionStatusMixin
10
10
  from pyobs.modules import Module
11
11
  from pyobs.utils.enums import MotionStatus
@@ -42,7 +42,6 @@ class DummyMode(MotionStatusMixin, Module, IMode, IMotion):
42
42
  # subscribe to events
43
43
  if isinstance(self, Module) and self._comm:
44
44
  await self.comm.register_event(ModeChangedEvent)
45
- await self.comm.set_state(IReady, ReadyState(ready=True))
46
45
 
47
46
  # publish capabilities and initial state
48
47
  await self.comm.set_capabilities(IMode, ModeCapabilities(modes=self._mode_options))
@@ -524,7 +524,13 @@ class FlatFielder(Object):
524
524
  def _calc_new_exptime(self) -> None:
525
525
  """Calculate new exposure time."""
526
526
  # calculate factor for new exposure time
527
- factor = (self._target_count - self._bias_level) / (self._median - self._bias_level)
527
+ denom = self._median - self._bias_level
528
+ if denom <= 0:
529
+ # median at or below bias level -- signal is indistinguishable from bias,
530
+ # so aim for the largest allowed increase in exposure time
531
+ factor = 10.0
532
+ else:
533
+ factor = (self._target_count - self._bias_level) / denom
528
534
 
529
535
  # limit factor to 0.1-10
530
536
  factor = min(10.0, max(0.1, factor))
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-core"
3
- version = "2.0.0.dev20"
3
+ version = "2.0.0.dev23"
4
4
  description = "robotic telescope software"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"