pyobs-core 2.0.0.dev76__tar.gz → 2.0.0.dev78__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 (449) hide show
  1. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/PKG-INFO +1 -1
  2. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/application.py +13 -0
  3. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/comm.py +10 -0
  4. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xmppclient.py +0 -1
  5. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xmppcomm.py +157 -13
  6. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/image.py +26 -0
  7. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IVideo.py +2 -1
  8. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/fitsheader.py +44 -4
  9. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/weatheraware.py +7 -6
  10. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/basecamera.py +3 -0
  11. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/basevideo.py +128 -20
  12. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/object.py +13 -0
  13. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/constraints/constraint.py +2 -7
  14. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/merit.py +10 -19
  15. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/imaging/imaging.py +9 -1
  16. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/_portal.py +13 -0
  17. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/task.py +2 -2
  18. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/config.py +49 -9
  19. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/serialization.py +31 -3
  20. pyobs_core-2.0.0.dev78/pyobs/utils/versions.py +50 -0
  21. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyproject.toml +1 -1
  22. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/.gitignore +0 -0
  23. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/LICENSE +0 -0
  24. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/__init__.py +0 -0
  25. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/background_task.py +0 -0
  26. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/cli/__init__.py +0 -0
  27. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/cli/_cli.py +0 -0
  28. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/cli/pyobs.py +0 -0
  29. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/cli/pyobsd.py +0 -0
  30. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/cli/pyobsw.py +0 -0
  31. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/__init__.py +0 -0
  32. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/commlogging.py +0 -0
  33. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/dummy/__init__.py +0 -0
  34. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/dummy/dummycomm.py +0 -0
  35. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/local/__init__.py +0 -0
  36. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/local/localcomm.py +0 -0
  37. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/local/localnetwork.py +0 -0
  38. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/proxy.py +0 -0
  39. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/__init__.py +0 -0
  40. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/rpc.py +0 -0
  41. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/serializer.py +0 -0
  42. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  43. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  44. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  45. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  46. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  47. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  48. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  49. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/__init__.py +0 -0
  50. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/badweather.py +0 -0
  51. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/event.py +0 -0
  52. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/exposurestatuschanged.py +0 -0
  53. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/filterchanged.py +0 -0
  54. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/focusfound.py +0 -0
  55. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/goodweather.py +0 -0
  56. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/log.py +0 -0
  57. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/modechanged.py +0 -0
  58. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/moduleclosed.py +0 -0
  59. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/moduleopened.py +0 -0
  60. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/motionstatuschanged.py +0 -0
  61. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/move.py +0 -0
  62. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/newimage.py +0 -0
  63. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/newspectrum.py +0 -0
  64. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/offsets.py +0 -0
  65. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/roofclosing.py +0 -0
  66. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/roofopened.py +0 -0
  67. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/taskfailed.py +0 -0
  68. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/taskfinished.py +0 -0
  69. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/taskstarted.py +0 -0
  70. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/events/testevent.py +0 -0
  71. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/__init__.py +0 -0
  72. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/meta/__init__.py +0 -0
  73. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/meta/altazoffsets.py +0 -0
  74. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/meta/exptime.py +0 -0
  75. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/meta/genericoffset.py +0 -0
  76. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/meta/onskydistance.py +0 -0
  77. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/meta/pixeloffsets.py +0 -0
  78. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/meta/radecoffsets.py +0 -0
  79. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/meta/skyoffsets.py +0 -0
  80. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processor.py +0 -0
  81. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/__init__.py +0 -0
  82. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  83. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/annotation/__init__.py +0 -0
  84. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/annotation/_pillow.py +0 -0
  85. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/annotation/circle.py +0 -0
  86. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/annotation/crosshair.py +0 -0
  87. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/annotation/text.py +0 -0
  88. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/astrometry/__init__.py +0 -0
  89. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  90. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  91. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  92. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  93. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  94. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  95. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/calibration/__init__.py +0 -0
  96. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  97. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  98. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/calibration/calibration.py +0 -0
  99. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/detection/__init__.py +0 -0
  100. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  101. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  102. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/detection/daophot.py +0 -0
  103. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/detection/pysep.py +0 -0
  104. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/detection/simpledisk.py +0 -0
  105. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  106. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/exptime/__init__.py +0 -0
  107. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/exptime/exptime.py +0 -0
  108. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/exptime/star.py +0 -0
  109. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/__init__.py +0 -0
  110. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  111. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/download.py +0 -0
  112. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/flip.py +0 -0
  113. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/grayscale.py +0 -0
  114. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/httpserver.py +0 -0
  115. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/normalize.py +0 -0
  116. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/save.py +0 -0
  117. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/saveimage.py +0 -0
  118. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/smooth.py +0 -0
  119. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/image/softbin.py +0 -0
  120. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/misc/__init__.py +0 -0
  121. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/misc/addmask.py +0 -0
  122. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  123. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/misc/circularmask.py +0 -0
  124. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/misc/createfilename.py +0 -0
  125. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  126. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/misc/removebackground.py +0 -0
  127. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/modules/__init__.py +0 -0
  128. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
  129. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/__init__.py +0 -0
  130. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/astrometry.py +0 -0
  131. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  132. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  133. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  134. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  135. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  136. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/offsets.py +0 -0
  137. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  138. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/projected.py +0 -0
  139. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/offsets/spilled_light.py +0 -0
  140. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/photometry/__init__.py +0 -0
  141. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  142. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  143. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  144. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  145. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/photometry/photometry.py +0 -0
  146. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/photometry/photutil.py +0 -0
  147. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/photometry/pysep.py +0 -0
  148. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/wcs/__init__.py +0 -0
  149. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  150. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IAbortable.py +0 -0
  151. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IAcquisition.py +0 -0
  152. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IAutoFocus.py +0 -0
  153. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IAutoGuiding.py +0 -0
  154. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IAutonomous.py +0 -0
  155. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IBinning.py +0 -0
  156. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ICalibrate.py +0 -0
  157. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ICamera.py +0 -0
  158. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IConfig.py +0 -0
  159. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ICooling.py +0 -0
  160. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IData.py +0 -0
  161. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IDataSequence.py +0 -0
  162. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IDome.py +0 -0
  163. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IExposure.py +0 -0
  164. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IExposureTime.py +0 -0
  165. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IFilters.py +0 -0
  166. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  167. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  168. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IFlatField.py +0 -0
  169. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IFocusModel.py +0 -0
  170. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IFocuser.py +0 -0
  171. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IGain.py +0 -0
  172. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IImageFormat.py +0 -0
  173. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IImageType.py +0 -0
  174. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IMode.py +0 -0
  175. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IModule.py +0 -0
  176. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IMotion.py +0 -0
  177. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IMultiFiber.py +0 -0
  178. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
  179. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
  180. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IPointingAltAz.py +0 -0
  181. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IPointingBody.py +0 -0
  182. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
  183. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
  184. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
  185. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
  186. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IPointingRaDec.py +0 -0
  187. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IPointingSeries.py +0 -0
  188. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IReady.py +0 -0
  189. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IRoof.py +0 -0
  190. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IRotation.py +0 -0
  191. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IRunnable.py +0 -0
  192. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IRunning.py +0 -0
  193. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IScriptRunner.py +0 -0
  194. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ISpectrograph.py +0 -0
  195. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IStartStop.py +0 -0
  196. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IStructuredConfig.py +0 -0
  197. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ISyncTarget.py +0 -0
  198. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ITelescope.py +0 -0
  199. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ITemperatures.py +0 -0
  200. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ITrackingMode.py +0 -0
  201. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/ITrackingRate.py +0 -0
  202. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IWeather.py +0 -0
  203. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/IWindow.py +0 -0
  204. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/__init__.py +0 -0
  205. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/interfaces/interface.py +0 -0
  206. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/__init__.py +0 -0
  207. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/camerasettings.py +0 -0
  208. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/fitsnamespace.py +0 -0
  209. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/follow.py +0 -0
  210. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/motionstatus.py +0 -0
  211. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/pipeline.py +0 -0
  212. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/mixins/waitformotion.py +0 -0
  213. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/__init__.py +0 -0
  214. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/__init__.py +0 -0
  215. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/adaptive.py +0 -0
  216. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/basespectrograph.py +0 -0
  217. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/dummycamera.py +0 -0
  218. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  219. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/dummyvideo.py +0 -0
  220. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/camera/pipelinecamera.py +0 -0
  221. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/flatfield/__init__.py +0 -0
  222. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/flatfield/flatfield.py +0 -0
  223. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/flatfield/pointing.py +0 -0
  224. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/flatfield/scheduler.py +0 -0
  225. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/focus/__init__.py +0 -0
  226. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/focus/dummyautofocus.py +0 -0
  227. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/focus/focusmodel.py +0 -0
  228. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/focus/focusseries.py +0 -0
  229. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/image/__init__.py +0 -0
  230. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/image/imagewatcher.py +0 -0
  231. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/image/imagewriter.py +0 -0
  232. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/image/pipeline.py +0 -0
  233. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/image/seeing.py +0 -0
  234. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/module.py +0 -0
  235. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/__init__.py +0 -0
  236. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/_base.py +0 -0
  237. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/_baseguiding.py +0 -0
  238. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/acquisition.py +0 -0
  239. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/autoguiding.py +0 -0
  240. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  241. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/dummyguiding.py +0 -0
  242. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  243. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  244. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  245. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  246. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  247. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
  248. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/robotic/__init__.py +0 -0
  249. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/robotic/mastermind.py +0 -0
  250. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/robotic/pointing.py +0 -0
  251. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/robotic/scheduler.py +0 -0
  252. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/robotic/scriptrunner.py +0 -0
  253. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/roof/__init__.py +0 -0
  254. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/roof/basedome.py +0 -0
  255. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/roof/baseroof.py +0 -0
  256. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/roof/dummyroof.py +0 -0
  257. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/telescope/__init__.py +0 -0
  258. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
  259. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/telescope/basetelescope.py +0 -0
  260. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
  261. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
  262. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
  263. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/test/__init__.py +0 -0
  264. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/test/standalone.py +0 -0
  265. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/__init__.py +0 -0
  266. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/dummymode.py +0 -0
  267. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/fluentlogger.py +0 -0
  268. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/httpfilecache.py +0 -0
  269. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/kiosk.py +0 -0
  270. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/matrix.py +0 -0
  271. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/stellarium.py +0 -0
  272. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/telegram.py +0 -0
  273. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/utils/trigger.py +0 -0
  274. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/weather/__init__.py +0 -0
  275. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/weather/mockweather.py +0 -0
  276. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/weather/weather.py +0 -0
  277. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/weather/weather_api.py +0 -0
  278. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/modules/weather/weather_state.py +0 -0
  279. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/py.typed +0 -0
  280. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/__init__.py +0 -0
  281. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/observation.py +0 -0
  282. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/__init__.py +0 -0
  283. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/_executor.py +0 -0
  284. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  285. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  286. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  287. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  288. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  289. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  290. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  291. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  292. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  293. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  294. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  295. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  296. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  297. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  298. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  299. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/random.py +0 -0
  300. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  301. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  302. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  303. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  304. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
  305. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  306. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
  307. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
  308. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  309. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  310. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  311. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  312. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/targets/target.py +0 -0
  313. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  314. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/__init__.py +0 -0
  315. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  316. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
  317. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
  318. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
  319. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/control/__init__.py +0 -0
  320. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/control/cases.py +0 -0
  321. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/control/conditional.py +0 -0
  322. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/control/parallel.py +0 -0
  323. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/control/selector.py +0 -0
  324. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/control/sequential.py +0 -0
  325. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/exceptions.py +0 -0
  326. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  327. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
  328. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  329. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/script.py +0 -0
  330. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  331. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
  332. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  333. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/scripts/utils/log.py +0 -0
  334. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/__init__.py +0 -0
  335. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/backend/__init__.py +0 -0
  336. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/backend/observationarchive.py +0 -0
  337. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/backend/taskarchive.py +0 -0
  338. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  339. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  340. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  341. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/__init__.py +0 -0
  342. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  343. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  344. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/configdb.py +0 -0
  345. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  346. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  347. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  348. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
  349. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
  350. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  351. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/task.py +0 -0
  352. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  353. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  354. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/memory/__init__.py +0 -0
  355. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  356. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  357. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/observationarchive.py +0 -0
  358. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/storage/taskarchive.py +0 -0
  359. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/taskrunner.py +0 -0
  360. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/__init__.py +0 -0
  361. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/archive/__init__.py +0 -0
  362. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/archive/archive.py +0 -0
  363. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  364. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  365. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  366. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  367. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
  368. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  369. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  370. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
  371. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  372. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  373. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  374. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  375. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  376. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  377. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  378. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  379. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/__init__.py +0 -0
  380. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/average.py +0 -0
  381. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/cache.py +0 -0
  382. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/config_schema.py +0 -0
  383. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/coordinates.py +0 -0
  384. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/curvefit.py +0 -0
  385. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/enums.py +0 -0
  386. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/exceptions.py +0 -0
  387. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/fits.py +0 -0
  388. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/focusseries/__init__.py +0 -0
  389. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/focusseries/base.py +0 -0
  390. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/focusseries/photometry.py +0 -0
  391. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/focusseries/projection.py +0 -0
  392. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/grids/__init__.py +0 -0
  393. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/grids/filters.py +0 -0
  394. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/grids/grid.py +0 -0
  395. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/grids/gridnode.py +0 -0
  396. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/grids/pipeline.py +0 -0
  397. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/__init__.py +0 -0
  398. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/camera/__init__.py +0 -0
  399. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  400. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  401. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  402. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  403. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  404. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  405. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  406. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/http.py +0 -0
  407. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/influxdb.py +0 -0
  408. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/logger.py +0 -0
  409. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/logging/__init__.py +0 -0
  410. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/logging/context.py +0 -0
  411. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/logging/resolvableerror.py +0 -0
  412. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/modulegui.py +0 -0
  413. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/offsets/__init__.py +0 -0
  414. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  415. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/offsets/applyoffsets.py +0 -0
  416. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  417. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/parallel.py +0 -0
  418. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/pipeline/__init__.py +0 -0
  419. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/pipeline/pipeline.py +0 -0
  420. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/pipeline/progress.py +0 -0
  421. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/pipeline/reduction.py +0 -0
  422. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/pipeline/reduction_base.py +0 -0
  423. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/publisher/__init__.py +0 -0
  424. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/publisher/csv.py +0 -0
  425. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/publisher/http.py +0 -0
  426. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/publisher/log.py +0 -0
  427. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/publisher/multi.py +0 -0
  428. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/publisher/publisher.py +0 -0
  429. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/shellcommand.py +0 -0
  430. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/threads/__init__.py +0 -0
  431. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/threads/lockwithabort.py +0 -0
  432. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/time.py +0 -0
  433. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/utils/units.py +0 -0
  434. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/version.py +0 -0
  435. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/__init__.py +0 -0
  436. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/archivefile.py +0 -0
  437. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/bufferedfile.py +0 -0
  438. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/file.py +0 -0
  439. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/filelists/__init__.py +0 -0
  440. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/filelists/filelist.py +0 -0
  441. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/filelists/testing.py +0 -0
  442. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/httpfile.py +0 -0
  443. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/localfile.py +0 -0
  444. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/memfile.py +0 -0
  445. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/sftpfile.py +0 -0
  446. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/smbfile.py +0 -0
  447. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/sshfile.py +0 -0
  448. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/tempfile.py +0 -0
  449. {pyobs_core-2.0.0.dev76 → pyobs_core-2.0.0.dev78}/pyobs/vfs/vfs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyobs-core
