pyobs-core 1.52.18__tar.gz → 2.0.0.dev1__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 (440) hide show
  1. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/PKG-INFO +1 -1
  2. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/application.py +1 -1
  3. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/comm.py +112 -18
  4. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/proxy.py +38 -1
  5. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xmppclient.py +0 -1
  6. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xmppcomm.py +185 -4
  7. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/event.py +2 -1
  8. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/modules/getfitsheaders.py +2 -4
  9. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/spilled_light.py +3 -3
  10. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IAutoFocus.py +5 -2
  11. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/ICooling.py +12 -2
  12. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IExposureTime.py +5 -4
  13. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/ILatLon.py +5 -2
  14. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IOffsetsAltAz.py +8 -3
  15. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IOffsetsRaDec.py +8 -3
  16. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IPointingAltAz.py +6 -3
  17. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IPointingHGS.py +8 -3
  18. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IPointingHelioprojective.py +8 -3
  19. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IPointingRaDec.py +6 -3
  20. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IRotation.py +4 -3
  21. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/ITemperatures.py +3 -2
  22. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/interface.py +4 -0
  23. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/camerasettings.py +3 -6
  24. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/fitsheader.py +9 -9
  25. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/follow.py +3 -4
  26. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/waitformotion.py +9 -12
  27. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/weatheraware.py +2 -4
  28. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/camera/dummycamera.py +11 -0
  29. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/flatfield/flatfield.py +30 -39
  30. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/flatfield/pointing.py +5 -10
  31. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/flatfield/scheduler.py +11 -15
  32. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/focus/focusmodel.py +20 -35
  33. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/focus/focusseries.py +52 -56
  34. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/module.py +2 -2
  35. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/_base.py +2 -6
  36. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/_baseguiding.py +8 -9
  37. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/acquisition.py +37 -38
  38. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/autoguiding.py +17 -16
  39. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/robotic/pointing.py +7 -8
  40. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/autonomouswarning.py +17 -13
  41. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/kiosk.py +19 -19
  42. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/stellarium.py +7 -8
  43. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/telegram.py +10 -13
  44. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/trigger.py +2 -4
  45. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/object.py +21 -9
  46. pyobs_core-2.0.0.dev1/pyobs/robotic/scheduler/targets/__init__.py +6 -0
  47. pyobs_core-2.0.0.dev1/pyobs/robotic/scheduler/targets/helioprojectiveradialarget.py +32 -0
  48. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/calibration/darkbias.py +13 -16
  49. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/calibration/pointing.py +9 -12
  50. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/calibration/skyflats.py +21 -24
  51. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/control/selector.py +13 -9
  52. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/imaging/autofocus.py +16 -22
  53. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/imaging/imaging.py +60 -74
  54. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/utils/callmodule.py +6 -8
  55. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/scripts/autofocus.py +30 -41
  56. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/scripts/default.py +56 -69
  57. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/exptime/stellarexptime.py +23 -16
  58. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/flatfielder.py +72 -60
  59. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/enums.py +36 -1
  60. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/logging/context.py +1 -1
  61. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/shellcommand.py +10 -11
  62. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyproject.toml +2 -1
  63. pyobs_core-1.52.18/pyobs/comm/mock/__init__.py +0 -3
  64. pyobs_core-1.52.18/pyobs/comm/mock/mockcomm.py +0 -85
  65. pyobs_core-1.52.18/pyobs/robotic/scheduler/targets/__init__.py +0 -5
  66. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/.gitignore +0 -0
  67. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/LICENSE +0 -0
  68. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/__init__.py +0 -0
  69. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/background_task.py +0 -0
  70. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/cli/__init__.py +0 -0
  71. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/cli/_cli.py +0 -0
  72. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/cli/pyobs.py +0 -0
  73. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/cli/pyobsd.py +0 -0
  74. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/cli/pyobsw.py +0 -0
  75. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/__init__.py +0 -0
  76. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/commlogging.py +0 -0
  77. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/dbus/__init__.py +0 -0
  78. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/dbus/dbuscomm.py +0 -0
  79. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/dbus/patch.py +0 -0
  80. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/dummy/__init__.py +0 -0
  81. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/dummy/dummycomm.py +0 -0
  82. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/local/__init__.py +0 -0
  83. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/local/localcomm.py +0 -0
  84. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/local/localnetwork.py +0 -0
  85. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/__init__.py +0 -0
  86. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/rpc.py +0 -0
  87. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  88. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  89. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  90. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  91. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  92. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  93. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  94. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/__init__.py +0 -0
  95. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/badweather.py +0 -0
  96. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/exposurestatuschanged.py +0 -0
  97. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/filterchanged.py +0 -0
  98. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/focusfound.py +0 -0
  99. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/goodweather.py +0 -0
  100. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/log.py +0 -0
  101. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/modechanged.py +0 -0
  102. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/moduleclosed.py +0 -0
  103. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/moduleopened.py +0 -0
  104. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/motionstatuschanged.py +0 -0
  105. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/move.py +0 -0
  106. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/newimage.py +0 -0
  107. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/newspectrum.py +0 -0
  108. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/offsets.py +0 -0
  109. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/roofclosing.py +0 -0
  110. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/roofopened.py +0 -0
  111. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/taskfailed.py +0 -0
  112. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/taskfinished.py +0 -0
  113. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/taskstarted.py +0 -0
  114. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/events/testevent.py +0 -0
  115. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/__init__.py +0 -0
  116. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/image.py +0 -0
  117. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/meta/__init__.py +0 -0
  118. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/meta/altazoffsets.py +0 -0
  119. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/meta/exptime.py +0 -0
  120. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/meta/genericoffset.py +0 -0
  121. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/meta/onskydistance.py +0 -0
  122. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/meta/pixeloffsets.py +0 -0
  123. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/meta/radecoffsets.py +0 -0
  124. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/meta/skyoffsets.py +0 -0
  125. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processor.py +0 -0
  126. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/__init__.py +0 -0
  127. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  128. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/annotation/__init__.py +0 -0
  129. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/annotation/_pillow.py +0 -0
  130. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/annotation/circle.py +0 -0
  131. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/annotation/crosshair.py +0 -0
  132. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/annotation/text.py +0 -0
  133. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/astrometry/__init__.py +0 -0
  134. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  135. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  136. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  137. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  138. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  139. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  140. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/calibration/__init__.py +0 -0
  141. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  142. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  143. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/calibration/calibration.py +0 -0
  144. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/detection/__init__.py +0 -0
  145. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  146. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  147. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/detection/daophot.py +0 -0
  148. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/detection/pysep.py +0 -0
  149. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/detection/simpledisk.py +0 -0
  150. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  151. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/exptime/__init__.py +0 -0
  152. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/exptime/exptime.py +0 -0
  153. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/exptime/star.py +0 -0
  154. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/__init__.py +0 -0
  155. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  156. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/download.py +0 -0
  157. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/flip.py +0 -0
  158. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/grayscale.py +0 -0
  159. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/httpserver.py +0 -0
  160. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/normalize.py +0 -0
  161. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/save.py +0 -0
  162. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/saveimage.py +0 -0
  163. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/smooth.py +0 -0
  164. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/image/softbin.py +0 -0
  165. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/misc/__init__.py +0 -0
  166. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/misc/addmask.py +0 -0
  167. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  168. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/misc/circularmask.py +0 -0
  169. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/misc/createfilename.py +0 -0
  170. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  171. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/misc/removebackground.py +0 -0
  172. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/modules/__init__.py +0 -0
  173. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/__init__.py +0 -0
  174. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/astrometry.py +0 -0
  175. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  176. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  177. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  178. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  179. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  180. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/offsets.py +0 -0
  181. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  182. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/offsets/projected.py +0 -0
  183. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/photometry/__init__.py +0 -0
  184. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  185. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  186. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  187. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  188. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/photometry/photometry.py +0 -0
  189. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/photometry/photutil.py +0 -0
  190. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/photometry/pysep.py +0 -0
  191. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/wcs/__init__.py +0 -0
  192. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  193. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IAbortable.py +0 -0
  194. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IAcquisition.py +0 -0
  195. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IAutoGuiding.py +0 -0
  196. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IAutonomous.py +0 -0
  197. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IBinning.py +0 -0
  198. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/ICalibrate.py +0 -0
  199. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/ICamera.py +0 -0
  200. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IConfig.py +0 -0
  201. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IData.py +0 -0
  202. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IDome.py +0 -0
  203. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IExposure.py +0 -0
  204. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IFilters.py +0 -0
  205. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  206. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  207. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IFlatField.py +0 -0
  208. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IFocusModel.py +0 -0
  209. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IFocuser.py +0 -0
  210. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IGain.py +0 -0
  211. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IImageFormat.py +0 -0
  212. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IImageType.py +0 -0
  213. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IMode.py +0 -0
  214. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IModule.py +0 -0
  215. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IMotion.py +0 -0
  216. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IMultiFiber.py +0 -0
  217. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IPointingSeries.py +0 -0
  218. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IReady.py +0 -0
  219. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IRoof.py +0 -0
  220. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IRunnable.py +0 -0
  221. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IRunning.py +0 -0
  222. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IScriptRunner.py +0 -0
  223. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/ISpectrograph.py +0 -0
  224. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IStartStop.py +0 -0
  225. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/ISyncTarget.py +0 -0
  226. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/ITelescope.py +0 -0
  227. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IVideo.py +0 -0
  228. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IWeather.py +0 -0
  229. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/IWindow.py +0 -0
  230. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/interfaces/__init__.py +0 -0
  231. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/__init__.py +0 -0
  232. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/fitsnamespace.py +0 -0
  233. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/motionstatus.py +0 -0
  234. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/mixins/pipeline.py +0 -0
  235. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/__init__.py +0 -0
  236. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/camera/__init__.py +0 -0
  237. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/camera/adaptive.py +0 -0
  238. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/camera/basecamera.py +0 -0
  239. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/camera/basespectrograph.py +0 -0
  240. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/camera/basevideo.py +0 -0
  241. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  242. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/camera/pipelinecamera.py +0 -0
  243. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/flatfield/__init__.py +0 -0
  244. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/focus/__init__.py +0 -0
  245. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/image/__init__.py +0 -0
  246. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/image/imagewatcher.py +0 -0
  247. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/image/imagewriter.py +0 -0
  248. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/image/pipeline.py +0 -0
  249. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/image/seeing.py +0 -0
  250. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/__init__.py +0 -0
  251. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  252. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/dummyguiding.py +0 -0
  253. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  254. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  255. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  256. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  257. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  258. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
  259. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/robotic/__init__.py +0 -0
  260. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/robotic/mastermind.py +0 -0
  261. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/robotic/scheduler.py +0 -0
  262. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/robotic/scriptrunner.py +0 -0
  263. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/roof/__init__.py +0 -0
  264. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/roof/basedome.py +0 -0
  265. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/roof/baseroof.py +0 -0
  266. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/roof/dummyroof.py +0 -0
  267. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/telescope/__init__.py +0 -0
  268. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/telescope/basetelescope.py +0 -0
  269. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/telescope/dummytelescope.py +0 -0
  270. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/test/__init__.py +0 -0
  271. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/test/standalone.py +0 -0
  272. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/__init__.py +0 -0
  273. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/dummymode.py +0 -0
  274. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/fluentlogger.py +0 -0
  275. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/httpfilecache.py +0 -0
  276. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/utils/matrix.py +0 -0
  277. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/weather/__init__.py +0 -0
  278. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/weather/weather.py +0 -0
  279. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/weather/weather_api.py +0 -0
  280. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/modules/weather/weather_state.py +0 -0
  281. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/py.typed +0 -0
  282. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/__init__.py +0 -0
  283. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/observation.py +0 -0
  284. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/__init__.py +0 -0
  285. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  286. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  287. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  288. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
  289. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  290. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  291. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  292. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  293. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  294. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  295. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  296. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  297. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  298. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  299. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  300. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  301. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  302. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/random.py +0 -0
  303. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  304. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  305. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  306. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  307. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  308. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  309. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  310. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  311. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  312. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/targets/target.py +0 -0
  313. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  314. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/__init__.py +0 -0
  315. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  316. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/control/__init__.py +0 -0
  317. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/control/cases.py +0 -0
  318. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/control/conditional.py +0 -0
  319. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/control/parallel.py +0 -0
  320. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/control/sequential.py +0 -0
  321. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  322. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  323. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/script.py +0 -0
  324. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  325. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  326. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/scripts/utils/log.py +0 -0
  327. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/__init__.py +0 -0
  328. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/backend/__init__.py +0 -0
  329. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/backend/observationarchive.py +0 -0
  330. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/backend/taskarchive.py +0 -0
  331. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  332. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  333. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  334. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/__init__.py +0 -0
  335. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/_portal.py +0 -0
  336. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  337. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  338. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/configdb.py +0 -0
  339. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  340. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  341. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  342. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  343. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/task.py +0 -0
  344. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  345. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  346. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/memory/__init__.py +0 -0
  347. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  348. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  349. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/observationarchive.py +0 -0
  350. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/storage/taskarchive.py +0 -0
  351. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/task.py +0 -0
  352. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/taskrunner.py +0 -0
  353. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/__init__.py +0 -0
  354. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/archive/__init__.py +0 -0
  355. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/archive/archive.py +0 -0
  356. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  357. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  358. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  359. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  360. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  361. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  362. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  363. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  364. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  365. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  366. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  367. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  368. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  369. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  370. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/__init__.py +0 -0
  371. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/average.py +0 -0
  372. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/cache.py +0 -0
  373. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/config.py +0 -0
  374. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/coordinates.py +0 -0
  375. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/curvefit.py +0 -0
  376. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/exceptions.py +0 -0
  377. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/fits.py +0 -0
  378. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/focusseries/__init__.py +0 -0
  379. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/focusseries/base.py +0 -0
  380. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/focusseries/photometry.py +0 -0
  381. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/focusseries/projection.py +0 -0
  382. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/grids/__init__.py +0 -0
  383. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/grids/filters.py +0 -0
  384. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/grids/grid.py +0 -0
  385. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/grids/gridnode.py +0 -0
  386. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/grids/pipeline.py +0 -0
  387. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/__init__.py +0 -0
  388. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/camera/__init__.py +0 -0
  389. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  390. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  391. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  392. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  393. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  394. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  395. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  396. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/http.py +0 -0
  397. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/influxdb.py +0 -0
  398. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/logger.py +0 -0
  399. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/logging/__init__.py +0 -0
  400. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/logging/resolvableerror.py +0 -0
  401. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/modulegui.py +0 -0
  402. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/offsets/__init__.py +0 -0
  403. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  404. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/offsets/applyoffsets.py +0 -0
  405. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  406. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/parallel.py +0 -0
  407. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/pipeline/__init__.py +0 -0
  408. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/pipeline/night.py +0 -0
  409. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/pipeline/pipeline.py +0 -0
  410. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/publisher/__init__.py +0 -0
  411. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/publisher/csv.py +0 -0
  412. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/publisher/http.py +0 -0
  413. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/publisher/log.py +0 -0
  414. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/publisher/multi.py +0 -0
  415. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/publisher/publisher.py +0 -0
  416. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/serialization.py +0 -0
  417. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/simulation/__init__.py +0 -0
  418. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/simulation/camera.py +0 -0
  419. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/simulation/telescope.py +0 -0
  420. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/simulation/world.py +0 -0
  421. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/threads/__init__.py +0 -0
  422. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/threads/lockwithabort.py +0 -0
  423. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/time.py +0 -0
  424. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/utils/types.py +0 -0
  425. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/version.py +0 -0
  426. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/__init__.py +0 -0
  427. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/archivefile.py +0 -0
  428. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/bufferedfile.py +0 -0
  429. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/file.py +0 -0
  430. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/filelists/__init__.py +0 -0
  431. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/filelists/filelist.py +0 -0
  432. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/filelists/testing.py +0 -0
  433. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/httpfile.py +0 -0
  434. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/localfile.py +0 -0
  435. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/memfile.py +0 -0
  436. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/sftpfile.py +0 -0
  437. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/smbfile.py +0 -0
  438. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/sshfile.py +0 -0
  439. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/tempfile.py +0 -0
  440. {pyobs_core-1.52.18 → pyobs_core-2.0.0.dev1}/pyobs/vfs/vfs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-core
