pyobs-core 2.0.0.dev90__tar.gz → 2.0.0.dev92__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.dev90 → pyobs_core-2.0.0.dev92}/PKG-INFO +1 -1
  2. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/application.py +10 -1
  3. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/weatheraware.py +1 -1
  4. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/basecamera.py +36 -3
  5. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/basevideo.py +199 -2
  6. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/image/imagewatcher.py +9 -1
  7. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/flatfielder.py +1 -1
  8. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/httpfile.py +8 -0
  9. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyproject.toml +1 -1
  10. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/.gitignore +0 -0
  11. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/LICENSE +0 -0
  12. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/__init__.py +0 -0
  13. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/background_task.py +0 -0
  14. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/cli/__init__.py +0 -0
  15. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/cli/_cli.py +0 -0
  16. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/cli/pyobs.py +0 -0
  17. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/cli/pyobsd.py +0 -0
  18. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/cli/pyobsw.py +0 -0
  19. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/__init__.py +0 -0
  20. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/comm.py +0 -0
  21. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/commlogging.py +0 -0
  22. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/dummy/__init__.py +0 -0
  23. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/dummy/dummycomm.py +0 -0
  24. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/local/__init__.py +0 -0
  25. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/local/localcomm.py +0 -0
  26. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/local/localnetwork.py +0 -0
  27. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/proxy.py +0 -0
  28. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/__init__.py +0 -0
  29. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/rpc.py +0 -0
  30. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/serializer.py +0 -0
  31. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  32. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  33. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  34. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  35. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  36. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  37. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  38. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xmppclient.py +0 -0
  39. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/comm/xmpp/xmppcomm.py +0 -0
  40. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/__init__.py +0 -0
  41. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/badweather.py +0 -0
  42. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/event.py +0 -0
  43. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/exposurestatuschanged.py +0 -0
  44. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/filterchanged.py +0 -0
  45. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/focusfound.py +0 -0
  46. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/goodweather.py +0 -0
  47. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/log.py +0 -0
  48. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/modechanged.py +0 -0
  49. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/moduleclosed.py +0 -0
  50. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/moduleopened.py +0 -0
  51. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/motionstatuschanged.py +0 -0
  52. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/move.py +0 -0
  53. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/newimage.py +0 -0
  54. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/newspectrum.py +0 -0
  55. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/offsets.py +0 -0
  56. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/roofclosing.py +0 -0
  57. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/roofopened.py +0 -0
  58. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/taskfailed.py +0 -0
  59. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/taskfinished.py +0 -0
  60. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/taskstarted.py +0 -0
  61. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/events/testevent.py +0 -0
  62. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/__init__.py +0 -0
  63. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/image.py +0 -0
  64. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/meta/__init__.py +0 -0
  65. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/meta/altazoffsets.py +0 -0
  66. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/meta/exptime.py +0 -0
  67. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/meta/genericoffset.py +0 -0
  68. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/meta/onskydistance.py +0 -0
  69. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/meta/pixeloffsets.py +0 -0
  70. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/meta/radecoffsets.py +0 -0
  71. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/meta/skyoffsets.py +0 -0
  72. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processor.py +0 -0
  73. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/__init__.py +0 -0
  74. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  75. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/annotation/__init__.py +0 -0
  76. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/annotation/_pillow.py +0 -0
  77. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/annotation/circle.py +0 -0
  78. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/annotation/crosshair.py +0 -0
  79. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/annotation/text.py +0 -0
  80. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/astrometry/__init__.py +0 -0
  81. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  82. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  83. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  84. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  85. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  86. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  87. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/calibration/__init__.py +0 -0
  88. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  89. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  90. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/calibration/calibration.py +0 -0
  91. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/detection/__init__.py +0 -0
  92. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  93. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  94. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/detection/daophot.py +0 -0
  95. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/detection/pysep.py +0 -0
  96. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/detection/simpledisk.py +0 -0
  97. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  98. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/exptime/__init__.py +0 -0
  99. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/exptime/exptime.py +0 -0
  100. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/exptime/star.py +0 -0
  101. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/__init__.py +0 -0
  102. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  103. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/download.py +0 -0
  104. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/flip.py +0 -0
  105. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/grayscale.py +0 -0
  106. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/httpserver.py +0 -0
  107. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/normalize.py +0 -0
  108. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/save.py +0 -0
  109. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/saveimage.py +0 -0
  110. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/smooth.py +0 -0
  111. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/image/softbin.py +0 -0
  112. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/misc/__init__.py +0 -0
  113. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/misc/addmask.py +0 -0
  114. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  115. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/misc/circularmask.py +0 -0
  116. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/misc/createfilename.py +0 -0
  117. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  118. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/misc/removebackground.py +0 -0
  119. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/modules/__init__.py +0 -0
  120. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
  121. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/__init__.py +0 -0
  122. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/astrometry.py +0 -0
  123. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  124. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  125. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  126. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  127. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  128. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/offsets.py +0 -0
  129. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  130. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/projected.py +0 -0
  131. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/offsets/spilled_light.py +0 -0
  132. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/photometry/__init__.py +0 -0
  133. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  134. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  135. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  136. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  137. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/photometry/photometry.py +0 -0
  138. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/photometry/photutil.py +0 -0
  139. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/photometry/pysep.py +0 -0
  140. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/wcs/__init__.py +0 -0
  141. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  142. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IAbortable.py +0 -0
  143. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IAcquisition.py +0 -0
  144. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IAutoFocus.py +0 -0
  145. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IAutoGuiding.py +0 -0
  146. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IAutonomous.py +0 -0
  147. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IBinning.py +0 -0
  148. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ICalibrate.py +0 -0
  149. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ICamera.py +0 -0
  150. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IConfig.py +0 -0
  151. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ICooling.py +0 -0
  152. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IData.py +0 -0
  153. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IDataSequence.py +0 -0
  154. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IDome.py +0 -0
  155. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IExposure.py +0 -0
  156. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IExposureTime.py +0 -0
  157. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IFilters.py +0 -0
  158. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  159. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  160. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IFlatField.py +0 -0
  161. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IFocusModel.py +0 -0
  162. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IFocuser.py +0 -0
  163. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IGain.py +0 -0
  164. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IImageFormat.py +0 -0
  165. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IImageType.py +0 -0
  166. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IMode.py +0 -0
  167. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IModule.py +0 -0
  168. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IMotion.py +0 -0
  169. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IMultiFiber.py +0 -0
  170. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
  171. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
  172. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IPointingAltAz.py +0 -0
  173. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IPointingBody.py +0 -0
  174. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
  175. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
  176. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
  177. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
  178. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IPointingRaDec.py +0 -0
  179. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IPointingSeries.py +0 -0
  180. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IReady.py +0 -0
  181. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IRoof.py +0 -0
  182. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IRotation.py +0 -0
  183. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IRunnable.py +0 -0
  184. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IRunning.py +0 -0
  185. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IScriptRunner.py +0 -0
  186. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ISpectrograph.py +0 -0
  187. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IStartStop.py +0 -0
  188. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IStructuredConfig.py +0 -0
  189. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ISyncTarget.py +0 -0
  190. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ITelescope.py +0 -0
  191. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ITemperatures.py +0 -0
  192. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ITrackingMode.py +0 -0
  193. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/ITrackingRate.py +0 -0
  194. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IVideo.py +0 -0
  195. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IWeather.py +0 -0
  196. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/IWindow.py +0 -0
  197. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/__init__.py +0 -0
  198. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/interfaces/interface.py +0 -0
  199. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/__init__.py +0 -0
  200. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/camerasettings.py +0 -0
  201. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/fitsheader.py +0 -0
  202. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/fitsnamespace.py +0 -0
  203. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/follow.py +0 -0
  204. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/motionstatus.py +0 -0
  205. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/pipeline.py +0 -0
  206. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/mixins/waitformotion.py +0 -0
  207. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/__init__.py +0 -0
  208. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/__init__.py +0 -0
  209. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/adaptive.py +0 -0
  210. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/basespectrograph.py +0 -0
  211. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/dummycamera.py +0 -0
  212. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  213. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/dummyvideo.py +0 -0
  214. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/camera/pipelinecamera.py +0 -0
  215. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/flatfield/__init__.py +0 -0
  216. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/flatfield/flatfield.py +0 -0
  217. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/flatfield/pointing.py +0 -0
  218. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/flatfield/scheduler.py +0 -0
  219. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/focus/__init__.py +0 -0
  220. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/focus/dummyautofocus.py +0 -0
  221. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/focus/focusmodel.py +0 -0
  222. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/focus/focusseries.py +0 -0
  223. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/image/__init__.py +0 -0
  224. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/image/imagewriter.py +0 -0
  225. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/image/pipeline.py +0 -0
  226. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/image/seeing.py +0 -0
  227. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/module.py +0 -0
  228. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/__init__.py +0 -0
  229. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/_base.py +0 -0
  230. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/_baseguiding.py +0 -0
  231. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/acquisition.py +0 -0
  232. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/autoguiding.py +0 -0
  233. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  234. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/dummyguiding.py +0 -0
  235. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  236. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  237. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  238. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  239. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  240. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
  241. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/robotic/__init__.py +0 -0
  242. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/robotic/mastermind.py +0 -0
  243. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/robotic/pointing.py +0 -0
  244. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/robotic/scheduler.py +0 -0
  245. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/robotic/scriptrunner.py +0 -0
  246. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/roof/__init__.py +0 -0
  247. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/roof/basedome.py +0 -0
  248. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/roof/baseroof.py +0 -0
  249. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/roof/dummyroof.py +0 -0
  250. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/telescope/__init__.py +0 -0
  251. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
  252. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/telescope/basetelescope.py +0 -0
  253. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
  254. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
  255. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
  256. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/test/__init__.py +0 -0
  257. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/test/standalone.py +0 -0
  258. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/__init__.py +0 -0
  259. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/dummymode.py +0 -0
  260. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/fluentlogger.py +0 -0
  261. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/httpfilecache.py +0 -0
  262. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/kiosk.py +0 -0
  263. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/matrix.py +0 -0
  264. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/stellarium.py +0 -0
  265. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/telegram.py +0 -0
  266. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/utils/trigger.py +0 -0
  267. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/weather/__init__.py +0 -0
  268. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/weather/mockweather.py +0 -0
  269. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/weather/weather.py +0 -0
  270. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/weather/weather_api.py +0 -0
  271. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/modules/weather/weather_state.py +0 -0
  272. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/object.py +0 -0
  273. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/py.typed +0 -0
  274. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/__init__.py +0 -0
  275. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/observation.py +0 -0
  276. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/__init__.py +0 -0
  277. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/_executor.py +0 -0
  278. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  279. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  280. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  281. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
  282. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  283. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  284. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  285. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  286. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  287. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  288. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  289. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  290. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  291. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  292. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  293. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  294. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  295. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/random.py +0 -0
  296. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  297. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  298. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  299. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  300. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
  301. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  302. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
  303. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
  304. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  305. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  306. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  307. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  308. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/targets/target.py +0 -0
  309. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  310. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/__init__.py +0 -0
  311. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  312. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
  313. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
  314. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
  315. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/control/__init__.py +0 -0
  316. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/control/cases.py +0 -0
  317. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/control/conditional.py +0 -0
  318. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/control/parallel.py +0 -0
  319. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/control/selector.py +0 -0
  320. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/control/sequential.py +0 -0
  321. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/exceptions.py +0 -0
  322. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  323. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
  324. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
  325. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  326. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/script.py +0 -0
  327. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  328. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
  329. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  330. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/scripts/utils/log.py +0 -0
  331. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/__init__.py +0 -0
  332. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/backend/__init__.py +0 -0
  333. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/backend/observationarchive.py +0 -0
  334. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/backend/taskarchive.py +0 -0
  335. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  336. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  337. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  338. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/__init__.py +0 -0
  339. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/_portal.py +0 -0
  340. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  341. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  342. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/configdb.py +0 -0
  343. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  344. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  345. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  346. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
  347. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
  348. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  349. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/task.py +0 -0
  350. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  351. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  352. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/memory/__init__.py +0 -0
  353. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  354. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  355. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/observationarchive.py +0 -0
  356. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/storage/taskarchive.py +0 -0
  357. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/task.py +0 -0
  358. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/taskrunner.py +0 -0
  359. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/__init__.py +0 -0
  360. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/archive/__init__.py +0 -0
  361. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/archive/archive.py +0 -0
  362. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  363. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  364. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  365. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  366. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
  367. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  368. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  369. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  370. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  371. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  372. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  373. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  374. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  375. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  376. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  377. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/__init__.py +0 -0
  378. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/average.py +0 -0
  379. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/cache.py +0 -0
  380. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/config.py +0 -0
  381. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/config_schema.py +0 -0
  382. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/coordinates.py +0 -0
  383. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/curvefit.py +0 -0
  384. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/enums.py +0 -0
  385. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/exceptions.py +0 -0
  386. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/fits.py +0 -0
  387. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/focusseries/__init__.py +0 -0
  388. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/focusseries/base.py +0 -0
  389. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/focusseries/photometry.py +0 -0
  390. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/focusseries/projection.py +0 -0
  391. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/grids/__init__.py +0 -0
  392. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/grids/filters.py +0 -0
  393. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/grids/grid.py +0 -0
  394. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/grids/gridnode.py +0 -0
  395. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/grids/pipeline.py +0 -0
  396. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/__init__.py +0 -0
  397. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/camera/__init__.py +0 -0
  398. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  399. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  400. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  401. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  402. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  403. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  404. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  405. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/http.py +0 -0
  406. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/influxdb.py +0 -0
  407. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/logger.py +0 -0
  408. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/logging/__init__.py +0 -0
  409. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/logging/context.py +0 -0
  410. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/logging/resolvableerror.py +0 -0
  411. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/modulegui.py +0 -0
  412. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/offsets/__init__.py +0 -0
  413. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  414. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/offsets/applyoffsets.py +0 -0
  415. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  416. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/parallel.py +0 -0
  417. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/pipeline/__init__.py +0 -0
  418. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/pipeline/pipeline.py +0 -0
  419. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/pipeline/progress.py +0 -0
  420. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/pipeline/reduction.py +0 -0
  421. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/pipeline/reduction_base.py +0 -0
  422. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/publisher/__init__.py +0 -0
  423. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/publisher/csv.py +0 -0
  424. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/publisher/http.py +0 -0
  425. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/publisher/log.py +0 -0
  426. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/publisher/multi.py +0 -0
  427. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/publisher/publisher.py +0 -0
  428. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/serialization.py +0 -0
  429. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/shellcommand.py +0 -0
  430. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/threads/__init__.py +0 -0
  431. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/threads/lockwithabort.py +0 -0
  432. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/time.py +0 -0
  433. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/units.py +0 -0
  434. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/utils/versions.py +0 -0
  435. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/version.py +0 -0
  436. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/__init__.py +0 -0
  437. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/archivefile.py +0 -0
  438. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/bufferedfile.py +0 -0
  439. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/file.py +0 -0
  440. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/filelists/__init__.py +0 -0
  441. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/filelists/filelist.py +0 -0
  442. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/filelists/testing.py +0 -0
  443. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/localfile.py +0 -0
  444. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/memfile.py +0 -0
  445. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/sftpfile.py +0 -0
  446. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/smbfile.py +0 -0
  447. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/sshfile.py +0 -0
  448. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/pyobs/vfs/tempfile.py +0 -0
  449. {pyobs_core-2.0.0.dev90 → pyobs_core-2.0.0.dev92}/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.dev90