3
- Version: 2.0.0.dev76
3
+ Version: 2.0.0.dev78
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -16,6 +16,7 @@ from pyobs.modules import Module, MultiModule
16
16
  from pyobs.object import get_class_from_string, get_object
17
17
  from pyobs.utils.config import pre_process_yaml
18
18
  from pyobs.utils.logging.context import ModuleNameFilter
19
+ from pyobs.utils.versions import loaded_pyobs_packages
19
20
 
20
21
  # just init logger with something here, will be overwritten in __init__
21
22
  log = logging.getLogger(__name__)
@@ -375,6 +376,18 @@ class Application:
375
376
 
376
377
  # open module
377
378
  log.info("Opening module...")
379
+
380
+ # record the loaded pyobs package versions for comm-less consumers (e.g. web-admin
381
+ # reading the log/journal); both construction paths have finished importing everything
382
+ # by this point. Guarded like the IERS priming above: metadata scanning can raise on
383
+ # any installed distribution with broken dist-info, and that's not worth aborting
384
+ # startup over.
385
+ try:
386
+ packages = loaded_pyobs_packages()
387
+ log.info("Loaded pyobs packages: %s", ", ".join(f"{k}={v}" for k, v in packages.items()))
388
+ except Exception:
389
+ log.warning("Could not determine loaded pyobs package versions, continuing anyway.", exc_info=True)
390
+
378
391
  await self._module.startup()