3
- Version: 1.52.18
3
+ Version: 2.0.0.dev1
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -98,7 +98,7 @@ class Application:
98
98
  super().__init__(identifier="pyobs", **kw)
99
99
 
100
100
  def _format_record(self, record: logging.LogRecord) -> list[tuple[str, Any]]:
101
- pairs = super()._format_record(record)
101
+ pairs: list[tuple[str, Any]] = super()._format_record(record)
102
102
  pairs.append(("PYOBS_MODULE", getattr(record, "pyobs_module", "")))
103
103
  return pairs
104
104
 
@@ -1,25 +1,25 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import functools
4
5
  import inspect
5
6
  import logging
6
7
  from collections.abc import Callable, Coroutine
7
- from typing import TYPE_CHECKING, Any, TypeVar, overload
8
+ from typing import TYPE_CHECKING, Any, overload
8
9
 
9
10
  import pyobs.interfaces
10
11
  from pyobs.events import Event, LogEvent, ModuleClosedEvent
11
12
  from pyobs.interfaces import Interface
12
13
 
13
14
  from .commlogging import CommLoggingHandler
14
- from .proxy import Proxy
15
+ from .proxy import Proxy, ProxyType, _ProxyContext
15
16
 
16
17
  if TYPE_CHECKING:
17
18
  from pyobs.modules import Module
