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