pyobs-brot 0.0.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.
@@ -0,0 +1,165 @@
1
+ # https://github.com/github/gitignore/blob/main/Python.gitignore
2
+
3
+ *.yaml
4
+ *.yml
5
+
6
+ #Byte-compiled / optimized / DLL files
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+
11
+ # C extensions
12
+ *.so
13
+
14
+ # Distribution / packaging
15
+ .Python
16
+ build/
17
+ develop-eggs/
18
+ dist/
19
+ downloads/
20
+ eggs/
21
+ .eggs/
22
+ lib/
23
+ lib64/
24
+ parts/
25
+ sdist/
26
+ var/
27
+ wheels/
28
+ share/python-wheels/
29
+ *.egg-info/
30
+ .installed.cfg
31
+ *.egg
32
+ MANIFEST
33
+
34
+ # PyInstaller
35
+ # Usually these files are written by a python script from a template
36
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
37
+ *.manifest
38
+ *.spec
39
+
40
+ # Installer logs
41
+ pip-log.txt
42
+ pip-delete-this-directory.txt
43
+
44
+ # Unit test / coverage reports
45
+ htmlcov/
46
+ .tox/
47
+ .nox/
48
+ .coverage
49
+ .coverage.*
50
+ .cache
51
+ nosetests.xml
52
+ coverage.xml
53
+ *.cover
54
+ *.py,cover
55
+ .hypothesis/
56
+ .pytest_cache/
57
+ cover/
58
+
59
+ # Translations
60
+ *.mo
61
+ *.pot
62
+
63
+ # Django stuff:
64
+ *.log
65
+ local_settings.py
66
+ db.sqlite3
67
+ db.sqlite3-journal
68
+
69
+ # Flask stuff:
70
+ instance/
71
+ .webassets-cache
72
+
73
+ # Scrapy stuff:
74
+ .scrapy
75
+
76
+ # Sphinx documentation
77
+ docs/_build/
78
+
79
+ # PyBuilder
80
+ .pybuilder/
81
+ target/
82
+
83
+ # Jupyter Notebook
84
+ .ipynb_checkpoints
85
+
86
+ # IPython
87
+ profile_default/
88
+ ipython_config.py
89
+
90
+ # pyenv
91
+ # For a library or package, you might want to ignore these files since the code is
92
+ # intended to run in multiple environments; otherwise, check them in:
93
+ # .python-version
94
+
95
+ # pipenv
96
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
97
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
98
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
99
+ # install all needed dependencies.
100
+ #Pipfile.lock
101
+
102
+ # poetry
103
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
104
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
105
+ # commonly ignored for libraries.
106
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
107
+ #poetry.lock
108
+
109
+ # pdm
110
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
111
+ #pdm.lock
112
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
113
+ # in version control.
114
+ # https://pdm.fming.dev/#use-with-ide
115
+ .pdm.toml
116
+
117
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
118
+ __pypackages__/
119
+
120
+ # Celery stuff
121
+ celerybeat-schedule
122
+ celerybeat.pid
123
+
124
+ # SageMath parsed files
125
+ *.sage.py
126
+
127
+ # Environments
128
+ .env
129
+ .venv
130
+ env/
131
+ venv/
132
+ ENV/
133
+ env.bak/
134
+ venv.bak/
135
+
136
+ # Spyder project settings
137
+ .spyderproject
138
+ .spyproject
139
+
140
+ # Rope project settings
141
+ .ropeproject
142
+
143
+ # mkdocs documentation
144
+ /site
145
+
146
+ # mypy
147
+ .mypy_cache/
148
+ .dmypy.json
149
+ dmypy.json
150
+
151
+ # Pyre type checker
152
+ .pyre/
153
+
154
+ # pytype static type analyzer
155
+ .pytype/
156
+
157
+ # Cython debug symbols
158
+ cython_debug/
159
+
160
+ # PyCharm
161
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
162
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
163
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
164
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165
+ .idea/
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Tim-Oliver Husser
4
+ thusser@uni-goettingen.de
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyobs-brot
3
+ Version: 0.0.1
4
+ Summary: pyobs module for BROTlib telescopes
5
+ Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>, Lukas Melzig <lukas.melzig@stud.uni-goettingen.de>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: astropy<8,>=7.0.1
10
+ Requires-Dist: paho-mqtt>=2.1.0
11
+ Requires-Dist: pybrotlib>=0.1.3
12
+ Requires-Dist: pyobs-core<2,>=1
@@ -0,0 +1,3 @@
1
+ BROTlib module for *pyobs*
2
+ ==========================
3
+
@@ -0,0 +1,2 @@
1
+ from .brottelescope import BrotBaseTelescope, BrotRaDecTelescope
2
+ from .brotdome import BrotDome
@@ -0,0 +1,194 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import Any
4
+
5
+ import async_timer # type: ignore
6
+ import qasync # type: ignore
7
+
8
+ from pyobs.events import RoofOpenedEvent, RoofClosingEvent
9
+ from pyobs.interfaces import IDome, IRoof, IMotion
10
+ from pyobs.modules.roof.basedome import BaseDome
11
+ from pyobs.modules import timeout
12
+ from pyobs.utils.enums import MotionStatus
13
+
14
+ from pybrotlib import BROT
15
+ from pybrotlib.mqtttransport import MQTTTransport
16
+ from pybrotlib.dome import DomeStatus, DomeShutterStatus
17
+
18
+ log = logging.getLogger(__name__)
19
+
20
+
21
+ class BrotDome(BaseDome, IDome, IRoof, IMotion):
22
+
23
+ def __init__(
24
+ self,
25
+ host: str,
26
+ name: str,
27
+ port: int = 1883,
28
+ keepalive: int = 60,
29
+ **kwargs: Any,
30
+ ):
31
+ BaseDome.__init__(self, **kwargs)
32
+ self.mqtt = MQTTTransport(host, port)
33
+ self.brot = BROT(self.mqtt, name)
34
+ # add thread for pulling the status constantly
35
+ self.add_background_task(self._update_status)
36
+
37
+ async def open(self) -> None:
38
+ await BaseDome.open(self)
39
+ asyncio.create_task(self.mqtt.run())
40
+ await asyncio.sleep(2)
41
+ await self.comm.register_event(RoofOpenedEvent)
42
+ await self.comm.register_event(RoofClosingEvent)
43
+ # check whats up
44
+ if self.brot.dome.status == DomeStatus.ERROR:
45
+ await self._error_state()
46
+ elif self.brot.dome.in_motion:
47
+ await self._change_motion_status(MotionStatus.SLEWING)
48
+ log.info("Dome is already in motion. Please make sure it is not used by another instance!")
49
+ elif self.brot.dome.status == DomeStatus.PARKED and self.brot.dome.shutter == DomeShutterStatus.CLOSED:
50
+ await self._change_motion_status(MotionStatus.PARKED)
51
+ log.info("Dome is closed and parked.")
52
+ else:
53
+ await self._change_motion_status(MotionStatus.POSITIONED)
54
+ log.info("Dome is already online. Please make sure it is not used by another instance!")
55
+
56
+ async def get_altaz(self, **kwargs: Any) -> tuple[float, float]:
57
+ """Returns current Alt and Az.
58
+
59
+ Returns:
60
+ Tuple of current Alt and Az in degrees.
61
+ """
62
+ return 0.0, self.brot.dome.azimuth
63
+
64
+ async def _update_status(self) -> None:
65
+ while True:
66
+ try:
67
+ current_state = await self.get_motion_status()
68
+ new_state = current_state
69
+ # first two can only be updated by the init/park method
70
+ if current_state == MotionStatus.INITIALIZING:
71
+ pass
72
+ elif current_state == MotionStatus.PARKING:
73
+ pass
74
+ elif self.brot.dome.status == DomeStatus.ERROR:
75
+ new_state = MotionStatus.ERROR
76
+ elif self.brot.dome.status == DomeStatus.PARKED and self.brot.dome.shutter == DomeShutterStatus.CLOSED:
77
+ new_state = MotionStatus.PARKED
78
+ elif self.brot.dome.in_motion:
79
+ new_state = MotionStatus.SLEWING
80
+ else:
81
+ new_state = MotionStatus.POSITIONED
82
+ if new_state != current_state:
83
+ await self._change_motion_status(new_state)
84
+ except:
85
+ pass
86
+ await asyncio.sleep(1)
87
+
88
+ @timeout(300)
89
+ async def init(self, **kwargs: Any) -> None:
90
+ log.info("Opening roof")
91
+ if self.brot.dome.shutter == DomeShutterStatus.OPEN:
92
+ return
93
+ elif self.brot.dome.status == DomeStatus.ERROR:
94
+ await self._error_state("Dome is in error state. Cannot open.")
95
+ return
96
+
97
+ await self._change_motion_status(MotionStatus.INITIALIZING)
98
+
99
+ # send open command
100
+ await self.brot.dome.open()
101
+
102
+ while True:
103
+ match self.brot.dome.shutter:
104
+ case DomeShutterStatus.OPEN:
105
+ log.info("Dome is open.")
106
+ break
107
+ case _:
108
+ pass
109
+ await asyncio.sleep(1)
110
+ # send tracking command
111
+ await self.brot.dome.start_tracking()
112
+ while True:
113
+ match self.brot.dome.status:
114
+ case DomeStatus.TRACKING:
115
+ log.info("Dome is tracking the telescope azimuth.")
116
+ break
117
+ case DomeStatus.ERROR:
118
+ await self._error_state()
119
+ return
120
+ case _:
121
+ pass
122
+ await asyncio.sleep(1)
123
+ await self._change_motion_status(MotionStatus.POSITIONED)
124
+ await self.comm.send_event(RoofOpenedEvent())
125
+
126
+ @timeout(300)
127
+ async def park(self, **kwargs: Any) -> None:
128
+ if self.brot.dome.status == DomeStatus.PARKED and self.brot.some.shutter == DomeShutterStatus.CLOSED:
129
+ return
130
+ elif self.brot.dome.status == DomeStatus.ERROR:
131
+ await self._error_state("Dome is in error state. Cannot close/park.")
132
+ return
133
+
134
+ await self._change_motion_status(MotionStatus.PARKING)
135
+ await self.comm.send_event(RoofClosingEvent())
136
+ # stop tracking
137
+ await self.brot.dome.stop_tracking()
138
+ while True:
139
+ match self.brot.dome.status:
140
+ case DomeStatus.TRACKING:
141
+ pass
142
+ case DomeStatus.ERROR:
143
+ await self._error_state()
144
+ return
145
+ case _:
146
+ break
147
+ await asyncio.sleep(1)
148
+ # close shutter
149
+ await self.brot.dome.close()
150
+ while True:
151
+ match self.brot.dome.shutter:
152
+ case DomeShutterStatus.CLOSED:
153
+ log.info("Dome shutter is closed.")
154
+ break
155
+ case _:
156
+ pass
157
+ await asyncio.sleep(1)
158
+
159
+ # go to parking position
160
+ await self.brot.dome.park()
161
+ while True:
162
+ match self.brot.dome.status:
163
+ case DomeStatus.PARKED:
164
+ log.info("Dome is parked.")
165
+ break
166
+ case DomeStatus.ERROR:
167
+ await self._error_state()
168
+ return
169
+ case _:
170
+ pass
171
+ await asyncio.sleep(1)
172
+ await self._change_motion_status(MotionStatus.PARKED)
173
+
174
+ async def stop_motion(self, device: str | None = None, **kwargs: Any) -> None:
175
+ pass # no stopping of the roof possible
176
+
177
+ async def move_altaz(self, alt: float, az: float, **kwargs: Any) -> None:
178
+ """Moves to given coordinates.
179
+
180
+ Args:
181
+ alt: Alt in deg to move to.
182
+ az: Az in deg to move to.
183
+
184
+ Raises:
185
+ MoveError: If device could not be moved.
186
+ """
187
+ pass
188
+
189
+ async def _error_state(self, mess: str = "Dome is in error state.") -> None:
190
+ log.error(mess)
191
+ await self._change_motion_status(MotionStatus.ERROR)
192
+
193
+
194
+ __all__ = ["BrotDome"]