18
19
 
19
- log = logging.getLogger(__name__)
20
-
20
+ StateCallback = Callable[[Any], None]
21
21
 
22
- ProxyType = TypeVar("ProxyType")
22
+ log = logging.getLogger(__name__)
23
23
 
24
24
 
25
25
  class Comm:
@@ -27,13 +27,13 @@ class Comm:
27
27
 
28
28
  __module__ = "pyobs.comm"
29
29
 
30
- def __init__(self, cache_proxies: bool = True):
30
+ def __init__(self) -> None:
31
31
  """Creates a comm module."""
32
32
 
33
33
  self._proxies: dict[str, Proxy] = {}
34
+ self._state_subscriptions: dict[str, list[tuple[type[Interface], StateCallback]]] = {}
34
35
  self._module: Module | None = None
35
36
  self._log_queue: asyncio.Queue[LogEvent] = asyncio.Queue()
36
- self._cache_proxies = cache_proxies
37
37
  self._logging_task: asyncio.Task[Any] | None = None
38
38
  self._event_handlers: dict[type[Event], list[Callable[[Event, str], Coroutine[Any, Any, bool]]]] = {}
39
39
  self._closing = asyncio.Event()
@@ -117,7 +117,7 @@ class Comm:
117
117
  return None
118
118
 