379
392
  log.info("Started successfully.")
380
393
 
@@ -470,18 +470,28 @@ class Comm:
470
470
  """
471
471
 
472
472
  # same derived-events expansion as register_event(), so this mirrors exactly what was added
473
+ unsubscribed: list[type[Event]] = []
473
474
  for ev in self._get_derived_events(event_class):
474
475
  handlers = self._event_handlers.get(ev)
475
476
  if handlers is not None and handler in handlers:
476
477
  handlers.remove(handler)
477
478
  if not handlers:
478
479
  self._events_subscribed.discard(ev)
480
+ unsubscribed.append(ev)
481
+
482
+ # only event classes that just lost their last handler need the comm layer to actually
483
+ # tear anything down (e.g. XmppComm unsubscribing from peers' event nodes)
484
+ if unsubscribed and not event_class.local:
485
+ await self._unregister_events(unsubscribed)
479
486
 
480
487
  async def _register_events(
481
488
  self, events: list[type[Event]], handler: Callable[[Event, str], Coroutine[Any, Any, bool]] | None = None
482
489
  ) -> None:
483
490
  pass
484
491
 
492
+ async def _unregister_events(self, events: list[type[Event]]) -> None:
493
+ pass
494
+
485
495
  async def set_state(self, interface: type[Interface], state: Any) -> None:
486
496
  """Publish state for this module.
