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