119
119
  # if client doesn't exist or we disabled caching, fetch a new proxy
120
- if client not in self._proxies or not self._cache_proxies:
120
+ if client not in self._proxies:
121
121
  # get interfaces
122
122
  try:
123
123
  interfaces = await self.get_interfaces(client)
@@ -126,18 +126,20 @@ class Comm:
126
126
 
127
127
  # create new proxy
128
128
  proxy = Proxy(self, client, interfaces)
129
+
130
+ # subscribe to state
131
+ for interface in interfaces:
132
+ if getattr(interface, "state", None) is not None:
133
+ await self.subscribe_state(client, interface, functools.partial(proxy.update_state, interface))
134
+
129
135
  self._proxies[client] = proxy
130
136
 
131
137
  # return proxy
132
138
  return self._proxies[client]
133
139
 
134
- @overload
135
- async def proxy(self, name_or_object: str | object, obj_type: type[ProxyType]) -> ProxyType: ...
136
-
137
- @overload
138
- async def proxy(self, name_or_object: str | object, obj_type: type[ProxyType] | None = None) -> Any: ...
139
-
140
- async def proxy(self, name_or_object: str | object, obj_type: type[ProxyType] | None = None) -> Any | ProxyType:
140
+ async def _resolve_proxy(
141
+ self, name_or_object: str | object, obj_type: type[ProxyType] | None = None
142
+ ) -> Any | ProxyType:
141
143
  """Returns object directly if it is of given type. Otherwise get proxy of client with given name and check type.
142
144
 
143
145
  If name_or_object is an object:
@@ -184,16 +186,41 @@ class Comm:
184
186
  # completely wrong...
185
187
  raise ValueError(f'Given parameter is neither a name nor an object of requested type "{obj_type}".')
186
188
 
187
- async def safe_proxy(
189
+ async def _safe_resolve_proxy(
188
190
  self, name_or_object: str | object, obj_type: type[ProxyType] | None = None
189
191
  ) -> Any | ProxyType | None:
190
192
  """Calls proxy() in a safe way and returns None instead of raising an exception."""
191
193
 
192
194
  try:
193
- return await self.proxy(name_or_object, obj_type)
195
+ return await self._resolve_proxy(name_or_object, obj_type)
194
196
  except ValueError:
195
197
  return None
196
198
 
199
+ @overload
200
+ def proxy(self, name_or_object: str | object, obj_type: type[ProxyType]) -> _ProxyContext[ProxyType]: ...
201
+ @overload
202
+ def proxy(self, name_or_object: str | object, obj_type: None = None) -> _ProxyContext[Any]: ...
203
+
204
+ def proxy(self, name_or_object: str | object, obj_type: type[ProxyType] | None = None) -> _ProxyContext[Any]:
205
+ """Returns a context manager; use as `async with self.proxy(...) as x:`."""
206
+ return _ProxyContext(self._resolve_proxy(name_or_object, obj_type))
207
+
208
+ @overload
209
+ def safe_proxy(
210
+ self, name_or_object: str | object, obj_type: type[ProxyType]
211
+ ) -> _ProxyContext[ProxyType | None]: ...
212
+ @overload
213
+ def safe_proxy(self, name_or_object: str | object, obj_type: None = None) -> _ProxyContext[Any]: ...
214
+
215
+ def safe_proxy(self, name_or_object: str | object, obj_type: type[ProxyType] | None = None) -> _ProxyContext[Any]:
216
+ """Same as proxy(), but yields None inside the block instead of raising."""
217
+ return _ProxyContext(self._safe_resolve_proxy(name_or_object, obj_type))
218
+
219
+ async def has_proxy(self, name_or_object: str | object, obj_type: type[Any] | None = None) -> bool:
220
+ """True if a proxy of the given type can currently be resolved. Doesn't keep a reference
221
+ to it, so doesn't need async with the way proxy()/safe_proxy() do."""
222
+ return await self._safe_resolve_proxy(name_or_object, obj_type) is not None
223
+
197
224
  async def _client_disconnected(self, event: Event, sender: str) -> bool:
198
225
  """Called when a client disconnects.
199
226
 
@@ -203,9 +230,15 @@ class Comm:
203
230
 
204
231
  """
205
232
 
206
- # if a client disconnects, we remove its proxy
233
+ # if a client disconnects, clear its proxy state then evict it
207
234
  if sender in self._proxies:
235
+ self._proxies[sender].clear_state()
208
236
  del self._proxies[sender]
237
+
238
+ # tear down any state subscriptions held for that client
239
+ for interface, callback in self._state_subscriptions.pop(sender, []):
240
+ await self.unsubscribe_state(sender, interface, callback)
241
+
209
242
  return True
210
243
 
211
244
  @property
@@ -397,6 +430,67 @@ class Comm:
397
430
  ) -> None:
398
431
  pass
399
432
 
433
+ async def set_state(self, interface: type[Interface], state: Any) -> None:
434
+ """Publish state for this module.
435
+
436
+ Args:
437
+ interface: Interface type for the state.
438
+ state: State object to publish.
439
+ """
440
+ await self._set_state(interface, state)
441
+
442
+ async def _set_state(self, interface: type[Interface], state: Any) -> None:
443
+ pass
444
+
445
+ async def subscribe_state(
446
+ self,
447
+ module: str,
448
+ interface: type[Interface],
449
+ callback: StateCallback,
450
+ ) -> None:
451
+ """Subscribe to state updates for a given module and interface.
452
+
453
+ Delivers the current value immediately on subscribe.
454
+
455
+ Args:
456
+ module: Name of remote module.
457
+ interface: Interface type to subscribe to.
458
+ callback: Called with state object on each update.
459
+ """
460
+ self._state_subscriptions.setdefault(module, []).append((interface, callback))
461
+ await self._subscribe_state(module, interface, callback)
462
+
463
+ async def _subscribe_state(
464
+ self,
465
+ module: str,
466
+ interface: type[Interface],
467
+ callback: StateCallback,
468
+ ) -> None:
469
+ pass
470
+
471
+ async def unsubscribe_state(
472
+ self,
473
+ module: str,
474
+ interface: type[Interface],
475
+ callback: StateCallback,
476
+ ) -> None:
477
+ """Unsubscribe from state updates.
478
+
479
+ Args:
480
+ module: Name of remote module.
481
+ interface: Interface type to unsubscribe from.
482
+ callback: Callback that was registered.
483
+ """
484
+ await self._unsubscribe_state(module, interface, callback)
485
+
486
+ async def _unsubscribe_state(
487
+ self,
488
+ module: str,
489
+ interface: type[Interface],
490
+ callback: StateCallback,
491
+ ) -> None:
492
+ pass
493
+
400
494
  def _send_event_to_module(self, event: Event, from_client: str) -> None:
401
495
  """Send an event to all connected modules.
402
496
 
@@ -2,7 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import inspect
4
4
  import types
5
- from typing import TYPE_CHECKING, Any, get_type_hints
5
+ from collections.abc import Coroutine
6
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar, get_type_hints
6
7
 
7
8
  from pyobs.interfaces import Interface
8
9
  from pyobs.utils.types import cast_bound_arguments_to_simple
@@ -10,6 +11,9 @@ from pyobs.utils.types import cast_bound_arguments_to_simple
10
11
  if TYPE_CHECKING:
11
12
  from pyobs.comm import Comm
12
13
 
14
+ """Class of a proxy."""
15
+ ProxyType = TypeVar("ProxyType")
16
+
13
17
 
14
18
  class Proxy:
15
19
  """A proxy for remote pyobs modules."""
@@ -48,6 +52,9 @@ class Proxy:
48
52
  # create methods
49
53
  self._methods = self._create_methods()
50
54
 
55
+ # store state
56
+ self._state: dict[type[Interface], Any] = {}
57
+
51
58
  @property
52
59
  def name(self) -> str:
53
60
  """Name of the client."""
@@ -150,5 +157,35 @@ class Proxy:
150
157
 
151
158
  return inner
152
159
 
160
+ def update_state(self, interface: type[Interface], state: Any) -> None:
161
+ """Called by Comm whenever a new state arrives. Not intended to be called directly by module code."""
162
+ self._state[interface] = state
163
+
164
+ def clear_state(self) -> None:
165
+ """Clear all cached state. Called by Comm when the remote module disconnects."""
166
+ self._state.clear()
167
+
168
+ def state(self, interface: type[Interface]) -> Any | None:
169
+ """Latest known state for the given interface, or None if nothing has arrived yet."""
170
+ return self._state.get(interface)
171
+
172
+
173
+ class _ProxyContext(Generic[ProxyType]):
174
+ """Returned by Comm.proxy() / Object.proxy() / Comm.safe_proxy(). Must be used as:
175
+ async with self.proxy("camera", ICooling) as camera:
176
+ ...
177
+ """
178
+
179
+ def __init__(self, coro: Coroutine[Any, Any, ProxyType]) -> None:
180
+ self._coro = coro
181
+
182
+ async def __aenter__(self) -> ProxyType:
183
+ return await self._coro
184
+
185
+ async def __aexit__(self, *exc_info: Any) -> None:
186
+ # intentionally a no-op: the underlying Proxy is owned and cached
187
+ # by Comm, not by this block, and stays alive for other callers.
188
+ pass
189
+
153
190
 
154
191
  __all__ = ["Proxy"]
@@ -29,7 +29,6 @@ class XmppClient(slixmpp.ClientXMPP):
29
29
 
30
30
  # stuff
31
31
  self._connect_event = asyncio.Event()
32
- self._logs_node = "logs"
33
32
  self._auth_event = asyncio.Event()
34
33
  self._auth_success = False
35
34
  self._jid_conflict = False
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import dataclasses
4
5
  import functools
5
6
  import json
6
7
  import logging
@@ -9,12 +10,15 @@ import ssl
9
10
  import time
10
11
  import xml.sax.saxutils
11
12
  from collections.abc import Callable, Coroutine
12
- from typing import TYPE_CHECKING, Any
13
+ from enum import StrEnum
14
+ from typing import TYPE_CHECKING, Annotated, Any, get_args, get_origin, get_type_hints
13
15
 
14
16
  import slixmpp
15
17
  import slixmpp.exceptions
16
18
  from slixmpp import ElementBase
17
19
  from slixmpp.xmlstream import ET
20
+ from slixmpp.xmlstream.handler import Callback
21
+ from slixmpp.xmlstream.matcher import MatchXMLMask
18
22
 
19
23
  from pyobs.comm import Comm
20
24
  from pyobs.events import Event, LogEvent, ModuleClosedEvent, ModuleOpenedEvent
@@ -36,6 +40,11 @@ class EventStanza(ElementBase):
36
40
  namespace = "pyobs:event"
37
41
 
38
42
 
43
+ class StateStanza(ElementBase):
44
+ name = "state"
45
+ namespace = "pyobs:state"
46
+
47
+
39
48
  class XmppComm(Comm):
40
49
  """A Comm class using XMPP.