487
497
 
@@ -60,7 +60,6 @@ class XmppClient(slixmpp.ClientXMPP):
60
60
  self.register_plugin("xep_0045") # Multi-User Chat
61
61
  self.register_plugin("xep_0060") # PubSub
62
62
  self.register_plugin("xep_0115") # Entity Capabilities
63
- self.register_plugin("xep_0163") # Personal Eventing Protocol
64
63
  self.register_plugin("xep_0199") # XMPP Ping
65
64
 
66
65
  # enable keep alive pings
@@ -190,6 +190,7 @@ class XmppComm(Comm):
190
190
  self._online_clients: list[str] = []
191
191
  self._interface_cache: dict[str, asyncio.Future[list[type[Interface]]]] = {}
192
192
  self._interface_features: dict[str, list[str]] = {}
193
+ self._peer_sent_events: dict[str, set[tuple[str, int]]] = {}
193
194
  self._warned_version_mismatches: set[tuple[str, str]] = set()
194
195
  self._user = user
195
196
  self._password = password
@@ -240,6 +241,11 @@ class XmppComm(Comm):
240
241
  self._pubsub_service = f"pubsub.{self._domain}"
241
242
  self._state_node_handlers: dict[str, tuple[type[Interface], list[Callable[[Any], None]]]] = {}
242
243
  self._client_states: dict[str, tuple[ModuleState, str]] = {} # jid -> (state, error_string)
244
+
245
+ # pubsub for events -- desired (peer module name, event class) subscriptions. A key's
246
+ # presence drives _subscribe_event_with_retry's retry loop; removing it (unregister, or
247
+ # never re-added on a fresh connect) is what stops a still-retrying subscribe.
248
+ self._event_subscriptions: set[tuple[str, type[Event]]] = set()
243
249
  self._capabilities: dict[type, Any] = {} # interface → Capabilities instance
244
250
  self._own_states: dict[type, Any] = {} # interface → last published state for this module
245
251
  self._presence_callbacks: dict[str, list[Callable[[ModuleState, str], None]]] = {}
@@ -500,6 +506,14 @@ class XmppComm(Comm):
500
506
  # cache raw features for this JID, so a later version-mismatch can be diagnosed
501
507
  self._interface_features[jid] = features
502
508
 
509
+ # cache which event types this peer actually publishes (role="send" in the rich <event>
510
+ # schema elements from _get_disco_info) -- distinct from the plain urn:pyobs:event:
511
+ # feature list above, which covers subscribe-only registrations too and can't tell a
512
+ # publisher apart from a mere consumer. Used to gate event-node subscriptions so we don't
513
+ # retry forever against peers that will never publish to that node (see _got_online,
514
+ # _register_events).
515
+ self._peer_sent_events[jid] = self._parse_peer_sent_events(info)
516
+
503
517
  # keep only names whose remote-published version matches what this client expects --