3
+ Version: 2.0.0.dev92
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -260,7 +260,16 @@ class Application:
260
260
  # stem-mismatch check below. Give it the config file's own stem instead.
261
261
  cfg = {**cfg, "comm": {"class": f"{DummyComm.__module__}.{DummyComm.__name__}", "name": config_stem}}
262
262
  log.info("Creating module from class %s...", klass.__name__)
263
- self._module = get_object(cfg, Module)
263
+ try:
264
+ self._module = get_object(cfg, Module)
265
+ except Exception:
266
+ # The module-creation failure (bad kwargs, broken __init__ chain, ...) is
267
+ # otherwise only visible as an uncaught traceback on stderr -- which is
268
+ # /dev/null for daemonized modules (--pid-file, as started by pyobsd /
269
+ # pyobs-web-admin), so it silently vanishes from all logs. Record it through
270
+ # the configured handlers (file/journald) before propagating.
271
+ log.exception("Could not create module %s from config %s.", klass.__name__, config)
272
+ raise
264
273
 
265
274
  # a config file whose stem doesn't match its own module's name silently splits
266
275
  # PYOBS_MODULE log tagging in two: logging that falls through both execute()
@@ -84,7 +84,7 @@ class WeatherAwareMixin:
84
84
  if not self.__weatheraware_is_error_state:
85
85
  log.error("Telescope is in error mode, cannot park.")
86
86
  else:
87
- log.warning("Weather is bad, shutting down.")
87
+ log.info("Weather is bad, shutting down.")
88
88
  await msi.park()
89
89
 
90
90
  # save error state
@@ -524,9 +524,42 @@ class BaseCamera(
524
524
  is_top = max(top, img_top)
525
525
  is_bottom = min(top + height, img_top + img_height)
526
526
 
527
- # for simplicity we allow prescan/overscan only in one dimension
528
- if (left < is_left or left + width > is_right) and (top < is_top or top + height > is_bottom):
529
- log.warning("BIASSEC/TRIMSEC can only be calculated with a prescan/overscan on one axis only.")
527
+ # prescan/overscan exists only where the window extends beyond the visible frame
528
+ x_ovs = img_left < left or img_left + img_width > left + width
529
+ y_ovs = img_top < top or img_top + img_height > top + height
530
+
531
+ # window extends beyond the visible frame on both axes: the simple calculation
532
+ # below only supports prescan/overscan on one axis
533
+ if x_ovs and y_ovs:
534
+ log.warning(
535
+ "Cannot compute BIASSEC/TRIMSEC: window %dx%d at %d,%d has prescan/overscan on both axes "
536
+ "(only one axis is supported).",
537
+ img_width,
538
+ img_height,
539
+ img_left,
540
+ img_top,
541
+ )
542
+ return
543
+
544
+ # window lies fully inside the visible frame: there is no prescan/overscan at all
545
+ if (
546
+ not x_ovs
547
+ and not y_ovs
548
+ and (
549
+ img_left > left
550
+ or img_left + img_width < left + width
551
+ or img_top > top
552
+ or img_top + img_height < top + height
553
+ )
554
+ ):
555
+ log.info(
556
+ "No BIASSEC/TRIMSEC: window %dx%d at %d,%d lies fully inside the visible frame "
557
+ "(no prescan/overscan).",
558
+ img_width,
559
+ img_height,
560
+ img_left,
561
+ img_top,
562
+ )
530
563
  return
531
564
 
532
565
  # comments
@@ -1,4 +1,6 @@
1
1
  import asyncio
2
+ import hashlib
3
+ import hmac
2
4
  import io
3
5
  import json
4
6
  import logging
@@ -33,12 +35,18 @@ INDEX_HTML = """
33
35
  <title>Title</title>
34
36
  </head>
35
37
  <body>
36
- <img src="/video.mjpg" width="100%">
38
+ <img src="video.mjpg" width="100%">
37
39
  </body>
38
40
  </html>
39
41
 
40
42
  """
41
43
 
44
+ # session cookie for browser login (see _check_auth); only used when a token is configured
45
+ _COOKIE_NAME = "pyobs_video_session"
46
+ _COOKIE_LIFETIME = 24 * 60 * 60 # 24 h, in seconds
47
+ # delay on a wrong login password, to slow brute force (compare is constant-time either way)
48
+ _LOGIN_FAILURE_SLEEP = 1.0
49
+
42
50
 
43
51
  async def calc_expose_timeout(webcam: IExposureTime, *args: Any, **kwargs: Any) -> float:
44
52
  """Calculates timeout for grabe_image()."""
@@ -71,7 +79,14 @@ class LastImage(NamedTuple):
71
79
 
72
80
 
73
81
  class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCMeta):
74
- """Base class for all webcam modules."""
82
+ """Base class for all webcam modules.
83
+
84
+ The built-in HTTP server serves the MJPEG live view, the raw frame stream and cached FITS
85
+ images. Set the ``token`` parameter to protect all of them: machine clients send
86
+ ``Authorization: Bearer <token>`` (as :class:`pyobs.vfs.HttpFile` does), browsers log in once
87
+ at ``/login`` and get an HMAC-signed session cookie that rides the same-origin ``<img>``
88
+ requests. Without a token (the default), no auth is enforced.
89
+ """
75
90
 
76
91
  __module__ = "pyobs.modules.camera"
77
92
 
@@ -90,6 +105,7 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
90
105
  flip: bool = False,
91
106
  sleep_time: int = 60,
92
107
  fits_header_timeout: float = 15.0,
108
+ token: str | None = None,
93
109
  **kwargs: Any,
94
110
  ):
95
111
  """Creates a new BaseWebcam.
@@ -112,6 +128,8 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
112
128
  flip: Whether to flip around Y axis.
113
129
  sleep_time: Time in s with inactivity after which the camera should go to sleep.
114
130
  fits_header_timeout: Maximum seconds to wait for a peer's FITS headers before skipping them.
131
+ token: Shared secret required in the "Authorization: Bearer <token>" header (or a
132
+ login-page cookie) for stream and data access. If None (default), no auth is enforced.
115
133
  """