41
50
 
@@ -157,6 +166,10 @@ class XmppComm(Comm):
157
166
  self._xmpp: XmppClient | None = None
158
167
  self._rpc: RPC | None = None
159
168
 
169
+ # pubsub for states
170
+ self._pubsub_service = f"pubsub.{self._domain}"
171
+ self._state_node_handlers: dict[str, tuple[type[Interface], list[Callable[[Any], None]]]] = {}
172
+
160
173
  def _set_module(self, module: Module) -> None:
161
174
  """Called, when the module connected to this Comm changes.
162
175
 
@@ -186,7 +199,22 @@ class XmppComm(Comm):
186
199
  self._xmpp = XmppClient(self._jid, self._password)
187
200
 
188
201
  # self._xmpp = slixmpp.ClientXMPP(self._jid, password)
189
- self._xmpp.add_event_handler("pubsub_publish", self._handle_event)
202
+ # Register directly with an XML mask rather than via pubsub_publish.
203
+ # slixmpp's StanzaPath matcher requires plugins to be lazily loaded
204
+ # before matching — for live notifications (not triggered by an IQ)
205
+ # this never happens, so pubsub_publish is never fired. MatchXMLMask
206
+ # works on raw XML and fires reliably for every pubsub notification.
207
+ self._xmpp.register_handler(
208
+ Callback(
209
+ "pyobs pubsub event",
210
+ MatchXMLMask(
211
+ '<message xmlns="jabber:client">'
212
+ '<event xmlns="http://jabber.org/protocol/pubsub#event">'
213
+ "<items /></event></message>"
214
+ ),
215
+ self._handle_event_sync,
216
+ )
217
+ )
190
218
  self._xmpp.add_event_handler("got_online", self._got_online)
191
219
  self._xmpp.add_event_handler("got_offline", self._got_offline)
192
220
  self._xmpp.add_event_handler("disconnected", self._disconnected)
@@ -421,8 +449,11 @@ class XmppComm(Comm):
421
449
  log.debug("Module %s does not seem to implement IModule, ignoring.", module)
422
450
  return
423
451
 
424
- # store interfaces
425
- self._interface_cache[jid].set_result(self._interface_names_to_classes(interface_names))
452
+ # store interfaces — guard against a second _got_online for the same JID
453
+ # (ejabberd may send multiple presence stanzas) racing against the first
454
+ future = self._interface_cache.get(jid)
455
+ if future is not None and not future.done():
456
+ future.set_result(self._interface_names_to_classes(interface_names))
426
457
 
427
458
  # append to list
428
459
  if jid not in self._online_clients:
@@ -533,6 +564,14 @@ class XmppComm(Comm):
533
564
  await self._safe_send(self.client["xep_0115"].update_caps)
534
565
  self.client.send_presence()
535
566
 
567
+ def _handle_event_sync(self, msg: Any) -> None:
568
+ """Synchronous entry point for the MatchXMLMask Callback.
569
+
570
+ Callback handlers must be synchronous; this creates an asyncio task
571
+ so that _handle_event can await safely.
572
+ """
573
+ asyncio.create_task(self._handle_event(msg))
574
+
536
575
  async def _handle_event(self, msg: Any) -> None:
537
576
  """Handles an event.
538
577
 
@@ -540,6 +579,26 @@ class XmppComm(Comm):
540
579
  msg: Received XMPP message.