504
518
  # a mismatch is treated the same as the interface not being there at all, rather than
505
519
  # silently using the local (possibly incompatible) class
@@ -522,6 +536,36 @@ class XmppComm(Comm):
522
536
  # finished
523
537
  return interface_names
524
538
 
539
+ @staticmethod
540
+ def _parse_peer_sent_events(info: Any) -> set[tuple[str, int]]:
541
+ """Extract (event class name, version) pairs a peer's disco#info marks role="send" for.
542
+
543
+ Reads the rich <{urn:pyobs:event:name:version}event role="..."> elements _get_disco_info
544
+ appends (see _event_role), not the plain urn:pyobs:event: feature list -- that list only
545
+ says the peer registered the event at all, sent or subscribe-only, and can't tell a
546
+ publisher apart from a mere consumer.
547
+ """
548
+ sent: set[tuple[str, int]] = set()
549
+ xml = getattr(info, "xml", None)
550
+ if xml is None:
551
+ return sent
552
+ ns_prefix = "urn:pyobs:event:"
553
+ for elem in xml:
554
+ tag = elem.tag
555
+ if not tag.startswith("{") or "}" not in tag:
556
+ continue
557
+ ns, _, local = tag[1:].partition("}")
558
+ if local != "event" or not ns.startswith(ns_prefix):
559
+ continue
560
+ if "send" not in elem.get("role", "").split():
561
+ continue
562
+ name = elem.get("name")
563
+ _, _, version_str = ns[len(ns_prefix) :].rpartition(":")
564
+ if not name or not version_str.isdigit():
565
+ continue
566
+ sent.add((name, int(version_str)))
567
+ return sent
568
+
525
569
  def _diagnose_missing_interface(self, client: str, obj_type: type[Any]) -> str | None:
526
570
  """Checks the disco#info features already cached for client for a version of obj_type
527
571
  other than the one this client expects, to tell a version mismatch apart from
@@ -665,6 +709,27 @@ class XmppComm(Comm):
665
709
  if jid not in self._online_clients:
666
710
  self._online_clients.append(jid)
667
711
 
712
+ # subscribe to this peer's event nodes for every event type we currently handle -- a
713
+ # handler registered before this peer came online never got a chance to subscribe to it
714
+ # from _register_events, since it wasn't in self._online_clients yet.
715
+ # Skip local events (e.g. ModuleOpenedEvent/ModuleClosedEvent, registered by every
716
+ # module via module.py/comm.py) -- they're synthesized locally here and in
717
+ # _jid_got_offline, never published to a node, so subscribing would retry forever
718
+ # against something that will never exist. Also skip event classes whose last handler
719
+ # was already removed -- unregister_event() discards from _events_subscribed but leaves
720
+ # an empty list in _event_handlers. Also skip event types this peer doesn't actually
721
+ # publish (per its disco#info role="send" list, cached above by _get_interfaces) --
722
+ # otherwise every module subscribes to every peer for every event type it handles, e.g. a
723
+ # camera's BadWeatherEvent handler retry-subscribing to admin:BadWeatherEvent forever,
724
+ # since that node will never be created.
725
+ peer_sent_events = self._peer_sent_events.get(jid, set())
726
+ for ev, handlers in self._event_handlers.items():
727
+ if ev.local or not handlers:
728
+ continue
729
+ if (ev.__name__, ev.version) not in peer_sent_events:
730
+ continue
731
+ asyncio.create_task(self._subscribe_event_with_retry(module_name, ev))
732
+
668
733
  # send event
669
734
  self._send_event_to_module(ModuleOpenedEvent(), msg["from"].username)
670
735
 
@@ -777,11 +842,18 @@ class XmppComm(Comm):
777
842
  # set xml and send event
778
843
  stanza.xml = ET.fromstring(f'<event xmlns="pyobs:event">{body}</event>')
779
844
 
780
- # send it
845
+ # publish to the shared pubsub service, same mechanism as state (see _set_state) --
846
+ # the node id encodes the publishing module, since notifications from this service
847
+ # arrive "from" the service itself, not from us (see _event_node/_handle_event).
848
+ # A module-less XmppComm (GUI, admin tool, observer) has no self._module -- fall back to
849
+ # the JID's own username, same identity peers would derive from our JID anyway.
850
+ publisher = self._module.name if self._module is not None else self.client.boundjid.user
851
+ node = self._event_node(publisher, event.__class__)
781
852
  await self._safe_send(
782
- self.client.plugin["xep_0163"].publish,
783
- stanza,
784
- node=f"urn:pyobs:event:{event.__class__.__name__}:{event.version}",
853
+ self.client.plugin["xep_0060"].publish,
854
+ self._pubsub_service,
855
+ node,
856
+ payload=stanza,
785
857
  callback=functools.partial(self._send_event_callback, event=event),
786
858
  )
787
859
 
@@ -804,18 +876,82 @@ class XmppComm(Comm):
804
876
  ) -> None:
805
877
  # loop events
806
878
  for ev in events:
807
- # register event at XMPP
879
+ # register event at XMPP (disco advertising only -- unrelated to delivery, used by
880
+ # e.g. pyobs-web-client to distinguish producers from consumers, see _event_role)
808
881
  self.client.plugin["xep_0030"].add_feature(f"urn:pyobs:event:{ev.__name__}:{ev.version}")
809
882
 
810
- # if we have a handler, we're also interested in receiving such events
883
+ # if we have a handler, subscribe to this event's node on every peer already online
884
+ # that actually publishes it (see _got_online for why this is gated). A peer coming
885
+ # online later is covered by _got_online subscribing to every event currently in
886
+ # self._event_handlers.
811
887
  if handler:
812
- # add interest
813
- self.client.plugin["xep_0163"].add_interest(f"urn:pyobs:event:{ev.__name__}:{ev.version}")
888
+ for peer_jid in list(self._online_clients):
889
+ if (ev.__name__, ev.version) not in self._peer_sent_events.get(peer_jid, set()):
890
+ continue
891
+ peer_module = peer_jid[: peer_jid.index("@")]
892
+ asyncio.create_task(self._subscribe_event_with_retry(peer_module, ev))
814
893
 
815
894
  # update caps and send presence
816
895
  await self._safe_send(self.client.plugin["xep_0115"].update_caps)
817
896
  self.client.send_presence()
818
897
 
898
+ async def _unregister_events(self, events: list[type[Event]]) -> None:
899
+ # unsubscribe goes to self._pubsub_service, not to the peer -- it doesn't depend on the
900
+ # peer being online, so this has to cover every (peer, ev) we're actually subscribed to,
901
+ # not just currently-online ones (a peer offline at unregister time would otherwise keep
902
+ # its server-side subscription, and we'd go on receiving that event's wire message)
903
+ for ev in events:
904
+ for peer_module, key_ev in list(self._event_subscriptions):
905
+ if key_ev is not ev:
906
+ continue
907
+ self._event_subscriptions.discard((peer_module, ev))
908
+ node = self._event_node(peer_module, ev)
909
+ try:
910
+ await self._safe_send(self.client.plugin["xep_0060"].unsubscribe, self._pubsub_service, node)
911
+ except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout):
912
+ pass # already gone server-side
913
+
914
+ @staticmethod
915
+ def _event_node(module: str, event_class: type[Event]) -> str:
916
+ return f"pyobs:event:{module}:{event_class.__name__}:{event_class.version}"
917
+
918
+ @staticmethod
919
+ def _event_node_module(node: str) -> str | None:
920
+ """Recover the publishing module's name from an event node id, since notifications from
921
+ the shared pubsub service arrive "from" the service itself, not from the publisher."""
922
+ parts = node.split(":")
923
+ if len(parts) == 5 and parts[0] == "pyobs" and parts[1] == "event":
924
+ return parts[2]
925
+ return None
926
+
927
+ async def _subscribe_event_with_retry(self, peer_module: str, event_class: type[Event]) -> None:
928
+ """Subscribe to a peer's event node, retrying until the node exists.
929
+
930
+ Mirrors _subscribe_with_retry (state). Runs as a background task; retries indefinitely
931
+ with capped backoff since the peer may not have published (and thus auto-created the
932
+ node) yet. Stops early if _unregister_events drops the (peer_module, event_class) key
933
+ in the meantime.
934
+ """
935
+ key = (peer_module, event_class)
936
+ if key in self._event_subscriptions:
937
+ return
938
+ self._event_subscriptions.add(key)
939
+ node = self._event_node(peer_module, event_class)
940
+ attempt = 0
941
+ while key in self._event_subscriptions:
942
+ try:
943
+ await self._safe_send(self.client.plugin["xep_0060"].subscribe, self._pubsub_service, node)
944
+ return
945
+ except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout):
946
+ attempt += 1
947
+ if attempt == 30:
948
+ log.warning(
949
+ "Still failing to subscribe to event node %s after %d attempts, will keep retrying",
950
+ node,
951
+ attempt,
952
+ )
953
+ await asyncio.sleep(_retry_delay(attempt))
954
+
819
955
  def _handle_event_sync(self, msg: Any) -> None:
820
956
  """Synchronous entry point for the MatchXMLMask Callback.
821
957
 
@@ -843,13 +979,16 @@ class XmppComm(Comm):
843
979
  for callback in callbacks:
844
980
  callback(state_obj)
845
981
  else:
846
- asyncio.create_task(self._handle_event(msg))
982
+ asyncio.create_task(self._handle_event(msg, node))
847
983
 
848
- async def _handle_event(self, msg: Any) -> None:
984
+ async def _handle_event(self, msg: Any, node: str) -> None:
849
985
  """Handles an event.
850
986
 
851
987
  Args:
852
988
  msg: Received XMPP message.
989
+ node: pubsub node id the event arrived on. Notifications from the shared pubsub
990
+ service come "from" that service, not from the publisher, so the publishing
991
+ module's name has to come from the node id (see _event_node) instead of msg["from"].
853
992
  """
854
993
 
855
994
  # get body, unescape it, parse it
@@ -863,8 +1002,13 @@ class XmppComm(Comm):
863
1002
  # ignore this message
864
1003
  return
865
1004
 
866
- # did we send this?
867
- if msg["from"] == self.client.boundjid.bare:
1005
+ from_module = self._event_node_module(node)
1006
+ if from_module is None:
1007
+ return
1008
+
1009
+ # did we send this? (we never subscribe to our own node, so this shouldn't happen, but
1010
+ # stay defensive rather than assume)
1011
+ if self._module is not None and from_module == self._module.name:
868
1012
  return