116
134
  super().__init__(
117
135
  fits_namespaces=fits_namespaces,
@@ -153,12 +171,23 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
153
171
  self._cache = DataCache(cache_size)
154
172
 
155
173
  # define web server
174
+ self._token = token
175
+ # serializes /login attempts so _LOGIN_FAILURE_SLEEP actually caps the guess rate;
176
+ # without it, concurrent connections each sleep independently and the rate scales
177
+ # with concurrency instead of being capped
178
+ self._login_lock = asyncio.Lock()
156
179
  self._app = web.Application()
157
180
  routes = [web.get("/ping", self.ping_handler), web.get("/{filename}", self.image_handler)]
158
181
  if self._video_path is not None:
159
182
  routes += [web.get("/", self.web_handler), web.get("/video.mjpg", self.video_handler)]
160
183
  if self._raw_path is not None:
161
184
  routes.append(web.get("/video.raw", self.raw_handler))
185
+ if self._token is not None:
186
+ routes += [
187
+ web.get("/login", self.login_handler),
188
+ web.post("/login", self.login_post_handler),
189
+ web.get("/logout", self.logout_handler),
190
+ ]
162
191
  self._app.add_routes(routes)
163
192
  self._runner = web.AppRunner(self._app)
164
193
  self._site: web.TCPSite | None = None
@@ -196,6 +225,165 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
196
225
  """Whether the server is started."""
197
226
  return self._is_listening
198
227
 
228
+ def _sign(self, expiry: int) -> str:
229
+ """HMAC-SHA256 of ``expiry``, keyed with the configured token, as a hex digest.
230
+
231
+ Shared by _make_session_value (signs) and _check_cookie (verifies), so the two can't
232
+ drift apart on hash algo/encoding.
233
+
234
+ Args:
235
+ expiry: Session expiry timestamp being signed.
236
+
237
+ Returns:
238
+ Hex-encoded HMAC signature.
239
+ """
240
+ token = self._token
241
+ if token is None:
242
+ raise ValueError("Cannot sign a session without a configured token.")
243
+ return hmac.new(token.encode(), str(expiry).encode(), hashlib.sha256).hexdigest()
244
+
245
+ def _make_session_value(self) -> str:
246
+ """Build the value for the login session cookie.
247
+
248
+ Stateless and HMAC-signed -- the cookie does not contain the token itself:
249
+
250
+ ``value = "{expiry_ts}.{hex}"`` where ``hex = HMAC-SHA256(key=token, msg=str(expiry_ts))``
251
+
252
+ Returns:
253
+ Cookie value.
254
+ """
255
+ expiry = int(time.time()) + _COOKIE_LIFETIME
256
+ return f"{expiry}.{self._sign(expiry)}"
257
+
258
+ def _check_bearer(self, request: web.Request) -> bool:
259
+ """Whether the request carries the token as "Authorization: Bearer <token>".
260
+
261
+ Only called (via _check_auth) once a token is configured.
262
+
263
+ Args:
264
+ request: Request to check.
265
+
266
+ Returns:
267
+ True if the header matches the configured token.
268
+ """
269
+ prefix = "Bearer "
270
+ header = request.headers.get("Authorization", "")
271
+ if not header.startswith(prefix):
272
+ return False
273
+ return hmac.compare_digest(header[len(prefix) :], self._token or "")
274
+
275
+ def _check_cookie(self, request: web.Request) -> bool:
276
+ """Whether the request carries a valid, unexpired session cookie.
277
+
278
+ Only called (via _check_auth) once a token is configured.
279
+
280
+ Args:
281
+ request: Request to check.
282
+
283
+ Returns:
284
+ True if the cookie's HMAC signature verifies and it has not expired.
285
+ """
286
+ value = request.cookies.get(_COOKIE_NAME)
287
+ if value is None:
288
+ return False
289
+ try:
290
+ expiry_str, signature = value.rsplit(".", 1)
291
+ expiry = int(expiry_str)
292
+ except ValueError:
293
+ return False
294
+ if expiry < time.time():
295
+ return False
296
+ return hmac.compare_digest(signature, self._sign(expiry))
297
+
298
+ def _check_auth(self, request: web.Request) -> None:
299
+ """Raises HTTPUnauthorized if a token is configured and the request doesn't carry it.
300
+
301
+ Accepts either a valid "Authorization: Bearer <token>" header or a valid session cookie.
302
+ No-op when no token is configured. Same contract as HttpFileCache._check_auth: the
303
+ exception type is identical regardless of which handler called it.
304
+
305
+ Args:
306
+ request: Request to check.
307
+ """
308
+ if self._token is None:
309
+ return
310
+ if not (self._check_bearer(request) or self._check_cookie(request)):
311
+ raise web.HTTPUnauthorized()
312
+
313
+ async def login_handler(self, request: web.Request) -> web.Response:
314
+ """Handles GET access to /login and returns the login form.
315
+
316
+ Served unauthenticated: this is the bootstrap a browser needs, since it cannot attach an
317
+ "Authorization" header to an <img> request. Only registered when a token is configured.
318
+
319
+ Args:
320
+ request: Request to respond to.
321
+
322
+ Returns:
323
+ Response containing the login form.
324
+ """
325
+ html = """<!DOCTYPE html>
326
+ <html lang="en">
327
+ <head>
328
+ <meta charset="UTF-8">
329
+ <title>Login</title>
330
+ </head>
331
+ <body>
332
+ <form method="post" action="/login">
333
+ <label>Password: <input type="password" name="token" autofocus></label>
334
+ <button type="submit">Login</button>
335
+ </form>
336
+ </body>
337
+ </html>
338
+ """
339
+ return web.Response(text=html, content_type="text/html")
340
+
341
+ async def login_post_handler(self, request: web.Request) -> web.Response:
342
+ """Handles POST access to /login, verifying the password and issuing the session cookie.
343
+
344
+ Args:
345
+ request: Request to respond to.
346
+
347
+ Returns:
348
+ 303 redirect to / on success, 401 on wrong password.
349
+ """
350
+ token = self._token
351
+ if token is None:
352
+ # login routes are only registered when a token is configured -- defensive
353
+ raise web.HTTPUnauthorized()
354
+ data = await request.post()
355
+ password = str(data.get("token", ""))
356
+ async with self._login_lock:
357
+ # serialized: caps the guess rate at 1/_LOGIN_FAILURE_SLEEP regardless of how many
358
+ # connections attempt concurrently
359
+ if not hmac.compare_digest(password, token):
360
+ # the compare above is constant-time; this sleep additionally slows brute force
361
+ await asyncio.sleep(_LOGIN_FAILURE_SLEEP)
362
+ raise web.HTTPUnauthorized()
363
+ response = web.HTTPSeeOther("/")
364
+ response.set_cookie(
365
+ _COOKIE_NAME,
366
+ self._make_session_value(),
367
+ max_age=_COOKIE_LIFETIME,
368
+ path="/",
369
+ httponly=True,
370
+ samesite="Lax",
371
+ )
372
+ return response
373
+
374
+ async def logout_handler(self, request: web.Request) -> web.Response:
375
+ """Handles GET access to /logout and clears the session cookie.
376
+
377
+ Args:
378
+ request: Request to respond to.
379
+
380
+ Returns:
381
+ 303 redirect to /login.
382
+ """
383
+ response = web.HTTPSeeOther("/login")
384
+ response.del_cookie(_COOKIE_NAME, path="/")
385
+ return response
386
+
199
387
  async def web_handler(self, request: web.Request) -> web.Response:
200
388
  """Handles access to / and returns HTML page.
201
389
 
@@ -205,6 +393,11 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
205
393
  Returns:
206
394
  Response containing web page.
207
395
  """
396
+ # a browser landing on / should be taken to the login form, not shown a bare 401
397
+ try:
398
+ self._check_auth(request)
399
+ except web.HTTPUnauthorized:
400
+ raise web.HTTPSeeOther("/login")
208
401
  return web.Response(text=INDEX_HTML, content_type="text/html")
209
402
 
210
403
  async def ping_handler(self, request: web.Request) -> web.Response:
@@ -227,6 +420,7 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
227
420
  Returns:
228
421
  Response containing video stream.
229
422
  """
423
+ self._check_auth(request)
230
424
 
231
425
  # create response
232
426
  response = web.StreamResponse()
@@ -273,6 +467,8 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
273
467
  Returns:
274
468
  Response containing raw frame stream.
275
469
  """
470
+ # auth first: an unauthenticated request must not wake the camera
471
+ self._check_auth(request)
276
472
 
277
473
  # activate camera
278
474
  await self.activate_camera()
@@ -368,6 +564,7 @@ class BaseVideo(Module, ImageFitsHeaderMixin, IVideo, IImageType, metaclass=ABCM
368
564
  Returns:
369
565
  Response containing image.
370
566
  """
567
+ self._check_auth(request)
371
568
 
372
569
  # get filename
373
570
  filename = request.match_info["filename"]
@@ -29,6 +29,12 @@ class ImageWatcher(Module):
29
29
 
30
30
  Watches a path for new files and stores them in all given destinations. Only if all operations were successful,
31
31
  the file is deleted.
32
+
33
+ New files are not processed immediately, but only after a wait time (``wait_time``) has passed since they were
34
+ detected. This is mainly to make sure the file is fully written before it is read: while the inotify watcher
35
+ only reports files after they have been closed for writing (``CLOSE_WRITE``), the polling watcher and the
36
+ initial scan in ``open()`` can see a file while its write is still in progress. The same wait time is also
37
+ used to delay re-processing of files whose destination copy failed (see ``_worker``).
32
38
  """
33
39
 
34
40
  __module__ = "pyobs.modules.image"
@@ -50,7 +56,9 @@ class ImageWatcher(Module):
50
56
  destinations: Filename patterns for destinations.
51
57
  poll: If True, watchpath is polled instead of watched by inotify.
52
58
  poll_interval: Interval for polling in seconds, if poll is True.
53
- wait_time: Time in seconds between adding file to list and processing it.
59
+ wait_time: Time in seconds between adding a file to the list and processing it. Gives a file that is
60
+ still being written time to finish (relevant for poll mode and the initial scan) and spaces out
61
+ re-queued files after failed destination copies.
54
62
  """
55
63
  Module.__init__(self, **kwargs)
56
64
 
@@ -489,7 +489,7 @@ class FlatFielder(Object):
489
489
 
490
490
  # log and return
491
491
  if frac > self._allowed_offset_frac:
492
- log.warning("Deviation from target count (%.1f%%) is larger than allowed, retrying last image...", frac)
492
+ log.info("Deviation from target count (%.1f%%) is larger than allowed, retrying last image...", frac)
493
493
  return False
494
494
  else:
495
495
  log.info("Calculated new exposure time to be %.2fs.", self._exptime)
@@ -78,6 +78,14 @@ class HttpFile(BufferedFile):
78
78
  raise ValueError("No download URL given.")
79
79
  return urljoin(self._download_path, self.filename)
80
80
 
81
+ @property
82
+ def headers(self) -> dict[str, str]:
83
+ """Headers sent with GET/POST requests, e.g. "Authorization: Bearer <token>".
84
+
85
+ Returns a copy; mutating it does not affect this instance's headers.
86
+ """
87
+ return dict(self._headers)
88
+
81
89
  async def _download(self) -> None:
82
90
  """For read access, download the file into a local buffer.
83
91
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-core"
3
- version = "2.0.0.dev90"
3
+ version = "2.0.0.dev92"
4
4
  description = "robotic telescope software"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"