541
580
  """
542
581
 
582
+ node = msg["pubsub_event"]["items"]["node"]
583
+
584
+ # State-node notifications: dispatch to handler if registered, then return.
585
+ # map_node_event() cannot be used here because the XEP-0060 plugin iterates
586
+ # Items.iterables (empty) to fire mapped events, so live notifications are
587
+ # silently dropped. _handle_event (pubsub_publish) fires reliably for all nodes.
588
+ if node.startswith("pyobs:state:"):
589
+ if node in self._state_node_handlers:
590
+ if len(msg.xml.findall("{urn:sleekxmpp:delay}delay")) == 0:
591
+ interface, callbacks = self._state_node_handlers[node]
592
+ payload = msg["pubsub_event"]["items"]["item"]["payload"]
593
+ if payload is not None:
594
+ state_obj = self._xml_to_dataclass(payload, interface.state)
595
+ for callback in callbacks:
596
+ callback(state_obj)
597
+ # If payload is None it's a retract notification for the old
598
+ # max_items:1 slot — the matching publish notification (with
599
+ # payload) arrives in the next pubsub_publish event.
600
+ return
601
+
543
602
  # get body, unescape it, parse it
544
603
  # node = msg['pubsub_event'][items']['node']
545
604
  body = json.loads(xml.sax.saxutils.unescape(msg["pubsub_event"]["items"]["item"]["payload"].text))
@@ -629,5 +688,127 @@ class XmppComm(Comm):
629
688
  else:
630
689
  return False, value
631
690
 
691
+ @staticmethod
692
+ def _state_namespace(interface: type[Interface]) -> str:
693
+ return f"urn:pyobs:state:{interface.__name__}:{interface.version}"
694
+
695
+ @staticmethod
696
+ def _state_node(module: str, interface: type[Interface]) -> str:
697
+ return f"pyobs:state:{module}:{interface.__name__}:{interface.version}"
698
+
699
+ @staticmethod
700
+ def _dataclass_to_xml(state: Any, namespace: str) -> ET.Element:
701
+ root = ET.Element(f"{{{namespace}}}state")
702
+ for f in dataclasses.fields(state):
703
+ value = getattr(state, f.name)
704
+ # Use ET.Element + append (not SubElement) so children stay in the
705
+ # empty namespace and elem.find("fieldname") works on the receiving side.
706
+ child = ET.Element(f.name)
707
+ if isinstance(value, bool):
708
+ child.text = "true" if value else "false"
709
+ elif isinstance(value, StrEnum):
710
+ child.text = value.value
711
+ else:
712
+ child.text = str(value)
713
+ root.append(child)
714
+ return root
715
+
716
+ @staticmethod
717
+ def _xml_to_dataclass(elem: ET.Element, state_cls: type) -> Any:
718
+ hints = get_type_hints(state_cls, include_extras=True)
719
+ # Extract namespace from root element tag, e.g. "{urn:pyobs:state:ICooling:1}state"
720
+ # Children may arrive namespaced (after ejabberd round-trip) or plain (locally),
721
+ # so try both forms.
722
+ ns = ""
723
+ if elem.tag.startswith("{"):
724
+ ns = elem.tag[1 : elem.tag.index("}")]
725
+ kwargs = {}
726
+ for f in dataclasses.fields(state_cls):
727
+ # Try namespaced lookup first, then plain
728
+ child = elem.find(f"{{{ns}}}{f.name}") if ns else None
729
+ if child is None:
730
+ child = elem.find(f.name)
731
+ if child is None or child.text is None:
732
+ continue
733
+ field_type = hints[f.name]
734
+ # unwrap Annotated[T, ...] → T for type dispatch
735
+ if get_origin(field_type) is Annotated:
736
+ field_type = get_args(field_type)[0]
737
+ if field_type is bool:
738
+ kwargs[f.name] = child.text == "true"
739
+ elif field_type is float:
740
+ kwargs[f.name] = float(child.text)
741
+ elif field_type is int:
742
+ kwargs[f.name] = int(child.text)
743
+ elif isinstance(field_type, type) and issubclass(field_type, StrEnum):
744
+ kwargs[f.name] = field_type(child.text)
745
+ else:
746
+ kwargs[f.name] = child.text
747
+ return state_cls(**kwargs)
748
+
749
+ async def _fetch_and_dispatch_state(
750
+ self, node: str, interface: type[Interface], callback: Callable[[Any], None]
751
+ ) -> None:
752
+ """Fetch the current item for *node* and dispatch it to *callback*.
753
+
754
+ Called when a live notification arrives without a payload — either a
755
+ retract stanza or a node whose deliver_payloads flag is off.
756
+ """
757
+ try:
758
+ result = await self._safe_send(self.client["xep_0060"].get_items, self._pubsub_service, node, max_items=1)
759
+ item_list = result["pubsub"]["items"]["items"]
760
+ if item_list:
761
+ callback(self._xml_to_dataclass(item_list[0]["payload"], interface.state))
762
+ except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout):
763
+ pass
764
+
765
+ async def _set_state(self, interface: type[Interface], state: Any) -> None:
766
+ node = self._state_node(self._module.name, interface)
767
+ stanza = StateStanza()
768
+ stanza.xml = self._dataclass_to_xml(state, self._state_namespace(interface))
769
+ await self._safe_send(self.client["xep_0060"].publish, self._pubsub_service, node, payload=stanza)
770
+
771
+ async def _subscribe_state(self, module: str, interface: type[Interface], callback: Callable[[Any], None]) -> None:
772
+ node = self._state_node(module, interface)
773
+ if node in self._state_node_handlers:
774
+ # Node already subscribed — just add the callback
775
+ self._state_node_handlers[node][1].append(callback)
776
+ else:
777
+ self._state_node_handlers[node] = (interface, [callback])
778
+ await self._safe_send(self.client["xep_0060"].subscribe, self._pubsub_service, node)
779
+
780
+ # "deliver the current value immediately on subscribe" -- hard requirement above
781
+ try:
782
+ result = await self._safe_send(self.client["xep_0060"].get_items, self._pubsub_service, node, max_items=1)
783
+ # result["pubsub"]["items"]["items"] uses plugin_multi_attrib to get
784
+ # the list of Item stanzas; item["payload"] returns the XML element
785
+ # via Item.get_payload(). Integer indexing items[0] is wrong here —
786
+ # it calls ElementBase.__getitem__("0") which returns a string.
787
+ item_list = result["pubsub"]["items"]["items"]
788
+ if item_list:
789
+ state_obj = self._xml_to_dataclass(item_list[0]["payload"], interface.state)
790
+ for cb in self._state_node_handlers.get(node, (None, []))[1]:
791
+ cb(state_obj)
792
+ except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout):
793
+ pass # node exists but nothing published yet
794
+
795
+ async def _unsubscribe_state(
796
+ self, module: str, interface: type[Interface], callback: Callable[[Any], None]
797
+ ) -> None:
798
+ node = self._state_node(module, interface)
799
+ if node in self._state_node_handlers:
800
+ _, callbacks = self._state_node_handlers[node]
801
+ try:
802
+ callbacks.remove(callback)
803
+ except ValueError:
804
+ pass
805
+ if not callbacks:
806
+ # Last subscriber — unsubscribe from ejabberd and remove handler
807
+ del self._state_node_handlers[node]
808
+ try:
809
+ await self._safe_send(self.client["xep_0060"].unsubscribe, self._pubsub_service, node)
810
+ except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout):
811
+ pass # already gone server-side
812
+
632
813
 
633
814
  __all__ = ["XmppComm"]
@@ -13,7 +13,8 @@ class Event:
13
13
  """Base class for all events."""
14
14
 
15
15
  __module__ = "pyobs.events"
16
- local = False
16
+ local: bool = False
17
+ version: int = 1
17
18
 
18
19
  def __init__(self, **kwargs: Any):
19
20
  self.uuid = str(uuid.uuid4())
@@ -101,11 +101,9 @@ class GetFitsHeaders(ImageProcessor):
101
101
  # loop all modules
102
102
  requests: list[Task[Any]] = []
103
103
  for sender in self._senders:
104
- # get proxy
105
- proxy = await self.proxy(sender, IFitsHeaderBefore)
106
-
107
104
  # request headers
108
- requests.append(asyncio.create_task(proxy.get_fits_header_before(self._namespace)))
105
+ async with self.proxy(sender, IFitsHeaderBefore) as proxy:
106
+ requests.append(asyncio.create_task(proxy.get_fits_header_before(self._namespace)))
109
107
 
110
108
  # copy image
111
109
  out = image.copy()
@@ -299,9 +299,9 @@ class SpilledLightGuiding(Offsets):
299
299
  return image
300
300
 
301
301
  async def _load_fibre_information(self) -> None:
302
- fibers = await self.comm.proxy(self._fibers, IMultiFiber)
303
- self._fibre_position = await fibers.get_pixel_position()
304
- self._inner_radius = await fibers.get_radius()
302
+ async with self.comm.proxy(self._fibers, IMultiFiber) as proxy:
303
+ self._fibre_position = await proxy.get_pixel_position()
304
+ self._inner_radius = await proxy.get_radius()
305
305
 
306
306
  async def _correct_for_binning(self, binning: int) -> None:
307
307
  self._fibre_position = (self._fibre_position[0] / binning, self._fibre_position[1] / binning)
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import ABCMeta, abstractmethod
4
- from typing import Any
4
+ from typing import Annotated, Any
5
5
 
6
+ from ..utils.enums import Unit
6
7
  from .IAbortable import IAbortable
7
8
 
8
9
 
@@ -12,7 +13,9 @@ class IAutoFocus(IAbortable, metaclass=ABCMeta):
12
13
  __module__ = "pyobs.interfaces"
13
14
 
14
15
  @abstractmethod
15
- async def auto_focus(self, count: int, step: float, exposure_time: float, **kwargs: Any) -> tuple[float, float]:
16
+ async def auto_focus(
17
+ self, count: int, step: float, exposure_time: Annotated[float, Unit.SECONDS], **kwargs: Any
18
+ ) -> tuple[float, float]:
16
19
  """Perform an auto-focus series.
