pyobs-core 2.14.2__tar.gz → 2.16.0__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.14.2 → pyobs_core-2.16.0}/PKG-INFO +1 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/comm.py +8 -10
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/rpc.py +58 -8
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xmppcomm.py +83 -10
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/__init__.py +4 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/event.py +37 -21
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingAltAz.py +2 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingRaDec.py +2 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/mastermind.py +38 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/scheduler.py +1 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/basetelescope.py +3 -2
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/weather.py +1 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/observation.py +15 -8
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +5 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/constraint.py +32 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +8 -2
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +5 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +9 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/timeconstraint.py +5 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/dataprovider.py +26 -5
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/follow.py +2 -2
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/pernight.py +6 -4
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/observationarchiveevolution.py +20 -22
- pyobs_core-2.16.0/pyobs/robotic/scheduler/observingperiod.py +89 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/ondemandscheduler.py +11 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/exceptions.py +1 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/exceptions.py +25 -6
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyproject.toml +1 -1
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/.gitignore +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/LICENSE +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/README.md +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/application.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/background_task.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/_cli.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/pyobs.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/pyobsd.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/pyobsw.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/commlogging.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/dummy/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/dummy/dummycomm.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/local/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/local/localcomm.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/local/localnetwork.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/proxy.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/serializer.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xmppclient.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/badweather.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/exposurestatuschanged.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/filterchanged.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/focusfound.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/goodweather.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/log.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/modechanged.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/moduleclosed.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/moduleopened.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/motionstatuschanged.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/move.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/newimage.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/newspectrum.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/offsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/roofclosing.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/roofopened.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/taskfailed.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/taskfinished.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/taskskipped.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/taskstarted.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/testevent.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/image.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/altazoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/datapipeline.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/exptime.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/genericoffset.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/onskydistance.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/pixeloffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/radecoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/skyoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processor.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/_daobackgroundremover.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/_pillow.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/circle.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/crosshair.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/text.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/astrometry.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/dotnet.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/calibration/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/calibration/calibration.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/_source_catalog.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/daophot.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/pysep.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/simpledisk.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/sourcedetection.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/exptime/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/exptime/exptime.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/exptime/star.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/addfitsheaders.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/download.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/flip.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/grayscale.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/httpserver.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/normalize.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/save.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/saveimage.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/smooth.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/softbin.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/addmask.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/circularmask.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/createfilename.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/removebackground.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/modules/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/astrometry.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/brighteststar.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/fitsheader.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/offsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/projected.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/spilled_light.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/photometry.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/photutil.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/pysep.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/combinestack.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/mean.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/median.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/sigmaclip.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/sum.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/wcs/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAbortable.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAcquisition.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAutoFocus.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAutoGuiding.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAutonomous.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IBinning.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ICalibrate.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ICamera.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IConfig.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ICooling.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IData.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IDataPipeline.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IDataSequence.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IDataStack.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IDome.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IExposure.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IExposureTime.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFilters.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFlatField.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFocusModel.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFocuser.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IGain.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IImageFormat.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IImageType.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IMode.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IModule.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IMotion.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IMultiFiber.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingBody.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingSeries.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPushNotifications.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IReady.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IResettable.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRobotic.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRoboticScheduler.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRoof.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRotation.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRunnable.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRunning.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IScriptRunner.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ISpectrograph.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IStartStop.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IStructuredConfig.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ISyncTarget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ITelescope.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ITemperatures.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ITrackingMode.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ITrackingRate.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IVideo.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IWeather.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IWindow.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/interface.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/camerasettings.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/datapipeline.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/datasequence.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/fitsheader.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/fitsnamespace.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/follow.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/motionstatus.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/pipeline.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/waitformotion.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/weatheraware.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/adaptive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/basecamera.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/basespectrograph.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/basevideo.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/dummycamera.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/dummyspectrograph.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/dummyvideo.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/pipelinecamera.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/videoframes.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/flatfield/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/flatfield/flatfield.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/flatfield/pointing.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/flatfield/scheduler.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/focus/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/focus/dummyautofocus.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/focus/focusmodel.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/focus/focusseries.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/imagewatcher.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/imagewriter.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/pipeline.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/seeing.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/module.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/_base.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/_baseguiding.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/acquisition.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/autoguiding.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/dummyacquisition.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/dummyguiding.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingsource.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/dummymastermind.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/dummyscheduler.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/pointing.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/scriptrunner.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/roof/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/roof/basedome.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/roof/baseroof.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/roof/dummyroof.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/test/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/test/standalone.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/dummymode.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/dummystructuredconfig.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/fluentlogger.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/httpfilecache.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/kiosk.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/matrix.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/pushnotifier.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/stellarium.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/telegram.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/trigger.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/mockweather.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/weather_api.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/weather_state.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/object.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/py.typed +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/instruments.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/_executor.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/constant.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/interval.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/merit.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/random.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/transit.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/target.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/cases.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/conditional.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/parallel.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/selector.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/sequential.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/script.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/utils/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/utils/log.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/_portal.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/configdb.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/task.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/memory/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/observationarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/portal/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/portal/observationarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/portal/taskarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/taskarchive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/task.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/taskrunner.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/archive/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/archive/archive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/archive/local_archive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/calibration.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/exptime/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/exptime/exptime.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/average.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/cache.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/config.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/config_schema.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/coordinates.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/curvefit.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/enums.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/exptime_grouping.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/fits.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/focusseries/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/focusseries/base.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/focusseries/photometry.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/focusseries/projection.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/filters.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/grid.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/gridnode.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/pipeline.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/binningwidget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/exposewidget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/http.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/influxdb.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/logger.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/logging/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/logging/context.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/logging/resolvableerror.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/modulegui.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/offsets/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/offsets/applyoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/parallel.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/pipeline.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/progress.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/reduction.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/reduction_base.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/csv.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/http.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/log.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/multi.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/publisher.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/serialization.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/shellcommand.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/stretch.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/threads/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/threads/lockwithabort.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/time.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/units.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/versions.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/version.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/archivefile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/bufferedfile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/file.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/filelists/__init__.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/filelists/filelist.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/filelists/testing.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/httpfile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/localfile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/memfile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/sftpfile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/smbfile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/sshfile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/tempfile.py +0 -0
- {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/vfs.py +0 -0
|
@@ -2,12 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import functools
|
|
5
|
-
import inspect
|
|
6
5
|
import logging
|
|
7
6
|
from collections.abc import Callable, Coroutine
|
|
8
7
|
from typing import TYPE_CHECKING, Any, overload
|
|
9
8
|
|
|
10
|
-
from pyobs.events import Event, LogEvent, ModuleClosedEvent
|
|
9
|
+
from pyobs.events import Event, LogEvent, ModuleClosedEvent, registered_events
|
|
11
10
|
from pyobs.interfaces import Interface
|
|
12
11
|
from pyobs.interfaces.interface import get_registered_interface
|
|
13
12
|
from pyobs.utils.enums import ModuleState
|
|
@@ -151,6 +150,10 @@ class Comm:
|
|
|
151
150
|
if interface.has_own_state():
|
|
152
151
|
await self.subscribe_state(client, interface, functools.partial(proxy.update_state, interface))
|
|
153
152
|
|
|
153
|
+
# a proxy without interfaces is useless and would stay stale (e.g. peer's disco#info not
|
|
154
|
+
# answered yet), so do not cache it
|
|
155
|
+
if len(interfaces) == 0:
|
|
156
|
+
return proxy
|
|
154
157
|
self._proxies[client] = proxy
|
|
155
158
|
|
|
156
159
|
return self._proxies[client]
|
|
@@ -416,17 +419,12 @@ class Comm:
|
|
|
416
419
|
Returns:
|
|
417
420
|
list of event classes.
|
|
418
421
|
"""
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
event_classes: list[type[Event]] = []
|
|
422
|
-
for cls in inspect.getmembers(pyobs.events, inspect.isclass):
|
|
423
|
-
if issubclass(cls[1], event):
|
|
424
|
-
event_classes.append(cls[1])
|
|
425
|
-
return event_classes
|
|
422
|
+
return [cls for cls in registered_events().values() if issubclass(cls, event)]
|
|
426
423
|
|
|
427
424
|
async def register_event(self, event_class: type[Event], handler: EventHandler | None = None) -> None:
|
|
428
425
|
"""Register an event type. If a handler is given, we also receive those events, otherwise we just
|
|
429
|
-
send them.
|
|
426
|
+
send them. Event classes derived from the given one are registered, too, but only those that have been
|
|
427
|
+
imported (and therefore registered) by now.
|
|
430
428
|
|
|
431
429
|
Args:
|
|
432
430
|
event_class: Class of event to register.
|
|
@@ -114,6 +114,22 @@ def fault_to_xml(exception: Exception) -> ET.Element:
|
|
|
114
114
|
msg_elem.text = getattr(exception, "message", None) or str(exception)
|
|
115
115
|
pyobs_fault.append(exc_elem)
|
|
116
116
|
pyobs_fault.append(msg_elem)
|
|
117
|
+
|
|
118
|
+
# PyobsError ancestors (nearest first), so a caller that never imported the exception's own module can
|
|
119
|
+
# still raise the nearest class it knows (e.g. MotionError). PyobsError itself and UnclassifiedError carry
|
|
120
|
+
# no information. If original_type was sent above, the exception itself is already an ancestor-resolved
|
|
121
|
+
# class (second hop), which the name no longer tells, so it goes first.
|
|
122
|
+
candidates = type(exception).__mro__ if original_type else type(exception).__mro__[1:]
|
|
123
|
+
bases = [
|
|
124
|
+
c
|
|
125
|
+
for c in candidates
|
|
126
|
+
if isinstance(c, type) and issubclass(c, exc.PyobsError) and c not in (exc.PyobsError, exc.UnclassifiedError)
|
|
127
|
+
]
|
|
128
|
+
bases_elem = ET.Element("bases")
|
|
129
|
+
for c in bases:
|
|
130
|
+
base_elem = ET.SubElement(bases_elem, "base")
|
|
131
|
+
base_elem.text = f"{c.__module__}.{c.__qualname__}"
|
|
132
|
+
pyobs_fault.append(bases_elem)
|
|
117
133
|
value_elem.append(pyobs_fault)
|
|
118
134
|
fault.append(value_elem)
|
|
119
135
|
return fault
|
|
@@ -135,6 +151,47 @@ def xml_to_fault(fault_elem: ET.Element) -> tuple[str, str]:
|
|
|
135
151
|
return exc_name, msg
|
|
136
152
|
|
|
137
153
|
|
|
154
|
+
def xml_to_fault_bases(fault_elem: ET.Element) -> list[str]:
|
|
155
|
+
"""Parse <fault> and return the qualified names of the exception's PyobsError ancestors, nearest first.
|
|
156
|
+
|
|
157
|
+
Empty if the fault has none, e.g. when it comes from a peer that does not send them.
|
|
158
|
+
"""
|
|
159
|
+
value_elem = fault_elem.find(f"{{{_NS}}}value")
|
|
160
|
+
if value_elem is None:
|
|
161
|
+
return []
|
|
162
|
+
pyobs_fault = value_elem.find(f"{{{_PYOBS_NS}}}fault")
|
|
163
|
+
if pyobs_fault is None:
|
|
164
|
+
return []
|
|
165
|
+
return [b.text for b in pyobs_fault.iterfind("bases/base") if b.text]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def reconstruct_fault(name: str, message: str, bases: list[str], remote_module: str) -> exc.PyobsError:
|
|
169
|
+
"""Build the exception for a received fault.
|
|
170
|
+
|
|
171
|
+
The exact class is used if it is registered here. If not (never a PyobsError, or its defining module was
|
|
172
|
+
never imported in this process), the nearest registered ancestor from `bases` is used, and failing that
|
|
173
|
+
UnclassifiedError. In both fallbacks the name survives as `original_type`.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
name: Qualified name of the exception, as sent.
|
|
177
|
+
message: Message of the exception.
|
|
178
|
+
bases: Qualified names of its PyobsError ancestors, nearest first.
|
|
179
|
+
remote_module: Name of the module the fault came from.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
The exception to raise.
|
|
183
|
+
"""
|
|
184
|
+
exception_class = exc.PyobsError.resolve(name)
|
|
185
|
+
if exception_class is not None:
|
|
186
|
+
# real registered type (e.g. FocusError) -- raised as itself, not wrapped
|
|
187
|
+
return exception_class(message, remote_module=remote_module)
|
|
188
|
+
for base in bases:
|
|
189
|
+
ancestor = exc.PyobsError.resolve(base)
|
|
190
|
+
if ancestor is not None and ancestor is not exc.UnclassifiedError:
|
|
191
|
+
return ancestor(message, original_type=name, remote_module=remote_module)
|
|
192
|
+
return exc.UnclassifiedError(message, original_type=name, remote_module=remote_module)
|
|
193
|
+
|
|
194
|
+
|
|
138
195
|
# ---------------------------------------------------------------------------
|
|
139
196
|
# RPC class
|
|
140
197
|
# ---------------------------------------------------------------------------
|
|
@@ -291,14 +348,7 @@ class RPC:
|
|
|
291
348
|
exc_name, msg = xml_to_fault(fault_elem)
|
|
292
349
|
|
|
293
350
|
sender = iq["from"].node
|
|
294
|
-
|
|
295
|
-
if exception_class is not None:
|
|
296
|
-
# real registered type (e.g. FocusError) -- raised as itself, not wrapped
|
|
297
|
-
exception: exc.PyobsError = exception_class(msg, remote_module=sender)
|
|
298
|
-
else:
|
|
299
|
-
# unresolvable: never a PyobsError to begin with, or its defining module was never
|
|
300
|
-
# imported in this process -- the qualified name string still survives as original_type
|
|
301
|
-
exception = exc.UnclassifiedError(msg, original_type=exc_name, remote_module=sender)
|
|
351
|
+
exception = reconstruct_fault(exc_name, msg, xml_to_fault_bases(fault_elem), sender)
|
|
302
352
|
|
|
303
353
|
# same correlation id Module.execute() logged on the origin side -- an operator can jump
|
|
304
354
|
# straight from this caller-side exception to the matching detailed log line, by id
|
|
@@ -223,6 +223,7 @@ class XmppComm(Comm):
|
|
|
223
223
|
self._interface_cache: dict[str, asyncio.Future[list[type[Interface]]]] = {}
|
|
224
224
|
self._interface_features: dict[str, list[str]] = {}
|
|
225
225
|
self._peer_sent_events: dict[str, set[tuple[str, int]]] = {}
|
|
226
|
+
self._discovery_retries: dict[str, asyncio.Task[None]] = {}
|
|
226
227
|
self._warned_version_mismatches: set[tuple[str, str]] = set()
|
|
227
228
|
self._user = user
|
|
228
229
|
self._password = password
|
|
@@ -436,8 +437,10 @@ class XmppComm(Comm):
|
|
|
436
437
|
self._capabilities = {} # clear capabilities on reconnect
|
|
437
438
|
|
|
438
439
|
# disconnect all clients
|
|
439
|
-
for jid in self._online_clients:
|
|
440
|
+
for jid in list(self._online_clients):
|
|
440
441
|
self._jid_got_offline(jid)
|
|
442
|
+
for jid in list(self._discovery_retries):
|
|
443
|
+
self._cancel_discovery_retry(jid)
|
|
441
444
|
|
|
442
445
|
if client.kicked_by_conflict:
|
|
443
446
|
# another session took over our JID/resource -- reconnecting would just
|
|
@@ -508,32 +511,35 @@ class XmppComm(Comm):
|
|
|
508
511
|
# return them from cache
|
|
509
512
|
return await self._interface_cache[client]
|
|
510
513
|
|
|
511
|
-
async def _get_interfaces(self, jid: str, attempts: int = 3) -> list[str]:
|
|
514
|
+
async def _get_interfaces(self, jid: str, attempts: int = 3) -> list[str] | None:
|
|
512
515
|
"""Return list of interfaces for the given JID.
|
|
513
516
|
|
|
514
517
|
Args:
|
|
515
518
|
jid: JID to get interfaces for.
|
|
516
519
|
|
|
517
520
|
Returns:
|
|
518
|
-
List of interface names
|
|
521
|
+
List of interface names, empty if the peer answered but does not implement IModule, or None
|
|
522
|
+
if disco#info itself failed (error, timeout, no or malformed answer).
|
|
519
523
|
"""
|
|
520
524
|
|
|
521
525
|
# request features
|
|
522
526
|
try:
|
|
523
527
|
info = await self._safe_send(self.client.plugin["xep_0030"].get_info, jid=jid, cached=False)
|
|
524
|
-
except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout):
|
|
525
|
-
|
|
528
|
+
except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout) as e:
|
|
529
|
+
log.warning("disco#info for %s failed (%s).", jid, _iq_failure_reason(e))
|
|
530
|
+
return None
|
|
526
531
|
|
|
527
532
|
# extract pyobs interface names
|
|
528
533
|
if info is None:
|
|
529
|
-
return
|
|
534
|
+
return None
|
|
530
535
|
try:
|
|
531
536
|
if isinstance(info, slixmpp.stanza.iq.Iq):
|
|
532
537
|
info = info["disco_info"]
|
|
533
538
|
prefix = "urn:pyobs:interface:"
|
|
534
539
|
features = [i for i in info["features"] if i.startswith(prefix)]
|
|
535
540
|
except TypeError:
|
|
536
|
-
|
|
541
|
+
log.warning("Malformed disco#info answer from %s.", jid)
|
|
542
|
+
return None
|
|
537
543
|
|
|
538
544
|
# cache raw features for this JID, so a later version-mismatch can be diagnosed
|
|
539
545
|
self._interface_features[jid] = features
|
|
@@ -563,7 +569,10 @@ class XmppComm(Comm):
|
|
|
563
569
|
return []
|
|
564
570
|
else:
|
|
565
571
|
await asyncio.sleep(5)
|
|
566
|
-
|
|
572
|
+
retried = await self._get_interfaces(jid, attempts - 1)
|
|
573
|
+
if retried is None:
|
|
574
|
+
return None
|
|
575
|
+
interface_names = retried
|
|
567
576
|
|
|
568
577
|
# finished
|
|
569
578
|
return interface_names
|
|
@@ -705,6 +714,55 @@ class XmppComm(Comm):
|
|
|
705
714
|
if jid == self._jid:
|
|
706
715
|
return
|
|
707
716
|
|
|
717
|
+
# try to get the peer online; if its disco#info failed, keep trying in the background, since
|
|
718
|
+
# slixmpp won't fire got_online again until the peer went offline and came back
|
|
719
|
+
if await self._process_online(msg):
|
|
720
|
+
self._cancel_discovery_retry(jid)
|
|
721
|
+
elif jid not in self._discovery_retries:
|
|
722
|
+
task = asyncio.create_task(self._retry_discovery(msg))
|
|
723
|
+
self._discovery_retries[jid] = task
|
|
724
|
+
task.add_done_callback(_log_task_exception)
|
|
725
|
+
|
|
726
|
+
def _cancel_discovery_retry(self, jid: str) -> None:
|
|
727
|
+
"""Cancel a pending background disco#info retry for the given JID, if any."""
|
|
728
|
+
task = self._discovery_retries.pop(jid, None)
|
|
729
|
+
if task is not None and task is not asyncio.current_task():
|
|
730
|
+
task.cancel()
|
|
731
|
+
|
|
732
|
+
async def _retry_discovery(self, msg: Any) -> None:
|
|
733
|
+
"""Repeat the disco#info of a peer that failed it, until it works or the peer is gone.
|
|
734
|
+
|
|
735
|
+
Args:
|
|
736
|
+
msg: Presence message of the peer.
|
|
737
|
+
"""
|
|
738
|
+
jid = msg["from"].full
|
|
739
|
+
try:
|
|
740
|
+
attempt = 0
|
|
741
|
+
while jid not in self._online_clients:
|
|
742
|
+
attempt += 1
|
|
743
|
+
if attempt == 30:
|
|
744
|
+
log.warning(
|
|
745
|
+
"Still failing to get disco#info for %s after %d attempts, will keep retrying", jid, attempt
|
|
746
|
+
)
|
|
747
|
+
await asyncio.sleep(_retry_delay(attempt))
|
|
748
|
+
if await self._process_online(msg):
|
|
749
|
+
log.info("Got disco#info for %s after %d retries.", jid, attempt)
|
|
750
|
+
return
|
|
751
|
+
finally:
|
|
752
|
+
if self._discovery_retries.get(jid) is asyncio.current_task():
|
|
753
|
+
del self._discovery_retries[jid]
|
|
754
|
+
|
|
755
|
+
async def _process_online(self, msg: Any) -> bool:
|
|
756
|
+
"""Get interfaces of a peer that came online and register it.
|
|
757
|
+
|
|
758
|
+
Args:
|
|
759
|
+
msg: XMPP presence message.
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
False if disco#info failed and it should be tried again, True otherwise.
|
|
763
|
+
"""
|
|
764
|
+
jid = msg["from"].full
|
|
765
|
+
|
|
708
766
|
# clear interface cache, just in case there is something there
|
|
709
767
|
if jid in self._interface_cache:
|
|
710
768
|
del self._interface_cache[jid]
|
|
@@ -715,6 +773,16 @@ class XmppComm(Comm):
|
|
|
715
773
|
# request interfaces
|
|
716
774
|
interface_names = await self._get_interfaces(jid)
|
|
717
775
|
|
|
776
|
+
# disco#info failed: fail waiting get_interfaces() calls instead of reporting "no interfaces"
|
|
777
|
+
if interface_names is None:
|
|
778
|
+
if jid not in self._discovery_retries:
|
|
779
|
+
log.warning("disco#info for %s failed, retrying in background.", jid)
|
|
780
|
+
future = self._interface_cache.pop(jid, None)
|
|
781
|
+
if future is not None and not future.done():
|
|
782
|
+
future.set_exception(IndexError(f"disco#info for {jid} failed."))
|
|
783
|
+
future.exception() # mark as retrieved, there may be no waiter
|
|
784
|
+
return False
|
|
785
|
+
|
|
718
786
|
# if no interfaces are implemented (not even IModule), quit here
|
|
719
787
|
if len(interface_names) == 0:
|
|
720
788
|
module = jid[: jid.index("@")]
|
|
@@ -724,7 +792,7 @@ class XmppComm(Comm):
|
|
|
724
792
|
future = self._interface_cache.get(jid)
|
|
725
793
|
if future is not None and not future.done():
|
|
726
794
|
future.set_result([])
|
|
727
|
-
return
|
|
795
|
+
return True
|
|
728
796
|
|
|
729
797
|
# store interfaces — guard against a second _got_online for the same JID
|
|
730
798
|
# (ejabberd may send multiple presence stanzas) racing against the first
|
|
@@ -775,6 +843,7 @@ class XmppComm(Comm):
|
|
|
775
843
|
|
|
776
844
|
# send event
|
|
777
845
|
self._send_event_to_module(ModuleOpenedEvent(), msg["from"].username)
|
|
846
|
+
return True
|
|
778
847
|
|
|
779
848
|
def _fire_presence_callbacks(self, module_name: str, state: ModuleState, status: str) -> None:
|
|
780
849
|
"""Call every presence callback registered for a module, isolating failures.
|
|
@@ -845,6 +914,8 @@ class XmppComm(Comm):
|
|
|
845
914
|
if jid in self._interface_cache:
|
|
846
915
|
del self._interface_cache[jid]
|
|
847
916
|
self._interface_features.pop(jid, None)
|
|
917
|
+
self._peer_sent_events.pop(jid, None)
|
|
918
|
+
self._cancel_discovery_retry(jid)
|
|
848
919
|
|
|
849
920
|
# send event
|
|
850
921
|
self._send_event_to_module(ModuleClosedEvent(), module_name)
|
|
@@ -1106,9 +1177,11 @@ class XmppComm(Comm):
|
|
|
1106
1177
|
event = EventFactory.from_dict(body)
|
|
1107
1178
|
if event is None:
|
|
1108
1179
|
return
|
|
1109
|
-
|
|
1180
|
+
age = time.time() - event.timestamp
|
|
1181
|
+
if age > 30:
|
|
1110
1182
|
# event is more than 30 seconds old, ignore it
|
|
1111
1183
|
# we do this do avoid resent events after a reconnect
|
|
1184
|
+
log.info("Dropping %s from %s, %.1f s old.", type(event).__name__, from_module, age)
|
|
1112
1185
|
return
|
|
1113
1186
|
|
|
1114
1187
|
# send it to module
|
|
@@ -5,7 +5,7 @@ TODO: write doc
|
|
|
5
5
|
__title__ = "Events"
|
|
6
6
|
|
|
7
7
|
from .badweather import BadWeatherEvent
|
|
8
|
-
from .event import Event
|
|
8
|
+
from .event import Event, EventFactory, get_registered_event, registered_events
|
|
9
9
|
from .exposurestatuschanged import ExposureStatusChangedEvent
|
|
10
10
|
from .filterchanged import FilterChangedEvent
|
|
11
11
|
from .focusfound import FocusFoundEvent
|
|
@@ -31,6 +31,9 @@ __all__ = [
|
|
|
31
31
|
"BadWeatherEvent",
|
|
32
32
|
"ExposureStatusChangedEvent",
|
|
33
33
|
"Event",
|
|
34
|
+
"EventFactory",
|
|
35
|
+
"get_registered_event",
|
|
36
|
+
"registered_events",
|
|
34
37
|
"FilterChangedEvent",
|
|
35
38
|
"FocusFoundEvent",
|
|
36
39
|
"GoodWeatherEvent",
|
|
@@ -8,14 +8,34 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
log = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
|
+
_REGISTRY: dict[str, type[Event]] = {}
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
class Event:
|
|
13
|
-
"""Base class for all events.
|
|
15
|
+
"""Base class for all events.
|
|
16
|
+
|
|
17
|
+
Every subclass is registered by its class name (the wire type and the pubsub node id only carry the
|
|
18
|
+
name), no matter where it is defined, so that plugins can define their own events. Two different classes
|
|
19
|
+
with the same name are rejected.
|
|
20
|
+
"""
|
|
14
21
|
|
|
15
22
|
__module__ = "pyobs.events"
|
|
16
23
|
local: bool = False
|
|
17
24
|
version: int = 1
|
|
18
25
|
|
|
26
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
27
|
+
super().__init_subclass__(**kwargs)
|
|
28
|
+
|
|
29
|
+
existing = _REGISTRY.get(cls.__name__)
|
|
30
|
+
if existing is not None and (existing.__module__, existing.__qualname__) != (cls.__module__, cls.__qualname__):
|
|
31
|
+
raise TypeError(
|
|
32
|
+
f"Event name '{cls.__name__}' is already registered by "
|
|
33
|
+
f"{existing.__module__}.{existing.__qualname__}; "
|
|
34
|
+
f"choose a distinct name for {cls.__module__}.{cls.__qualname__}."
|
|
35
|
+
)
|
|
36
|
+
# same module and name: a reload or a redefinition, replace the old class
|
|
37
|
+
_REGISTRY[cls.__name__] = cls
|
|
38
|
+
|
|
19
39
|
def __init__(self, **kwargs: Any):
|
|
20
40
|
self.uuid = str(uuid.uuid4())
|
|
21
41
|
self.timestamp = time.time()
|
|
@@ -35,9 +55,20 @@ class Event:
|
|
|
35
55
|
return cls(**d)
|
|
36
56
|
|
|
37
57
|
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
_REGISTRY["Event"] = Event
|
|
59
|
+
|
|
40
60
|
|
|
61
|
+
def get_registered_event(name: str) -> type[Event] | None:
|
|
62
|
+
"""Return the event class registered under the given class name, or None."""
|
|
63
|
+
return _REGISTRY.get(name)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def registered_events() -> dict[str, type[Event]]:
|
|
67
|
+
"""Return a copy of all registered event classes by name, in registration order."""
|
|
68
|
+
return dict(_REGISTRY)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class EventFactory:
|
|
41
72
|
@staticmethod
|
|
42
73
|
def from_dict(obj_dict: dict[str, Any]) -> Event | None:
|
|
43
74
|
"""Create Event from a dictionary.
|
|
@@ -49,23 +80,8 @@ class EventFactory:
|
|
|
49
80
|
Event object containing event.
|
|
50
81
|
"""
|
|
51
82
|
|
|
52
|
-
#
|
|
53
|
-
cls
|
|
54
|
-
for p in EventFactory.packages:
|
|
55
|
-
if p is None:
|
|
56
|
-
continue
|
|
57
|
-
# import package
|
|
58
|
-
parts = p.split(".")
|
|
59
|
-
pkg = __import__(parts[0])
|
|
60
|
-
for comp in parts[1:]:
|
|
61
|
-
pkg = getattr(pkg, comp)
|
|
62
|
-
|
|
63
|
-
# does it have the given type as class?
|
|
64
|
-
if hasattr(pkg, obj_dict["type"]):
|
|
65
|
-
cls = getattr(pkg, obj_dict["type"])
|
|
66
|
-
break
|
|
67
|
-
|
|
68
|
-
# not found?
|
|
83
|
+
# find class, it has to be imported (and therefore registered) in this process
|
|
84
|
+
cls = get_registered_event(obj_dict["type"])
|
|
69
85
|
if cls is None:
|
|
70
86
|
return None
|
|
71
87
|
|
|
@@ -84,4 +100,4 @@ class EventFactory:
|
|
|
84
100
|
return None
|
|
85
101
|
|
|
86
102
|
|
|
87
|
-
__all__ = ["Event", "EventFactory"]
|
|
103
|
+
__all__ = ["Event", "EventFactory", "get_registered_event", "registered_events"]
|
|
@@ -35,7 +35,8 @@ class IPointingAltAz(Interface, metaclass=ABCMeta):
|
|
|
35
35
|
|
|
36
36
|
Raises:
|
|
37
37
|
NotSupportedError: If this device doesn't support Alt/Az pointing.
|
|
38
|
-
|
|
38
|
+
PointingLimitError: If the destination is outside the pointing limits, e.g. below the configured
|
|
39
|
+
altitude limit (AltitudeLimitError).
|
|
39
40
|
MoveError: If device could not be moved.
|
|
40
41
|
"""
|
|
41
42
|
...
|
|
@@ -36,7 +36,8 @@ class IPointingRaDec(Interface, metaclass=ABCMeta):
|
|
|
36
36
|
Raises:
|
|
37
37
|
NotSupportedError: If this device doesn't support RA/Dec pointing.
|
|
38
38
|
MissingObserverError: If no observer is configured.
|
|
39
|
-
|
|
39
|
+
PointingLimitError: If the destination is outside the pointing limits, e.g. below the configured
|
|
40
|
+
altitude limit (AltitudeLimitError).
|
|
40
41
|
MoveError: If device could not be moved.
|
|
41
42
|
"""
|
|
42
43
|
...
|
|
@@ -63,6 +63,8 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
|
|
|
63
63
|
self._after_task_sleep = after_task_sleep
|
|
64
64
|
self._cant_run_reason: str | None = None
|
|
65
65
|
self._next_observation: Observation | None = None
|
|
66
|
+
self._project_priority: float | None = None
|
|
67
|
+
self._observation: Observation | None = None
|
|
66
68
|
|
|
67
69
|
# add thread func
|
|
68
70
|
self.add_background_task(self._run_thread, True)
|
|
@@ -258,7 +260,9 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
|
|
|
258
260
|
|
|
259
261
|
# task is definitely not None here
|
|
260
262
|
self._task = observation.task
|
|
263
|
+
self._observation = observation
|
|
261
264
|
self._task_target = observation.target
|
|
265
|
+
self._project_priority = await self._lookup_project_priority(observation.task)
|
|
262
266
|
self._obsnum = await self._next_obsnum()
|
|
263
267
|
|
|
264
268
|
# ETA
|
|
@@ -296,6 +300,8 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
|
|
|
296
300
|
await self._observation_archive.update_observation(observation)
|
|
297
301
|
await self.comm.send_event(TaskFailedEvent(name=self._task.name, id=self._task.id, obsnum=self._obsnum))
|
|
298
302
|
self._task = None
|
|
303
|
+
self._project_priority = None
|
|
304
|
+
self._observation = None
|
|
299
305
|
self._task_target = None
|
|
300
306
|
self._obsnum = None
|
|
301
307
|
self._task_start = None
|
|
@@ -312,6 +318,8 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
|
|
|
312
318
|
# finish
|
|
313
319
|
log.info("Finished task %s.", self._task.name)
|
|
314
320
|
self._task = None
|
|
321
|
+
self._project_priority = None
|
|
322
|
+
self._observation = None
|
|
315
323
|
self._task_target = None
|
|
316
324
|
self._obsnum = None
|
|
317
325
|
self._task_start = None
|
|
@@ -321,6 +329,18 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
|
|
|
321
329
|
# sleep?
|
|
322
330
|
await asyncio.sleep(self._after_task_sleep)
|
|
323
331
|
|
|
332
|
+
async def _lookup_project_priority(self, task: Task) -> float | None:
|
|
333
|
+
"""Priority of the project of the given task, or None if it cannot be determined (no task archive, an
|
|
334
|
+
archive without projects, unknown project, project without priority). Looked up once when a task starts,
|
|
335
|
+
so that all frames of a run carry the same value."""
|
|
336
|
+
if self._task_archive is None or not task.project:
|
|
337
|
+
return None
|
|
338
|
+
try:
|
|
339
|
+
projects = await self._task_archive.get_projects()
|
|
340
|
+
except NotImplementedError:
|
|
341
|
+
return None
|
|
342
|
+
return next((p.priority for p in projects if p.code == task.project), None)
|
|
343
|
+
|
|
324
344
|
async def get_fits_header_before(
|
|
325
345
|
self, namespaces: list[str] | None = None, **kwargs: Any
|
|
326
346
|
) -> dict[str, FitsHeaderEntry]:
|
|
@@ -342,6 +362,24 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
|
|
|
342
362
|
hdr["PROJECT"] = FitsHeaderEntry(self._task.project, "Project code")
|
|
343
363
|
if self._obsnum is not None:
|
|
344
364
|
hdr["OBSNUM"] = FitsHeaderEntry(self._obsnum, "Observation number (night-obsnum)")
|
|
365
|
+
|
|
366
|
+
# what the frame was asked for: priorities and constraints of the task as it was when the run started
|
|
367
|
+
if self._task.priority is not None:
|
|
368
|
+
hdr["HIERARCH REQ PRIORITY"] = FitsHeaderEntry(float(self._task.priority), "")
|
|
369
|
+
if self._project_priority is not None:
|
|
370
|
+
hdr["HIERARCH REQ PROJECT PRIORITY"] = FitsHeaderEntry(float(self._project_priority), "")
|
|
371
|
+
if self._observation is not None and self._observation.priority is not None:
|
|
372
|
+
# the value the scheduler ranked this slot by when it planned it (merit times task priority times
|
|
373
|
+
# project priority), only set by schedulers that fill Observation.priority
|
|
374
|
+
hdr["HIERARCH REQ MERIT"] = FitsHeaderEntry(float(self._observation.priority), "plan-time rank")
|
|
375
|
+
counts: dict[type, int] = {}
|
|
376
|
+
for constraint in self._task.constraints:
|
|
377
|
+
counts[type(constraint)] = counts.get(type(constraint), 0) + 1
|
|
378
|
+
for key, entry in constraint.get_fits_headers(counts[type(constraint)]).items():
|
|
379
|
+
if key in hdr:
|
|
380
|
+
log.debug("Header %s already set, ignoring the one of %s.", key, type(constraint).__name__)
|
|
381
|
+
continue
|
|
382
|
+
hdr[key] = entry
|
|
345
383
|
hdr.update(version_fits_headers(self.name))
|
|
346
384
|
return hdr
|
|
347
385
|
else:
|
|
@@ -230,7 +230,7 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
230
230
|
log.info("Removed: %d, added: %d, changed: %d", len(removed), len(added), len(changed))
|
|
231
231
|
if len(removed) == 1:
|
|
232
232
|
log.info(
|
|
233
|
-
"Found 1 removed block with ID %
|
|
233
|
+
"Found 1 removed block with ID %s. Last task ID was %s, current is %s.",
|
|
234
234
|
removed[0],
|
|
235
235
|
str(self._last_task_id),
|
|
236
236
|
str(self._current_task_id),
|
|
@@ -79,9 +79,10 @@ class MissingObserverError(exc.MotionError):
|
|
|
79
79
|
pass
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
class AltitudeLimitError(exc.
|
|
82
|
+
class AltitudeLimitError(exc.PointingLimitError):
|
|
83
83
|
"""Destination altitude is below the configured limit -- an expected, deferrable condition
|
|
84
|
-
from a scheduler's point of view, not a failure to alert on.
|
|
84
|
+
from a scheduler's point of view, not a failure to alert on. Derives from PointingLimitError, so a caller
|
|
85
|
+
that never imported this module still catches it as that. Deliberately not a MotionError:
|
|
85
86
|
it's not a motion fault (nothing was attempted), so it must not count towards the module's
|
|
86
87
|
repeated-motion-failure escalation (see _register_exception(exc.MotionError, ...) below) --
|
|
87
88
|
picking a target below the horizon is normal, not a sign the telescope is broken."""
|
|
@@ -20,7 +20,7 @@ from pyobs.utils.versions import version_fits_headers
|
|
|
20
20
|
log = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class WeatherResponseError(exc.
|
|
23
|
+
class WeatherResponseError(exc.GeneralError):
|
|
24
24
|
"""The weather station's API response was malformed or incomplete (missing an expected field)
|
|
25
25
|
-- plausibly transient (a flaky station/network), worth retrying, as opposed to a caller
|
|
26
26
|
passing a bad station/sensor name."""
|
|
@@ -45,20 +45,27 @@ class Observation(BaseModel):
|
|
|
45
45
|
consumes it -- it is always None for the pending/in_progress states Mastermind schedules
|
|
46
46
|
on, and is declared so the portal's payloads keep validating under ``extra="forbid"``."""
|
|
47
47
|
|
|
48
|
+
@property
|
|
49
|
+
def task_id(self) -> Any:
|
|
50
|
+
"""Id of the task: ``task.id`` for a full ``Task``, else ``task`` itself (the portal serializes
|
|
51
|
+
it as a plain id)."""
|
|
52
|
+
return self.task.id if isinstance(self.task, Task) else self.task
|
|
53
|
+
|
|
48
54
|
def __str__(self) -> str:
|
|
49
|
-
|
|
50
|
-
f"
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
if isinstance(self.task, Task):
|
|
56
|
+
task = f"{self.task.name} (#{self.task.id})"
|
|
57
|
+
else:
|
|
58
|
+
task = f"#{self.task}"
|
|
59
|
+
return f"Observation {self.id} of {task} from {self.start} to {self.end} [{self.state}]"
|
|
53
60
|
|
|
54
61
|
def __eq__(self, other: object) -> bool:
|
|
55
62
|
if isinstance(other, Observation):
|
|
56
|
-
return bool(self.
|
|
63
|
+
return bool(self.task_id == other.task_id and self.start == other.start and self.end == other.end)
|
|
57
64
|
return super().__eq__(other)
|
|
58
65
|
|
|
59
66
|
def __ne__(self, other: object) -> bool:
|
|
60
67
|
if isinstance(other, Observation):
|
|
61
|
-
return bool(self.
|
|
68
|
+
return bool(self.task_id != other.task_id or self.start != other.start or self.end != other.end)
|
|
62
69
|
return super().__ne__(other)
|
|
63
70
|
|
|
64
71
|
def __lt__(self, other: object) -> bool:
|
|
@@ -107,7 +114,7 @@ class ObservationList(UserList[Observation]): # noqa: F821
|
|
|
107
114
|
def filter(
|
|
108
115
|
self,
|
|
109
116
|
state: ObservationState | None = None,
|
|
110
|
-
task_id:
|
|
117
|
+
task_id: Any | None = None,
|
|
111
118
|
start_before: Time | None = None,
|
|
112
119
|
start_after: Time | None = None,
|
|
113
120
|
end_before: Time | None = None,
|
|
@@ -117,7 +124,7 @@ class ObservationList(UserList[Observation]): # noqa: F821
|
|
|
117
124
|
if state is not None:
|
|
118
125
|
new_list = [obs for obs in new_list if obs.state == state]
|
|
119
126
|
if task_id is not None:
|
|
120
|
-
new_list = [obs for obs in new_list if obs.
|
|
127
|
+
new_list = [obs for obs in new_list if obs.task_id == task_id]
|
|
121
128
|
if start_before is not None:
|
|
122
129
|
new_list = [obs for obs in new_list if obs.start <= start_before]
|
|
123
130
|
if start_after is not None:
|
{pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/airmassconstraint.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
4
4
|
|
|
5
5
|
import astroplan
|
|
6
6
|
import numpy as np
|
|
@@ -22,6 +22,10 @@ class AirmassConstraint(Constraint):
|
|
|
22
22
|
cost: float = 2.0
|
|
23
23
|
target_dependent: bool = True
|
|
24
24
|
max_airmass: float = Field(ge=1.0, le=9.9, default=1.3)
|
|
25
|
+
req_token: ClassVar[str | None] = "AIRMASS"
|
|
26
|
+
|
|
27
|
+
def req_fields(self) -> dict[str, tuple[int | float | str, str]]:
|
|
28
|
+
return {"MAX": (float(self.max_airmass), "")}
|
|
25
29
|
|
|
26
30
|
def to_astroplan(self) -> astroplan.AirmassConstraint:
|
|
27
31
|
return astroplan.AirmassConstraint(max=self.max_airmass)
|