869
1013
 
870
1014
  # create event and check timestamp
@@ -877,7 +1021,7 @@ class XmppComm(Comm):
877
1021
  return
878
1022
 
879
1023
  # send it to module
880
- self._send_event_to_module(event, msg["from"].username)
1024
+ self._send_event_to_module(event, from_module)
881
1025
 
882
1026
  async def _safe_send(self, method: Callable[..., Coroutine[Any, Any, Any]], *args: Any, **kwargs: Any) -> Any:
883
1027
  """Safely send an XMPP message.
@@ -196,6 +196,32 @@ class Image:
196
196
  # load image
197
197
  return cls._from_hdu_list(d)
198
198
 
199
+ @classmethod
200
+ def from_ndarray(cls, data: npt.NDArray[Any], meta: dict[str, Any]) -> Image:
201
+ """Create an Image from a raw numpy array and a metadata dict.
202
+
203
+ This is the consumer side of BaseVideo's raw-frame wire format: the meta dict
204
+ carries FITS keywords (plus a DTYPE entry used to decode the raw bytes on the
205
+ wire) and is mapped straight onto a FITS header, bypassing FITS bytes entirely.
206
+
207
+ Args:
208
+ data: Numpy array of pixel data.
209
+ meta: Dictionary of metadata -- FITS keywords plus an optional DTYPE marker.
210
+
211
+ Returns:
212
+ New image.
213
+ """
214
+
215
+ # build header from the FITS-named keys, skipping the DTYPE marker
216
+ header = fits.Header()
217
+ for key, value in meta.items():
218
+ if key == "DTYPE":
219
+ continue
220
+ header[key] = value
221
+
222
+ # create image (also sets NAXIS1/NAXIS2 from the data shape)
223
+ return cls(data=data, header=header)
224
+
199
225
  @classmethod
200
226
  def from_file(cls, filename: str) -> Image:
201
227
  """Create image from FITS file.
@@ -8,7 +8,8 @@ from .IData import IData
8
8
 
9
9
  @dataclass
10
10
  class VideoCapabilities:
11
- video: str = ""
11
+ mjpeg: str | None = None
12
+ raw: str | None = None
12
13
 
13
14
 
14
15
  class IVideo(IData, metaclass=ABCMeta):
@@ -31,6 +31,7 @@ class FitsHeaderMixin:
31
31
  filenames: str = "/cache/pyobs-{DAY-OBS|date:}-{FRAMENUM|string:04d}.fits",
32
32
  frame_number: bool = True,
33
33
  night_obs: bool = True,
34
+ fits_header_timeout: float = 15.0,
34
35
  **kwargs: Any,
35
36
  ):
36
37
  """Initialise the mixin.
@@ -41,6 +42,9 @@ class FitsHeaderMixin:
41
42
  filename: Filename pattern for FITS images.
42
43
  frame_number: Whether to add frame number to FITS file header.
43
44
  night_obs: If True, DAY-OBS will contain the night of observation, not the calendar day.
45
+ fits_header_timeout: Maximum seconds to wait for a peer's FITS headers before skipping
46
+ them. A peer that never answers (e.g. a laptop put to sleep without closing its
47
+ client) would otherwise stall the frame for the full XMPP IQ timeout.
44
48
  """
45
49
  module = cast(Module, self)
46
50
 
@@ -48,6 +52,7 @@ class FitsHeaderMixin:
48
52
  self._fitsheadermixin_fits_namespaces = fits_namespaces
49
53
  self._fitsheadermixin_filename_pattern = filenames
50
54
  self._fitsheadermixin_fits_headers = fits_headers if fits_headers is not None else {}
55
+ self._fitsheadermixin_header_timeout = fits_header_timeout
51
56
  if "OBSERVER" not in self._fitsheadermixin_fits_headers:
52
57
  self._fitsheadermixin_fits_headers["OBSERVER"] = ["pyobs", "Name of observer"]
53
58
  self._fitsheadermixin_night_obs = night_obs
@@ -98,12 +103,28 @@ class FitsHeaderMixin:
98
103
  futures: Futures to get headers from.