17
20
 
18
21
  This method performs an auto-focus series with "count" images on each side of the initial guess and the given
@@ -1,15 +1,25 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import ABCMeta, abstractmethod
4
- from typing import Any
4
+ from dataclasses import dataclass
5
+ from typing import Annotated, Any
5
6
 
7
+ from ..utils.enums import Unit
6
8
  from .ITemperatures import ITemperatures
7
9
 
8
10
 
11
+ @dataclass
12
+ class CoolingState:
13
+ setpoint: Annotated[float, Unit.CELSIUS] | None
14
+ power: Annotated[int, Unit.PERCENT] | None
15
+ enabled: bool
16
+
17
+
9
18
  class ICooling(ITemperatures, metaclass=ABCMeta):
10
19
  """The module can control the cooling of a device."""
11
20
 
12
21
  __module__ = "pyobs.interfaces"
22
+ state = CoolingState
13
23
 
14
24
  @abstractmethod
15
25
  async def get_cooling(self, **kwargs: Any) -> tuple[bool, float, float]:
@@ -24,7 +34,7 @@ class ICooling(ITemperatures, metaclass=ABCMeta):
24
34
  ...
25
35
 
26
36
  @abstractmethod
27
- async def set_cooling(self, enabled: bool, setpoint: float, **kwargs: Any) -> None:
37
+ async def set_cooling(self, enabled: bool, setpoint: Annotated[float, Unit.CELSIUS], **kwargs: Any) -> None:
28
38
  """Enables/disables cooling and sets setpoint.
29
39
 
30
40
  Args: