pyobs-core 2.9.2__tar.gz → 2.10.1__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 (462) hide show
  1. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/PKG-INFO +1 -1
  2. pyobs_core-2.10.1/pyobs/interfaces/IPushNotifications.py +54 -0
  3. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/__init__.py +2 -1
  4. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/__init__.py +2 -0
  5. pyobs_core-2.10.1/pyobs/mixins/datasequence.py +132 -0
  6. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/basecamera.py +7 -75
  7. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/basespectrograph.py +10 -3
  8. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/pushnotifier.py +63 -19
  9. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyproject.toml +1 -1
  10. pyobs_core-2.9.2/pyobs/interfaces/IPushNotifications.py +0 -26
  11. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/.gitignore +0 -0
  12. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/LICENSE +0 -0
  13. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/__init__.py +0 -0
  14. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/application.py +0 -0
  15. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/background_task.py +0 -0
  16. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/cli/__init__.py +0 -0
  17. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/cli/_cli.py +0 -0
  18. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/cli/pyobs.py +0 -0
  19. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/cli/pyobsd.py +0 -0
  20. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/cli/pyobsw.py +0 -0
  21. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/__init__.py +0 -0
  22. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/comm.py +0 -0
  23. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/commlogging.py +0 -0
  24. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/dummy/__init__.py +0 -0
  25. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/dummy/dummycomm.py +0 -0
  26. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/local/__init__.py +0 -0
  27. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/local/localcomm.py +0 -0
  28. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/local/localnetwork.py +0 -0
  29. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/proxy.py +0 -0
  30. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/__init__.py +0 -0
  31. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/rpc.py +0 -0
  32. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/serializer.py +0 -0
  33. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  34. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  35. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  36. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  37. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  38. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  39. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  40. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xmppclient.py +0 -0
  41. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/comm/xmpp/xmppcomm.py +0 -0
  42. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/__init__.py +0 -0
  43. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/badweather.py +0 -0
  44. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/event.py +0 -0
  45. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/exposurestatuschanged.py +0 -0
  46. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/filterchanged.py +0 -0
  47. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/focusfound.py +0 -0
  48. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/goodweather.py +0 -0
  49. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/log.py +0 -0
  50. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/modechanged.py +0 -0
  51. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/moduleclosed.py +0 -0
  52. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/moduleopened.py +0 -0
  53. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/motionstatuschanged.py +0 -0
  54. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/move.py +0 -0
  55. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/newimage.py +0 -0
  56. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/newspectrum.py +0 -0
  57. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/offsets.py +0 -0
  58. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/roofclosing.py +0 -0
  59. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/roofopened.py +0 -0
  60. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/taskfailed.py +0 -0
  61. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/taskfinished.py +0 -0
  62. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/taskskipped.py +0 -0
  63. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/taskstarted.py +0 -0
  64. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/events/testevent.py +0 -0
  65. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/__init__.py +0 -0
  66. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/image.py +0 -0
  67. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/meta/__init__.py +0 -0
  68. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/meta/altazoffsets.py +0 -0
  69. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/meta/exptime.py +0 -0
  70. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/meta/genericoffset.py +0 -0
  71. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/meta/onskydistance.py +0 -0
  72. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/meta/pixeloffsets.py +0 -0
  73. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/meta/radecoffsets.py +0 -0
  74. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/meta/skyoffsets.py +0 -0
  75. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processor.py +0 -0
  76. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/__init__.py +0 -0
  77. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  78. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/annotation/__init__.py +0 -0
  79. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/annotation/_pillow.py +0 -0
  80. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/annotation/circle.py +0 -0
  81. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/annotation/crosshair.py +0 -0
  82. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/annotation/text.py +0 -0
  83. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/astrometry/__init__.py +0 -0
  84. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  85. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  86. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  87. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  88. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  89. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  90. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/calibration/__init__.py +0 -0
  91. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  92. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  93. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/calibration/calibration.py +0 -0
  94. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/detection/__init__.py +0 -0
  95. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  96. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  97. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/detection/daophot.py +0 -0
  98. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/detection/pysep.py +0 -0
  99. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/detection/simpledisk.py +0 -0
  100. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  101. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/exptime/__init__.py +0 -0
  102. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/exptime/exptime.py +0 -0
  103. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/exptime/star.py +0 -0
  104. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/__init__.py +0 -0
  105. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  106. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/download.py +0 -0
  107. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/flip.py +0 -0
  108. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/grayscale.py +0 -0
  109. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/httpserver.py +0 -0
  110. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/normalize.py +0 -0
  111. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/save.py +0 -0
  112. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/saveimage.py +0 -0
  113. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/smooth.py +0 -0
  114. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/image/softbin.py +0 -0
  115. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/misc/__init__.py +0 -0
  116. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/misc/addmask.py +0 -0
  117. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  118. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/misc/circularmask.py +0 -0
  119. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/misc/createfilename.py +0 -0
  120. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  121. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/misc/removebackground.py +0 -0
  122. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/modules/__init__.py +0 -0
  123. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
  124. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/__init__.py +0 -0
  125. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/astrometry.py +0 -0
  126. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  127. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  128. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  129. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  130. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  131. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/offsets.py +0 -0
  132. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  133. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/projected.py +0 -0
  134. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/offsets/spilled_light.py +0 -0
  135. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/photometry/__init__.py +0 -0
  136. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  137. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  138. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  139. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  140. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/photometry/photometry.py +0 -0
  141. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/photometry/photutil.py +0 -0
  142. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/photometry/pysep.py +0 -0
  143. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/wcs/__init__.py +0 -0
  144. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  145. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IAbortable.py +0 -0
  146. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IAcquisition.py +0 -0
  147. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IAutoFocus.py +0 -0
  148. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IAutoGuiding.py +0 -0
  149. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IAutonomous.py +0 -0
  150. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IBinning.py +0 -0
  151. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ICalibrate.py +0 -0
  152. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ICamera.py +0 -0
  153. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IConfig.py +0 -0
  154. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ICooling.py +0 -0
  155. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IData.py +0 -0
  156. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IDataSequence.py +0 -0
  157. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IDome.py +0 -0
  158. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IExposure.py +0 -0
  159. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IExposureTime.py +0 -0
  160. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IFilters.py +0 -0
  161. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  162. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  163. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IFlatField.py +0 -0
  164. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IFocusModel.py +0 -0
  165. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IFocuser.py +0 -0
  166. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IGain.py +0 -0
  167. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IImageFormat.py +0 -0
  168. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IImageType.py +0 -0
  169. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IMode.py +0 -0
  170. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IModule.py +0 -0
  171. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IMotion.py +0 -0
  172. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IMultiFiber.py +0 -0
  173. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
  174. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
  175. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IPointingAltAz.py +0 -0
  176. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IPointingBody.py +0 -0
  177. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
  178. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
  179. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
  180. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
  181. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IPointingRaDec.py +0 -0
  182. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IPointingSeries.py +0 -0
  183. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IReady.py +0 -0
  184. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IRobotic.py +0 -0
  185. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IRoboticScheduler.py +0 -0
  186. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IRoof.py +0 -0
  187. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IRotation.py +0 -0
  188. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IRunnable.py +0 -0
  189. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IRunning.py +0 -0
  190. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IScriptRunner.py +0 -0
  191. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ISpectrograph.py +0 -0
  192. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IStartStop.py +0 -0
  193. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IStructuredConfig.py +0 -0
  194. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ISyncTarget.py +0 -0
  195. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ITelescope.py +0 -0
  196. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ITemperatures.py +0 -0
  197. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ITrackingMode.py +0 -0
  198. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/ITrackingRate.py +0 -0
  199. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IVideo.py +0 -0
  200. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IWeather.py +0 -0
  201. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/IWindow.py +0 -0
  202. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/interfaces/interface.py +0 -0
  203. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/camerasettings.py +0 -0
  204. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/fitsheader.py +0 -0
  205. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/fitsnamespace.py +0 -0
  206. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/follow.py +0 -0
  207. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/motionstatus.py +0 -0
  208. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/pipeline.py +0 -0
  209. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/waitformotion.py +0 -0
  210. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/mixins/weatheraware.py +0 -0
  211. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/__init__.py +0 -0
  212. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/__init__.py +0 -0
  213. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/adaptive.py +0 -0
  214. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/basevideo.py +0 -0
  215. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/dummycamera.py +0 -0
  216. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  217. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/dummyvideo.py +0 -0
  218. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/camera/pipelinecamera.py +0 -0
  219. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/flatfield/__init__.py +0 -0
  220. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/flatfield/flatfield.py +0 -0
  221. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/flatfield/pointing.py +0 -0
  222. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/flatfield/scheduler.py +0 -0
  223. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/focus/__init__.py +0 -0
  224. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/focus/dummyautofocus.py +0 -0
  225. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/focus/focusmodel.py +0 -0
  226. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/focus/focusseries.py +0 -0
  227. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/image/__init__.py +0 -0
  228. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/image/imagewatcher.py +0 -0
  229. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/image/imagewriter.py +0 -0
  230. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/image/pipeline.py +0 -0
  231. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/image/seeing.py +0 -0
  232. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/module.py +0 -0
  233. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/__init__.py +0 -0
  234. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/_base.py +0 -0
  235. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/_baseguiding.py +0 -0
  236. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/acquisition.py +0 -0
  237. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/autoguiding.py +0 -0
  238. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  239. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/dummyguiding.py +0 -0
  240. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  241. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  242. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  243. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  244. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  245. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
  246. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/robotic/__init__.py +0 -0
  247. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/robotic/dummymastermind.py +0 -0
  248. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/robotic/dummyscheduler.py +0 -0
  249. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/robotic/mastermind.py +0 -0
  250. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/robotic/pointing.py +0 -0
  251. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/robotic/scheduler.py +0 -0
  252. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/robotic/scriptrunner.py +0 -0
  253. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/roof/__init__.py +0 -0
  254. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/roof/basedome.py +0 -0
  255. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/roof/baseroof.py +0 -0
  256. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/roof/dummyroof.py +0 -0
  257. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/telescope/__init__.py +0 -0
  258. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
  259. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/telescope/basetelescope.py +0 -0
  260. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
  261. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
  262. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
  263. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/test/__init__.py +0 -0
  264. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/test/standalone.py +0 -0
  265. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/__init__.py +0 -0
  266. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/dummymode.py +0 -0
  267. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/dummystructuredconfig.py +0 -0
  268. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/fluentlogger.py +0 -0
  269. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/httpfilecache.py +0 -0
  270. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/kiosk.py +0 -0
  271. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/matrix.py +0 -0
  272. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/stellarium.py +0 -0
  273. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/telegram.py +0 -0
  274. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/utils/trigger.py +0 -0
  275. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/weather/__init__.py +0 -0
  276. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/weather/mockweather.py +0 -0
  277. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/weather/weather.py +0 -0
  278. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/weather/weather_api.py +0 -0
  279. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/modules/weather/weather_state.py +0 -0
  280. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/object.py +0 -0
  281. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/py.typed +0 -0
  282. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/__init__.py +0 -0
  283. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/instruments.py +0 -0
  284. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/observation.py +0 -0
  285. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/__init__.py +0 -0
  286. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/_executor.py +0 -0
  287. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  288. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  289. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  290. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
  291. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  292. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  293. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  294. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  295. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  296. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  297. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  298. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  299. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  300. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  301. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  302. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  303. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  304. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/random.py +0 -0
  305. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  306. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  307. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  308. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  309. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
  310. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  311. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
  312. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
  313. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  314. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  315. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  316. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  317. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/targets/target.py +0 -0
  318. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  319. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/__init__.py +0 -0
  320. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  321. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
  322. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
  323. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
  324. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/control/__init__.py +0 -0
  325. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/control/cases.py +0 -0
  326. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/control/conditional.py +0 -0
  327. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/control/parallel.py +0 -0
  328. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/control/selector.py +0 -0
  329. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/control/sequential.py +0 -0
  330. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/exceptions.py +0 -0
  331. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  332. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
  333. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
  334. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  335. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/script.py +0 -0
  336. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  337. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
  338. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  339. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/scripts/utils/log.py +0 -0
  340. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/__init__.py +0 -0
  341. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  342. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  343. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  344. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/__init__.py +0 -0
  345. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/_portal.py +0 -0
  346. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  347. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  348. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/configdb.py +0 -0
  349. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  350. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  351. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  352. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
  353. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
  354. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  355. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/task.py +0 -0
  356. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  357. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  358. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/memory/__init__.py +0 -0
  359. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  360. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  361. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/observationarchive.py +0 -0
  362. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/portal/__init__.py +0 -0
  363. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/portal/observationarchive.py +0 -0
  364. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/portal/taskarchive.py +0 -0
  365. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/storage/taskarchive.py +0 -0
  366. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/task.py +0 -0
  367. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/taskrunner.py +0 -0
  368. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/__init__.py +0 -0
  369. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/archive/__init__.py +0 -0
  370. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/archive/archive.py +0 -0
  371. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  372. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  373. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/calibration.py +0 -0
  374. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  375. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  376. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
  377. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  378. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  379. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
  380. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  381. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  382. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  383. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  384. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  385. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  386. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  387. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  388. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/__init__.py +0 -0
  389. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/average.py +0 -0
  390. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/cache.py +0 -0
  391. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/config.py +0 -0
  392. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/config_schema.py +0 -0
  393. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/coordinates.py +0 -0
  394. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/curvefit.py +0 -0
  395. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/enums.py +0 -0
  396. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/exceptions.py +0 -0
  397. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/exptime_grouping.py +0 -0
  398. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/fits.py +0 -0
  399. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/focusseries/__init__.py +0 -0
  400. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/focusseries/base.py +0 -0
  401. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/focusseries/photometry.py +0 -0
  402. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/focusseries/projection.py +0 -0
  403. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/grids/__init__.py +0 -0
  404. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/grids/filters.py +0 -0
  405. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/grids/grid.py +0 -0
  406. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/grids/gridnode.py +0 -0
  407. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/grids/pipeline.py +0 -0
  408. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/__init__.py +0 -0
  409. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/camera/__init__.py +0 -0
  410. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  411. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  412. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  413. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  414. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  415. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  416. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  417. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/http.py +0 -0
  418. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/influxdb.py +0 -0
  419. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/logger.py +0 -0
  420. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/logging/__init__.py +0 -0
  421. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/logging/context.py +0 -0
  422. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/logging/resolvableerror.py +0 -0
  423. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/modulegui.py +0 -0
  424. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/offsets/__init__.py +0 -0
  425. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  426. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/offsets/applyoffsets.py +0 -0
  427. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  428. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/parallel.py +0 -0
  429. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/pipeline/__init__.py +0 -0
  430. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/pipeline/pipeline.py +0 -0
  431. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/pipeline/progress.py +0 -0
  432. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/pipeline/reduction.py +0 -0
  433. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/pipeline/reduction_base.py +0 -0
  434. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/publisher/__init__.py +0 -0
  435. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/publisher/csv.py +0 -0
  436. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/publisher/http.py +0 -0
  437. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/publisher/log.py +0 -0
  438. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/publisher/multi.py +0 -0
  439. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/publisher/publisher.py +0 -0
  440. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/serialization.py +0 -0
  441. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/shellcommand.py +0 -0
  442. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/threads/__init__.py +0 -0
  443. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/threads/lockwithabort.py +0 -0
  444. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/time.py +0 -0
  445. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/units.py +0 -0
  446. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/utils/versions.py +0 -0
  447. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/version.py +0 -0
  448. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/__init__.py +0 -0
  449. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/archivefile.py +0 -0
  450. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/bufferedfile.py +0 -0
  451. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/file.py +0 -0
  452. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/filelists/__init__.py +0 -0
  453. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/filelists/filelist.py +0 -0
  454. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/filelists/testing.py +0 -0
  455. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/httpfile.py +0 -0
  456. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/localfile.py +0 -0
  457. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/memfile.py +0 -0
  458. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/sftpfile.py +0 -0
  459. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/smbfile.py +0 -0
  460. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/sshfile.py +0 -0
  461. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/tempfile.py +0 -0
  462. {pyobs_core-2.9.2 → pyobs_core-2.10.1}/pyobs/vfs/vfs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyobs-core
3
- Version: 2.9.2
3
+ Version: 2.10.1
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABCMeta, abstractmethod
4
+ from enum import StrEnum
5
+ from typing import Any
6
+
7
+ from .interface import Interface
8
+
9
+
10
+ class PushNotificationType(StrEnum):
11
+ """Distinct alert kinds `PushNotifier` can emit, one opt-in/out toggle each.
12
+
13
+ Extensible: future kinds (bad weather, roof open, guiding lost) become new members here.
14
+ Stored preferences keep working across such an addition, since they are stored as the
15
+ enum's string values -- a kind added later simply defaults to on, and a stored value with
16
+ no matching member never compares equal to anything.
17
+ """
18
+
19
+ MODULE_ERROR = "module_error"
20
+ LOG_ERROR = "log_error"
21
+ LOG_CRITICAL = "log_critical"
22
+
23
+
24
+ class IPushNotifications(Interface, metaclass=ABCMeta):
25
+ """The module accepts device registrations for push notifications."""
26
+
27
+ __module__ = "pyobs.interfaces"
28
+
29
+ @abstractmethod
30
+ async def register_device(self, token: str, platform: str = "android", **kwargs: Any) -> None:
31
+ """Register a device to receive push notifications.
32
+
33
+ Args:
34
+ token: FCM/APNs device token.
35
+ platform: Device platform ("android" or "ios"). Stored as given; only "android"
36
+ actually receives pushes in v1 (see specs/design/push-notification-module.md).
37
+ """
38
+ ...
39
+
40
+ @abstractmethod
41
+ async def set_preferences(self, types: list[PushNotificationType], **kwargs: Any) -> None:
42
+ """Set which notification types this caller wants to receive.
43
+
44
+ Keyed by the calling account (`sender`), not by device -- applies to every device that
45
+ account has registered, present and future. A caller that never calls this receives all
46
+ types (all-on default); calling it with an empty list opts out of everything.
47
+
48
+ Args:
49
+ types: Notification types the caller wants to receive.
50
+ """
51
+ ...
52
+
53
+
54
+ __all__ = ["IPushNotifications", "PushNotificationType"]
@@ -51,7 +51,7 @@ from .IPointingHelioprojective import HelioprojectiveState, IPointingHelioprojec
51
51
  from .IPointingOrbitalElements import IPointingOrbitalElements, OrbitalElements
52
52
  from .IPointingRaDec import IPointingRaDec, RaDecState
53
53
  from .IPointingSeries import IPointingSeries
54
- from .IPushNotifications import IPushNotifications
54
+ from .IPushNotifications import IPushNotifications, PushNotificationType
55
55
  from .IReady import IReady, ReadyState
56
56
  from .IRobotic import IRobotic, RoboticState, RoboticTask
57
57
  from .IRoboticScheduler import IRoboticScheduler, SchedulerState
@@ -151,6 +151,7 @@ __all__ = [
151
151
  "RaDecState",
152
152
  "IPointingSeries",
153
153
  "IPushNotifications",
154
+ "PushNotificationType",
154
155
  "IReady",
155
156
  "ReadyState",
156
157
  "IRobotic",
@@ -5,6 +5,7 @@ Mixins are classes that can be inherited from to automatically add some function
5
5
  __title__ = "Mixins"
6
6
 
7
7
  from .camerasettings import CameraSettingsMixin
8
+ from .datasequence import DataSequenceMixin
8
9
  from .fitsheader import (
9
10
  FilterHeaderMixin,
10
11
  FitsHeaderMixin,
@@ -26,6 +27,7 @@ __all__ = [
26
27
  "WaitForMotionMixin",
27
28
  "WeatherAwareMixin",
28
29
  "CameraSettingsMixin",
30
+ "DataSequenceMixin",
29
31
  "ImageFitsHeaderMixin",
30
32
  "SpectrumFitsHeaderMixin",
31
33
  "FilterHeaderMixin",
@@ -0,0 +1,132 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ from abc import ABCMeta, abstractmethod
6
+ from typing import Any
7
+
8
+ from pyobs.interfaces import DataSequenceState, IDataSequence
9
+ from pyobs.modules import Module
10
+ from pyobs.utils import exceptions as exc
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+
15
+ class DataSequenceMixin(IDataSequence, metaclass=ABCMeta):
16
+ """Implements IDataSequence.grab_sequence()/abort_sequence() on top of a host's grab_data().
17
+
18
+ Shared by BaseCamera and BaseSpectrograph, whose grab_data() implementations differ only in
19
+ what they grab. The host class must:
20
+ - be a Module (comm is used to publish DataSequenceState)
21
+ - implement grab_data(broadcast, **kwargs) -> str
22
+ - implement _sequence_busy(), reporting whether it's busy outside of a running sequence
23
+ (e.g. mid-exposure)
24
+ - call _abort_data_sequence() from its own abort() implementation, so a hard abort also cuts
25
+ short a sequence waiting out its inter-grab delay (there's no exception to catch in that
26
+ case, unlike an abort during grab_data() itself, which grab_data() surfaces as a
27
+ PyobsError that _run_sequence() below already ends the sequence on)
28
+ """
29
+
30
+ __module__ = "pyobs.mixins"
31
+
32
+ def __init__(self, **kwargs: Any):
33
+ # count_left > 0 means a sequence is currently running (also between individual grabs,
34
+ # while the host's own status is briefly IDLE again)
35
+ self._sequence_count_left = 0
36
+ self._sequence_task: asyncio.Task[None] | None = None
37
+ self._sequence_delay_abort = asyncio.Event()
38
+ super().__init__(**kwargs)
39
+
40
+ async def _datasequence_open(self) -> None:
41
+ """Call from the host's open() to publish the initial idle sequence state."""
42
+ if not isinstance(self, Module):
43
+ raise ValueError("This is not a module.")
44
+ await self.comm.set_state(IDataSequence, DataSequenceState(count_total=0, count_left=0))
45
+
46
+ @abstractmethod
47
+ async def grab_data(self, broadcast: bool = True, **kwargs: Any) -> str: ...
48
+
49
+ @abstractmethod
50
+ def _sequence_busy(self) -> bool:
51
+ """Whether the device is currently busy outside of a running sequence."""
52
+ ...
53
+
54
+ async def grab_sequence(self, count: int, broadcast: bool = True, delay: float = 0, **kwargs: Any) -> None:
55
+ """Start a sequence of `count` grabs. Returns immediately; progress is available via
56
+ the pushed DataSequenceState.
57
+
58
+ Args:
59
+ count: Number of grabs to take.
60
+ broadcast: Broadcast existence of each grab.
61
+ delay: Seconds to wait between the end of one grab and the start of the next.
62
+ Does not apply after the last grab.
63
+
64
+ Raises:
65
+ InvalidArgumentError: If count or delay is out of range.
66
+ DeviceBusyError: If the device is already busy (exposing or already running a
67
+ sequence).
68
+ """
69
+ if not isinstance(self, Module):
70
+ raise ValueError("This is not a module.")
71
+
72
+ if count < 1:
73
+ raise exc.InvalidArgumentError("count must be >= 1.")
74
+ if delay < 0:
75
+ raise exc.InvalidArgumentError("delay must be >= 0.")
76
+
77
+ if self._sequence_count_left > 0 or self._sequence_busy():
78
+ raise exc.DeviceBusyError("Cannot start new sequence because device is not idle.")
79
+
80
+ log.info("Starting sequence of %d grabs...", count)
81
+ self._sequence_count_left = count
82
+ await self.comm.set_state(IDataSequence, DataSequenceState(count_total=count, count_left=count))
83
+ self._sequence_task = asyncio.create_task(self._run_sequence(count, broadcast, delay))
84
+
85
+ async def _run_sequence(self, count_total: int, broadcast: bool, delay: float) -> None:
86
+ """Runs a sequence of grab_data() calls, started by grab_sequence()."""
87
+ if not isinstance(self, Module):
88
+ raise ValueError("This is not a module.")
89
+
90
+ try:
91
+ while self._sequence_count_left > 0:
92
+ try:
93
+ await self.grab_data(broadcast=broadcast)
94
+ except exc.PyobsError:
95
+ log.exception("Grab failed during sequence, aborting sequence.")
96
+ break
97
+ self._sequence_count_left -= 1
98
+ await self.comm.set_state(
99
+ IDataSequence, DataSequenceState(count_total=count_total, count_left=self._sequence_count_left)
100
+ )
101
+
102
+ # wait between grabs, unless this was the last one or the sequence was
103
+ # aborted in the meantime -- either abort_sequence() or abort() cuts this short
104
+ if self._sequence_count_left > 0 and delay > 0:
105
+ self._sequence_delay_abort.clear()
106
+ try:
107
+ await asyncio.wait_for(self._sequence_delay_abort.wait(), timeout=delay)
108
+ except TimeoutError:
109
+ pass
110
+ finally:
111
+ log.info("Finished sequence.")
112
+ self._sequence_count_left = 0
113
+ self._sequence_task = None
114
+ await self.comm.set_state(IDataSequence, DataSequenceState(count_total=0, count_left=0))
115
+
116
+ async def abort_sequence(self, **kwargs: Any) -> None:
117
+ """Stop the sequence after the current grab. The grab currently in progress, if any,
118
+ finishes normally; no further grabs in the sequence are started.
119
+ """
120
+ log.info("Aborting sequence after current grab...")
121
+ self._abort_data_sequence()
122
+
123
+ def _abort_data_sequence(self) -> None:
124
+ """Clear sequence bookkeeping and cut short an in-progress inter-grab delay wait.
125
+
126
+ Call this from the host's own abort() alongside its hardware-specific abort logic.
127
+ """
128
+ self._sequence_count_left = 0
129
+ self._sequence_delay_abort.set()
130
+
131
+
132
+ __all__ = ["DataSequenceMixin"]
@@ -12,16 +12,15 @@ from astropy.io import fits
12
12
  from pyobs.events import BadWeatherEvent, Event, ExposureStatusChangedEvent, NewImageEvent
13
13
  from pyobs.images import Image
14
14
  from pyobs.interfaces import (
15
- DataSequenceState,
16
15
  ExposureState,
17
16
  ExposureTimeState,
18
17
  ICamera,
19
- IDataSequence,
20
18
  IExposure,
21
19
  IExposureTime,
22
20
  IImageType,
23
21
  ImageTypeState,
24
22
  )
23
+ from pyobs.mixins.datasequence import DataSequenceMixin
25
24
  from pyobs.mixins.fitsheader import ImageFitsHeaderMixin
26
25
  from pyobs.modules import Module, timeout
27
26
  from pyobs.utils import exceptions as exc
@@ -43,7 +42,7 @@ async def calc_expose_timeout(camera: BaseCamera, *args: Any, **kwargs: Any) ->
43
42
 
44
43
 
45
44
  class BaseCamera(
46
- Module, ImageFitsHeaderMixin, ICamera, IExposure, IExposureTime, IImageType, IDataSequence, metaclass=ABCMeta
45
+ Module, ImageFitsHeaderMixin, ICamera, IExposure, IExposureTime, IImageType, DataSequenceMixin, metaclass=ABCMeta
47
46
  ):
48
47
  """Base class for all camera modules."""
49
48
 
@@ -104,12 +103,6 @@ class BaseCamera(
104
103
  # multi-threading
105
104
  self.expose_abort = asyncio.Event()
106
105
 
107
- # grab_sequence() state -- count_left > 0 means a sequence is currently running
108
- # (also between individual grabs, while _camera_status is briefly IDLE again)
109
- self._sequence_count_left = 0
110
- self._sequence_task: asyncio.Task[None] | None = None
111
- self._sequence_delay_abort = asyncio.Event()
112
-
113
106
  # register exception
114
107
  self._register_exception(exc.GrabImageError, 3, timespan=600, callback=self._default_remote_error_callback)
115
108
 
@@ -138,7 +131,7 @@ class BaseCamera(
138
131
  exposure_time_left=0.0,
139
132
  ),
140
133
  )
141
- await self.comm.set_state(IDataSequence, DataSequenceState(count_total=0, count_left=0))
134
+ await self._datasequence_open()
142
135
 
143
136
  async def set_exposure_time(self, exposure_time: float, **kwargs: Any) -> None:
144
137
  """Set the exposure time in seconds.
@@ -402,69 +395,9 @@ class BaseCamera(
402
395
  # return filename
403
396
  return filename
404
397
 
405
- async def grab_sequence(self, count: int, broadcast: bool = True, delay: float = 0, **kwargs: Any) -> None:
406
- """Start a sequence of `count` images. Returns immediately; progress is available via
407
- the pushed DataSequenceState.
408
-
409
- Args:
410
- count: Number of images to take.
411
- broadcast: Broadcast existence of each image.
412
- delay: Seconds to wait between the end of one image and the start of the next.
413
- Does not apply after the last image.
414
-
415
- Raises:
416
- InvalidArgumentError: If count or delay is out of range.
417
- DeviceBusyError: If camera is already busy (exposing or already running a sequence).
418
- """
419
- if count < 1:
420
- raise exc.InvalidArgumentError("count must be >= 1.")
421
- if delay < 0:
422
- raise exc.InvalidArgumentError("delay must be >= 0.")
423
-
424
- # already running a sequence, or mid-exposure outside of one?
425
- if self._sequence_count_left > 0 or self._camera_status != ExposureStatus.IDLE:
426
- raise exc.DeviceBusyError("Cannot start new sequence because camera is not idle.")
427
-
428
- log.info("Starting sequence of %d images...", count)
429
- self._sequence_count_left = count
430
- await self.comm.set_state(IDataSequence, DataSequenceState(count_total=count, count_left=count))
431
- self._sequence_task = asyncio.create_task(self._run_sequence(count, broadcast, delay))
432
-
433
- async def _run_sequence(self, count_total: int, broadcast: bool, delay: float) -> None:
434
- """Runs a sequence of grab_data() calls, started by grab_sequence()."""
435
- try:
436
- while self._sequence_count_left > 0:
437
- try:
438
- await self.grab_data(broadcast=broadcast)
439
- except exc.PyobsError:
440
- log.exception("Grab failed during sequence, aborting sequence.")
441
- break
442
- self._sequence_count_left -= 1
443
- await self.comm.set_state(
444
- IDataSequence, DataSequenceState(count_total=count_total, count_left=self._sequence_count_left)
445
- )
446
-
447
- # wait between images, unless this was the last one or the sequence was
448
- # aborted in the meantime -- either abort_sequence() or abort() cuts this short
449
- if self._sequence_count_left > 0 and delay > 0:
450
- self._sequence_delay_abort.clear()
451
- try:
452
- await asyncio.wait_for(self._sequence_delay_abort.wait(), timeout=delay)
453
- except TimeoutError:
454
- pass
455
- finally:
456
- log.info("Finished sequence.")
457
- self._sequence_count_left = 0
458
- self._sequence_task = None
459
- await self.comm.set_state(IDataSequence, DataSequenceState(count_total=0, count_left=0))
460
-
461
- async def abort_sequence(self, **kwargs: Any) -> None:
462
- """Stop the sequence after the current image. The image currently exposing, if any,
463
- finishes normally; no further images in the sequence are started.
464
- """
465
- log.info("Aborting sequence after current image...")
466
- self._sequence_count_left = 0
467
- self._sequence_delay_abort.set()
398
+ def _sequence_busy(self) -> bool:
399
+ """Whether the camera is busy exposing outside of a running sequence."""
400
+ return self._camera_status != ExposureStatus.IDLE
468
401
 
469
402
  async def _abort_exposure(self) -> None:
470
403
  """Abort the running exposure. Should be implemented by derived class.
@@ -484,8 +417,7 @@ class BaseCamera(
484
417
  # set abort event
485
418
  log.info("Aborting current image and sequence...")
486
419
  self.expose_abort.set()
487
- self._sequence_count_left = 0
488
- self._sequence_delay_abort.set()
420
+ self._abort_data_sequence()
489
421
 
490
422
  # do camera-specific abort
491
423
  await self._abort_exposure()
@@ -10,6 +10,7 @@ from astropy.io import fits
10
10
 
11
11
  from pyobs.events import ExposureStatusChangedEvent, NewSpectrumEvent
12
12
  from pyobs.interfaces import ExposureState, IExposure, ISpectrograph
13
+ from pyobs.mixins.datasequence import DataSequenceMixin
13
14
  from pyobs.mixins.fitsheader import SpectrumFitsHeaderMixin
14
15
  from pyobs.modules import Module, timeout
15
16
  from pyobs.utils import exceptions as exc
@@ -24,7 +25,7 @@ class ExposureInfo(NamedTuple):
24
25
  start: datetime
25
26
 
26
27
 
27
- class BaseSpectrograph(Module, SpectrumFitsHeaderMixin, ISpectrograph, IExposure, metaclass=ABCMeta):
28
+ class BaseSpectrograph(Module, SpectrumFitsHeaderMixin, ISpectrograph, IExposure, DataSequenceMixin, metaclass=ABCMeta):
28
29
  """Base class for all spectrograph modules."""
29
30
 
30
31
  __module__ = "pyobs.modules.camera"
@@ -66,6 +67,7 @@ class BaseSpectrograph(Module, SpectrumFitsHeaderMixin, ISpectrograph, IExposure
66
67
  await self.comm.set_state(
67
68
  IExposure, ExposureState(status=self._spectrograph_status, progress=0.0, exposure_time_left=0.0)
68
69
  )
70
+ await self._datasequence_open()
69
71
 
70
72
  @abstractmethod
71
73
  async def _expose(self, abort_event: asyncio.Event) -> fits.HDUList:
@@ -206,13 +208,18 @@ class BaseSpectrograph(Module, SpectrumFitsHeaderMixin, ISpectrograph, IExposure
206
208
  self._spectrograph_status = status
207
209
  await self.comm.set_state(IExposure, ExposureState(status=status, progress=0.0, exposure_time_left=0.0))
208
210
 
211
+ def _sequence_busy(self) -> bool:
212
+ """Whether the spectrograph is busy exposing outside of a running sequence."""
213
+ return self._spectrograph_status != ExposureStatus.IDLE
214
+
209
215
  async def abort(self, **kwargs: Any) -> None:
210
- """Aborts the current exposure.
216
+ """Aborts the current exposure and sequence. Derived class must implement the actual
217
+ hardware abort for this!
211
218
 
212
219
  Raises:
213
220
  ValueError: If exposure could not be aborted.
214
221
  """
215
- pass
222
+ self._abort_data_sequence()
216
223
 
217
224
 
218
225
  __all__ = ["BaseSpectrograph"]
@@ -6,7 +6,7 @@ from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING, Any
7
7
 
8
8
  from pyobs.events import Event, LogEvent, ModuleOpenedEvent
9
- from pyobs.interfaces import Interface, IPushNotifications
9
+ from pyobs.interfaces import Interface, IPushNotifications, PushNotificationType
10
10
  from pyobs.modules import Module
11
11
  from pyobs.utils.time import Time
12
12
 
@@ -21,6 +21,10 @@ _QUEUE_MAX = 200
21
21
  # Log levels that trigger a push notification.
22
22
  _ALERT_LOG_LEVELS = {"ERROR", "CRITICAL"}
23
23
 
24
+ # All notification types, i.e. what a caller that never set a preference receives. Stored as the
25
+ # enum's string values, matching what `set_preferences` persists.
26
+ _ALL_TYPES = [t.value for t in PushNotificationType]
27
+
24
28
  # Timeout for blocking Firebase SDK calls run in a thread -- bounds the wait so a hung network
25
29
  # call can't stall the sender thread (and thus the alert queue) forever.
26
30
  _FIREBASE_CALL_TIMEOUT = 15.0
@@ -31,6 +35,7 @@ _STORAGE_FILE = "/pyobs/pushnotifier.yaml"
31
35
 
32
36
  @dataclass
33
37
  class _Alert:
38
+ kind: PushNotificationType
34
39
  title: str
35
40
  body: str
36
41
 
@@ -38,6 +43,9 @@ class _Alert:
38
43
  class PushNotifier(Module, IPushNotifications):
39
44
  """Relays fleet-wide module-`ERROR` state and `ERROR`/`CRITICAL` log events to mobile devices
40
45
  via Firebase Cloud Messaging (Android only in v1 -- see specs/design/push-notification-module.md).
46
+
47
+ Each caller may opt out of individual alert kinds via `set_preferences`; a caller that never
48
+ does receives all of them.
41
49
  """
42
50
 
43
51
  __module__ = "pyobs.modules.utils"
@@ -53,7 +61,7 @@ class PushNotifier(Module, IPushNotifications):
53
61
  Module.__init__(self, **kwargs)
54
62
 
55
63
  self._credentials_file = credentials_file
56
- self._devices: dict[str, list[dict[str, Any]]] = {}
64
+ self._devices: dict[str, dict[str, Any]] = {}
57
65
  self._alert_queue: asyncio.Queue[_Alert] = asyncio.Queue(maxsize=_QUEUE_MAX)
58
66
  self._fcm_app: App | None = None
59
67
 
@@ -63,11 +71,16 @@ class PushNotifier(Module, IPushNotifications):
63
71
  """Open module."""
64
72
  await Module.open(self)
65
73
 
66
- # load registered devices
74
+ # load registered devices, migrating the legacy {sender: [devices]} shape in place -- a
75
+ # value that is still a bare list predates per-user preferences, and gets wrapped with an
76
+ # absent `preferences` key, which the reader treats as "all types on".
67
77
  try:
68
- self._devices = await self.vfs.read_yaml(_STORAGE_FILE)
78
+ raw = await self.vfs.read_yaml(_STORAGE_FILE)
69
79
  except FileNotFoundError:
70
- self._devices = {}
80
+ raw = {}
81
+ self._devices = {
82
+ sender: ({"devices": value} if isinstance(value, list) else value) for sender, value in (raw or {}).items()
83
+ }
71
84
 
72
85
  # init Firebase
73
86
  await self._init_firebase()
@@ -152,7 +165,11 @@ class PushNotifier(Module, IPushNotifications):
152
165
  def _callback(state: Any) -> None:
153
166
  status = getattr(state, "status", None)
154
167
  if status is not None and str(status).upper() == "ERROR":
155
- self._enqueue_alert(f"{module}: ERROR", f"{iface.__name__} reports an ERROR state.")
168
+ self._enqueue_alert(
169
+ PushNotificationType.MODULE_ERROR,
170
+ f"{module}: ERROR",
171
+ f"{iface.__name__} reports an ERROR state.",
172
+ )
156
173
 
157
174
  return _callback
158
175
 
@@ -182,36 +199,39 @@ class PushNotifier(Module, IPushNotifications):
182
199
  if entry.level not in _ALERT_LOG_LEVELS:
183
200
  return False
184
201
 
185
- self._enqueue_alert(f"{sender}: {entry.level}", entry.message)
202
+ kind = PushNotificationType.LOG_CRITICAL if entry.level == "CRITICAL" else PushNotificationType.LOG_ERROR
203
+ self._enqueue_alert(kind, f"{sender}: {entry.level}", entry.message)
186
204
  return True
187
205
 
188
- def _enqueue_alert(self, title: str, body: str) -> None:
206
+ def _enqueue_alert(self, kind: PushNotificationType, title: str, body: str) -> None:
189
207
  """Queue an alert for sending, dropping it if the queue is full.
190
208
 
191
209
  Args:
210
+ kind: Notification type of the alert.
192
211
  title: Alert title.
193
212
  body: Alert body.
194
213
  """
195
214
  if self._alert_queue.full():
196
215
  log.warning("Push notification queue full, dropping alert.")
197
216
  return
198
- self._alert_queue.put_nowait(_Alert(title=title, body=body))
217
+ self._alert_queue.put_nowait(_Alert(kind=kind, title=title, body=body))
199
218
 
200
219
  async def _sender_thread(self) -> None:
201
220
  """Drain the alert queue and send notifications one at a time.
202
221
 
203
- Consecutive duplicate alerts (same title and body) are suppressed, since one fixed rule
204
- set applies fleet-wide -- every registered device gets the exact same alert, so a single
205
- global dedup (rather than Telegram's per-user one) is enough.
222
+ Consecutive duplicate alerts (same kind, title and body) are suppressed. A single global
223
+ dedup (rather than Telegram's per-user one) is enough: for stable preferences a given
224
+ alert always targets the same recipient subset, so anyone who would receive a repeat
225
+ already received the original.
206
226
  """
207
- last: tuple[str, str] | None = None
227
+ last: tuple[PushNotificationType, str, str] | None = None
208
228
  repeat_count = 0
209
229
 
210
230
  while True:
211
231
  alert = await self._alert_queue.get()
212
232
 
213
233
  try:
214
- key = (alert.title, alert.body)
234
+ key = (alert.kind, alert.title, alert.body)
215
235
  if key == last:
216
236
  repeat_count += 1
217
237
  continue
@@ -229,8 +249,18 @@ class PushNotifier(Module, IPushNotifications):
229
249
  finally:
230
250
  self._alert_queue.task_done()
231
251
 
252
+ @staticmethod
253
+ def _wants(entry: dict[str, Any], kind: PushNotificationType) -> bool:
254
+ """Whether the user an entry belongs to wants the given notification kind.
255
+
256
+ An entry with no `preferences` key at all (never set, or migrated from the legacy storage
257
+ shape) receives everything; an explicitly empty list means opted out of all kinds.
258
+ """
259
+ prefs = entry.get("preferences")
260
+ return kind.value in (_ALL_TYPES if prefs is None else prefs)
261
+
232
262
  async def _send_to_all_devices(self, alert: _Alert) -> None:
233
- """Send an alert to every registered Android device.
263
+ """Send an alert to every registered Android device whose owner wants this kind.
234
264
 
235
265
  Args:
236
266
  alert: The alert to send.
@@ -240,9 +270,9 @@ class PushNotifier(Module, IPushNotifications):
240
270
 
241
271
  tokens = [
242
272
  device["token"]
243
- for devices in self._devices.values()
244
- for device in devices
245
- if device.get("platform", "android") == "android"
273
+ for entry in self._devices.values()
274
+ for device in entry.get("devices", [])
275
+ if device.get("platform", "android") == "android" and self._wants(entry, alert.kind)
246
276
  ]
247
277
  if not tokens:
248
278
  return
@@ -276,11 +306,25 @@ class PushNotifier(Module, IPushNotifications):
276
306
  """
277
307
  sender = kwargs.get("sender", "")
278
308
 
279
- devices = self._devices.setdefault(sender, [])
309
+ entry = self._devices.setdefault(sender, {"devices": []})
310
+ devices = entry.setdefault("devices", [])
280
311
  devices[:] = [d for d in devices if d.get("token") != token]
281
312
  devices.append({"token": token, "platform": platform, "registered_at": Time.now().isot})
282
313
 
283
314
  await self.vfs.write_yaml(_STORAGE_FILE, self._devices)
284
315
 
316
+ async def set_preferences(self, types: list[PushNotificationType], **kwargs: Any) -> None:
317
+ """Set which notification types the calling account wants to receive.
318
+
319
+ Args:
320
+ types: Notification types to receive; an empty list opts out of everything.
321
+ """
322
+ sender = kwargs.get("sender", "")
323
+
324
+ entry = self._devices.setdefault(sender, {"devices": []})
325
+ entry["preferences"] = [PushNotificationType(t).value for t in types]
326
+
327
+ await self.vfs.write_yaml(_STORAGE_FILE, self._devices)
328
+
285
329
 
286
330
  __all__ = ["PushNotifier"]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-core"
3
- version = "2.9.2"
3
+ version = "2.10.1"
4
4
  description = "robotic telescope software"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"
@@ -1,26 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import ABCMeta, abstractmethod
4
- from typing import Any
5
-
6
- from .interface import Interface
7
-
8
-
9
- class IPushNotifications(Interface, metaclass=ABCMeta):
10
- """The module accepts device registrations for push notifications."""
11
-
12
- __module__ = "pyobs.interfaces"
13
-
14
- @abstractmethod
15
- async def register_device(self, token: str, platform: str = "android", **kwargs: Any) -> None:
16
- """Register a device to receive push notifications.
17
-
18
- Args:
19
- token: FCM/APNs device token.
20
- platform: Device platform ("android" or "ios"). Stored as given; only "android"
21
- actually receives pushes in v1 (see specs/design/push-notification-module.md).
22
- """
23
- ...
24
-
25
-
26
- __all__ = ["IPushNotifications"]
File without changes
File without changes