99
104
  """
100
105
 
106
+ # Bound the whole collection to a single deadline: a peer that never answers its
107
+ # IQ (e.g. a laptop put to sleep without closing its client) would otherwise stall
108
+ # the frame for the full XMPP IQ timeout (~120s) per dead peer. Give up on anything
109
+ # still pending and proceed without its headers.
110
+ pending: set[Task[Any]] = set()
111
+ if futures:
112
+ _, pending = await asyncio.wait(futures.values(), timeout=self._fitsheadermixin_header_timeout)
113
+ for task in pending:
114
+ task.cancel()
115
+ if pending:
116
+ await asyncio.gather(*pending, return_exceptions=True)
117
+
101
118
  # get fits headers from other clients
102
119
  for client, future in futures.items():
103
120
  # join thread
104
121
  log.info("Fetching FITS headers from %s...", client)
122
+ if future in pending:
123
+ log.warning("Could not fetch FITS headers from %s: timed out.", client)
124
+ continue
125
+
105
126
  try:
106
- headers = await future
127
+ headers = future.result()
107
128
  except exc.RemoteError as e:
108
129
  log.warning("Could not fetch FITS headers from %s: %s.", client, str(e))
109
130
  continue
@@ -114,8 +135,13 @@ class FitsHeaderMixin:
114
135
  for key, entry in headers.items():
115
136
  image.header[key] = (entry.value, entry.comment)
116
137
 
117
- async def add_fits_headers(self, image: Image | fits.PrimaryHDU) -> None:
118
- """Add requested FITS headers to header of given image.
138
+ def add_local_fits_headers(self, image: Image | fits.PrimaryHDU) -> None:
139
+ """Add the cheap, local FITS headers to the given image (no I/O, no comm).
140
+
141
+ This is the always-on half of add_fits_headers(): the configured static
142
+ fits_headers dict plus the computed fields from _fitsheadermixin_add_fits_headers().
143
+ The frame-sequence-number step is deliberately kept out (it does a VFS read+write
144
+ per call), so video-rate callers can reuse this without hitting the VFS on every frame.
119
145
 
120
146
  Args:
121
147
  image: Image with header to add to.
@@ -130,6 +156,18 @@ class FitsHeaderMixin:
130
156
 
131
157
  # add more fits headers
132
158
  self._fitsheadermixin_add_fits_headers(image)
159
+
160
+ async def add_fits_headers(self, image: Image | fits.PrimaryHDU) -> None:
161
+ """Add requested FITS headers to header of given image.
162
+
163
+ Args:
164
+ image: Image with header to add to.
165
+ """
166
+
167
+ # cheap, local half
168
+ self.add_local_fits_headers(image)
169
+
170
+ # persistent nightly frame-sequence number (VFS I/O -- not per-frame safe)
133
171
  if self._fitsheadermixin_enable_frame_number:
134
172
  await self._fitsheadermixin_add_framenum(image)
135
173
 
@@ -281,6 +319,7 @@ class ImageFitsHeaderMixin(FitsHeaderMixin):
281
319
  self._fitsheadermixin_warned_cdelt = False
282
320
  self._fitsheadermixin_warned_crpix = False
283
321
  self._fitsheadermixin_warned_cd_matrix = False
322
+ self._fitsheadermixin_warned_centre = False
284
323
 
285
324
  @property
286
325
  def rotation(self) -> float | None:
@@ -335,8 +374,9 @@ class ImageFitsHeaderMixin(FitsHeaderMixin):
335
374
  if self._fitsheadermixin_centre is not None:
336
375
  hdr["DET-CPX1"] = (self._fitsheadermixin_centre[0], "x-pixel on mechanical axis in unbinned image")
337
376
  hdr["DET-CPX2"] = (self._fitsheadermixin_centre[1], "y-pixel on mechanical axis in unbinned image")
338
- else:
377
+ elif not self._fitsheadermixin_warned_centre:
339
378
  log.warning("Could not calculate DET-CPX1/DET-CPX2 (centre not given in config).")
379
+ self._fitsheadermixin_warned_centre = True
340
380
 
341
381
  # reference pixel in binned image
342
382
  if "DET-CPX1" in hdr and "DET-BIN1" in hdr and "DET-CPX2" in hdr and "DET-BIN2" in hdr:
@@ -76,13 +76,14 @@ class WeatherAwareMixin:
76
76
  # do we need to park?
77
77
  if motion_status not in [MotionStatus.PARKED, MotionStatus.PARKING]:
78
78
  # error state?
79
- if is_error_state and not self.__weatheraware_is_error_state:
80
- log.error("Telescope is in error mode, cannot park.")
79
+ if is_error_state:
80
+ # only log once when entering error state, and never try to park
81
+ # while it persists -- park() would just raise again every cycle
82
+ if not self.__weatheraware_is_error_state:
83
+ log.error("Telescope is in error mode, cannot park.")
81
84
  else:
82
- # parking?
83
- if motion_status != MotionStatus.PARKED:
84
- log.warning("Weather is bad, shutting down.")
85
- await msi.park()
85
+ log.warning("Weather is bad, shutting down.")
86
+ await msi.park()
86
87
 
87
88
  # save error state
88
89
  self.__weatheraware_is_error_state = is_error_state
@@ -60,6 +60,7 @@ class BaseCamera(
60
60
  filenames: str = "/cache/pyobs-{DAY-OBS|date:}-{FRAMENUM|string:04d}-{IMAGETYP|type}00.fits.gz",
61
61
  fits_namespaces: list[str] | None = None,
62
62
  meridian_flip_on: str | None = None,
63
+ fits_header_timeout: float = 15.0,
63
64
  **kwargs: Any,
64
65
  ):
65
66
  """Creates a new BaseCamera.
@@ -73,6 +74,7 @@ class BaseCamera(
73
74
  flip_y: Whether or not to flip the image along its second axis.
74
75
  filenames: Template for file naming.
75
76
  fits_namespaces: List of namespaces for FITS headers that this camera should request
77
+ fits_header_timeout: Maximum seconds to wait for a peer's FITS headers before skipping them.
76
78
  """
77
79
  Module.__init__(self, **kwargs)
78
80
  ImageFitsHeaderMixin.__init__(
@@ -82,6 +84,7 @@ class BaseCamera(
82
84
  centre=centre,
83
85
  rotation=rotation,
84
86
  filenames=filenames,
87
+ fits_header_timeout=fits_header_timeout,
85
88
  )
86
89
 
87
90
  # check