pyobs-brot 1.0.0__tar.gz → 2.0.0.dev2__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,26 @@
1
+ name: Publish package to PyPI
2
+ on:
3
+ push:
4
+ tags:
5
+ - 'v*'
6
+ jobs:
7
+ build-n-publish:
8
+ name: Build and publish package to PyPI
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Check out repository code
12
+ uses: actions/checkout@v4
13
+
14
+ - name: Set up Python
15
+ uses: astral-sh/setup-uv@v5
16
+ with:
17
+ enable-cache: true
18
+ python-version: "3.11"
19
+
20
+ - name: Build
21
+ run: uv build
22
+
23
+ - name: Publish
24
+ env:
25
+ PYPI_TOKEN: ${{ secrets.pypi_password }}
26
+ run: uv publish --token $PYPI_TOKEN
@@ -0,0 +1,23 @@
1
+ name: ruff
2
+ on: push
3
+
4
+ jobs:
5
+ ruff:
6
+ runs-on: ubuntu-latest
7
+ timeout-minutes: 10
8
+
9
+ steps:
10
+ - name: Check out repository code
11
+ uses: actions/checkout@v4
12
+
13
+ - name: Set up Python
14
+ uses: astral-sh/setup-uv@v5
15
+ with:
16
+ enable-cache: true
17
+ python-version: "3.13"
18
+
19
+ - name: Install packages
20
+ run: uv sync --all-extras --dev
21
+
22
+ - name: Run ruff
23
+ run: uv run ruff check pyobs_brot/
@@ -1,8 +1,5 @@
1
1
  # https://github.com/github/gitignore/blob/main/Python.gitignore
2
2
 
3
- *.yaml
4
- *.yml
5
-
6
3
  #Byte-compiled / optimized / DLL files
7
4
  __pycache__/
8
5
  *.py[cod]
@@ -0,0 +1,10 @@
1
+ repos:
2
+ - repo: https://github.com/psf/black-pre-commit-mirror
3
+ rev: 23.7.0
4
+ hooks:
5
+ - id: black
6
+ language_version: python3.11
7
+ - repo: https://github.com/astral-sh/ruff-pre-commit
8
+ rev: v0.9.0
9
+ hooks:
10
+ - id: ruff
@@ -0,0 +1,15 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.11"
7
+
8
+ sphinx:
9
+ configuration: docs/source/conf.py
10
+
11
+ python:
12
+ install:
13
+ - method: pip
14
+ path: .
15
+ - requirements: docs/requirements.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-brot
3
- Version: 1.0.0
3
+ Version: 2.0.0.dev2
4
4
  Summary: pyobs module for BROTlib telescopes
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>, Lukas Melzig <lukas.melzig@stud.uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -9,4 +9,4 @@ Requires-Python: >=3.11
9
9
  Requires-Dist: astropy<8,>=7.0.1
10
10
  Requires-Dist: paho-mqtt>=2.1.0
11
11
  Requires-Dist: pybrotlib>=1.0.0
12
- Requires-Dist: pyobs-core<2,>=1.42.0
12
+ Requires-Dist: pyobs-core<3,>=2.0.0.dev6
@@ -0,0 +1,60 @@
1
+ BROTlib module for *pyobs*
2
+ ==========================
3
+
4
+ This is a [pyobs](https://www.pyobs.org) module for telescopes, domes and roll-off roofs controlled via
5
+ [BROTlib](https://pypi.org/project/pybrotlib/) over MQTT.
6
+
7
+
8
+ Install *pyobs-brot*
9
+ ---------------------
10
+ Clone the repository:
11
+
12
+ git clone https://github.com/pyobs/pyobs-brot.git
13
+ cd pyobs-brot
14
+
15
+ Install it with [uv](https://docs.astral.sh/uv/):
16
+
17
+ uv sync
18
+
19
+ Alternatively, with plain `venv`/`pip`:
20
+
21
+ python3 -m venv .venv
22
+ source .venv/bin/activate
23
+ pip install .
24
+
25
+
26
+ Configuration
27
+ -------------
28
+ *BrotRaDecTelescope* (or its base class *BrotBaseTelescope*), *BrotDome* and *BrotRoof* all connect to the same
29
+ MQTT broker used by BROTlib:
30
+
31
+ host:
32
+ Hostname of the MQTT broker.
33
+ name:
34
+ Name of the BROT device.
35
+ port:
36
+ MQTT port (default: 1883).
37
+
38
+ A basic telescope configuration would look like this:
39
+
40
+ class: pyobs_brot.BrotRaDecTelescope
41
+ name: My telescope
42
+ host: 1.2.3.4
43
+ roof: roof
44
+ dome: dome
45
+
46
+ # communication
47
+ comm:
48
+ jid: test@example.com
49
+ password: ***
50
+
51
+ *BrotDome* and *BrotRoof* follow the same pattern, just with `class: pyobs_brot.BrotDome` respectively
52
+ `class: pyobs_brot.BrotRoof`.
53
+
54
+
55
+ Dependencies
56
+ ------------
57
+ * [pyobs-core](https://github.com/pyobs/pyobs-core) for the core functionality.
58
+ * [pybrotlib](https://pypi.org/project/pybrotlib/) for communicating with BROT devices.
59
+ * [paho-mqtt](https://pypi.org/project/paho-mqtt/) as the MQTT client used by BROTlib.
60
+ * [Astropy](http://www.astropy.org/) for FITS file handling.
@@ -0,0 +1,31 @@
1
+ # Minimal makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line.
5
+ SPHINXOPTS =
6
+ SPHINXBUILD = sphinx-build
7
+ SPHINXPROJ = pyobs
8
+ SOURCEDIR = source
9
+ BUILDDIR = ../.doc-build
10
+
11
+ #.EXPORT_ALL_VARIABLES:
12
+
13
+ #PYTHONPATH:=../pyobs-core/:../pyobs-sbig/:../pyobs-fli:../pyobs-weather:../pyobs-archive:${PYTHONPATH}
14
+
15
+ # Put it first so that "make" without argument is like "make help".
16
+ help:
17
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
18
+
19
+ .PHONY: help Makefile
20
+
21
+ html:
22
+ @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
23
+
24
+ livehtml:
25
+ sphinx-autobuild -b html --watch ../pyobs_brot $(ALLSPHINXOPTS) "$(SOURCEDIR)/" "$(BUILDDIR)/html/"
26
+
27
+ # Catch-all target: route all unknown targets to Sphinx using the new
28
+ # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
29
+ %: Makefile
30
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
31
+
@@ -0,0 +1,36 @@
1
+ @ECHO OFF
2
+
3
+ pushd %~dp0
4
+
5
+ REM Command file for Sphinx documentation
6
+
7
+ if "%SPHINXBUILD%" == "" (
8
+ set SPHINXBUILD=sphinx-build
9
+ )
10
+ set SOURCEDIR=source
11
+ set BUILDDIR=build
12
+ set SPHINXPROJ=pyobs
13
+
14
+ if "%1" == "" goto help
15
+
16
+ %SPHINXBUILD% >NUL 2>NUL
17
+ if errorlevel 9009 (
18
+ echo.
19
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
20
+ echo.installed, then set the SPHINXBUILD environment variable to point
21
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
22
+ echo.may add the Sphinx directory to PATH.
23
+ echo.
24
+ echo.If you don't have Sphinx installed, grab it from
25
+ echo.http://sphinx-doc.org/
26
+ exit /b 1
27
+ )
28
+
29
+ %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
30
+ goto end
31
+
32
+ :help
33
+ %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
34
+
35
+ :end
36
+ popd
@@ -0,0 +1 @@
1
+ sphinx-rtd-theme
@@ -0,0 +1,117 @@
1
+ # Configuration file for the Sphinx documentation builder.
2
+ #
3
+ # This file does only contain a selection of the most common options. For a
4
+ # full list see the documentation:
5
+ # http://www.sphinx-doc.org/en/stable/config
6
+
7
+ # -- Path setup --------------------------------------------------------------
8
+
9
+ # If extensions (or modules to document with autodoc) are in another directory,
10
+ # add these directories to sys.path here. If the directory is relative to the
11
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
12
+ #
13
+ import os
14
+ import sys
15
+
16
+ sys.path.insert(0, os.path.abspath("../../"))
17
+
18
+
19
+ # -- Project information -----------------------------------------------------
20
+
21
+ project = "pyobs-brot"
22
+ copyright = "2024, Tim-Oliver Husser"
23
+ author = "Tim-Oliver Husser"
24
+
25
+ # The short X.Y version
26
+ # version = "0.16"
27
+ # The full version, including alpha/beta/rc tags
28
+ # release = "0.16.0"
29
+
30
+
31
+ # -- General configuration ---------------------------------------------------
32
+
33
+ add_module_names = False
34
+
35
+ # If your documentation needs a minimal Sphinx version, state it here.
36
+ #
37
+ # needs_sphinx = '1.0'
38
+
39
+ # Add any Sphinx extension module names here, as strings. They can be
40
+ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
41
+ # ones.
42
+ extensions = [
43
+ "sphinx.ext.autodoc",
44
+ "sphinx.ext.githubpages",
45
+ "sphinx.ext.napoleon",
46
+ "sphinx.ext.viewcode",
47
+ "sphinx.ext.autosectionlabel",
48
+ "sphinx.ext.intersphinx",
49
+ #'sphinx_autodoc_typehints'
50
+ ]
51
+
52
+ intersphinx_mapping = {"http://docs.python.org/3": None}
53
+
54
+ # napoleon settings
55
+ napoleon_google_docstring = True
56
+ napoleon_numpy_docstring = False
57
+ napoleon_use_param = False
58
+ napoleon_use_ivar = True
59
+
60
+ # typehints
61
+ # set_type_checking_flag = True
62
+ # autodoc_typehints = "description"
63
+
64
+ # show c'tor parameters in class only
65
+ autoclass_content = "both"
66
+
67
+ # Add any paths that contain templates here, relative to this directory.
68
+ templates_path = ["_templates"]
69
+
70
+ # The suffix(es) of source filenames.
71
+ # You can specify multiple suffix as a list of string:
72
+ #
73
+ source_suffix = ".rst"
74
+
75
+ # The master toctree document.
76
+ master_doc = "index"
77
+
78
+ # The language for content autogenerated by Sphinx. Refer to documentation
79
+ # for a list of supported languages.
80
+ #
81
+ # This is also used if you do content translation via gettext catalogs.
82
+ # Usually you set "language" from the command line for these cases.
83
+ language = "en"
84
+
85
+ # List of patterns, relative to source directory, that match files and
86
+ # directories to ignore when looking for source files.
87
+ # This pattern also affects html_static_path and html_extra_path .
88
+ exclude_patterns = []
89
+
90
+ # The name of the Pygments (syntax highlighting) style to use.
91
+ pygments_style = "sphinx"
92
+
93
+ # Be a little nitpicky
94
+ nitpicky = True
95
+ nitpick_ignore = []
96
+
97
+ # intersphinx
98
+ intersphinx_mapping = {
99
+ "pyobs": ("https://pyobs-core.readthedocs.io/en/latest/", None),
100
+ }
101
+
102
+ # -- Options for HTML output -------------------------------------------------
103
+
104
+ # The theme to use for HTML and HTML Help pages. See the documentation for
105
+ # a list of builtin themes.
106
+ html_theme = "sphinx_rtd_theme"
107
+ html_theme_options = {
108
+ "collapse_navigation": False,
109
+ "sticky_navigation": True,
110
+ "navigation_depth": 4,
111
+ "display_version": False,
112
+ "logo_only": False,
113
+ "prev_next_buttons_location": "bottom",
114
+ "titles_only": False,
115
+ "style_nav_header_background": "#cccccc",
116
+ }
117
+ html_logo = "_static/pyobs.gif"
@@ -0,0 +1,75 @@
1
+ pyobs-brot
2
+ ##########
3
+
4
+ This is a `pyobs <https://www.pyobs.org>`_ (`documentation <https://docs.pyobs.org>`_) module for telescopes,
5
+ domes and roll-off roofs controlled via `BROTlib <https://pypi.org/project/pybrotlib/>`_ over MQTT.
6
+
7
+
8
+ Example configuration
9
+ *********************
10
+
11
+ This is an example configuration for a telescope::
12
+
13
+ class: pyobs_brot.BrotRaDecTelescope
14
+ host: 1.2.3.4
15
+ name: My telescope
16
+ roof: roof
17
+ dome: dome
18
+
19
+ # communication
20
+ comm:
21
+ jid: test@example.com
22
+ password: ***
23
+
24
+ The dome and roof connect to the same BROT device and MQTT broker::
25
+
26
+ class: pyobs_brot.BrotDome
27
+ host: 1.2.3.4
28
+ name: My telescope
29
+
30
+ # communication
31
+ comm:
32
+ jid: test@example.com
33
+ password: ***
34
+
35
+ ::
36
+
37
+ class: pyobs_brot.BrotRoof
38
+ host: 1.2.3.4
39
+ name: My telescope
40
+
41
+ # communication
42
+ comm:
43
+ jid: test@example.com
44
+ password: ***
45
+
46
+
47
+ Available classes
48
+ *****************
49
+
50
+ :class:`~pyobs_brot.BrotRaDecTelescope` (based on :class:`~pyobs_brot.BrotBaseTelescope`) drives a telescope,
51
+ :class:`~pyobs_brot.BrotDome` a dome, and :class:`~pyobs_brot.BrotRoof` a roll-off roof.
52
+
53
+ BrotBaseTelescope
54
+ =================
55
+ .. autoclass:: pyobs_brot.BrotBaseTelescope
56
+ :members:
57
+ :show-inheritance:
58
+
59
+ BrotRaDecTelescope
60
+ ==================
61
+ .. autoclass:: pyobs_brot.BrotRaDecTelescope
62
+ :members:
63
+ :show-inheritance:
64
+
65
+ BrotDome
66
+ ========
67
+ .. autoclass:: pyobs_brot.BrotDome
68
+ :members:
69
+ :show-inheritance:
70
+
71
+ BrotRoof
72
+ ========
73
+ .. autoclass:: pyobs_brot.BrotRoof
74
+ :members:
75
+ :show-inheritance:
@@ -0,0 +1,4 @@
1
+ from .brotdome import BrotDome as BrotDome
2
+ from .brotroof import BrotRoof as BrotRoof
3
+ from .brottelescope import BrotBaseTelescope as BrotBaseTelescope
4
+ from .brottelescope import BrotRaDecTelescope as BrotRaDecTelescope
@@ -2,21 +2,19 @@ import asyncio
2
2
  import logging
3
3
  from typing import Any
4
4
 
5
- from pyobs.events import RoofOpenedEvent, RoofClosingEvent
6
- from pyobs.interfaces import IDome, IRoof, IMotion
7
- from pyobs.modules.roof.basedome import BaseDome
8
- from pyobs.modules import timeout
9
- from pyobs.utils.enums import MotionStatus
10
-
11
5
  from pybrotlib import BROT
6
+ from pybrotlib.components.dome import DomeShutterStatus, DomeStatus
12
7
  from pybrotlib.transport import MQTTTransport
13
- from pybrotlib.components.dome import DomeStatus, DomeShutterStatus
8
+ from pyobs.events import RoofClosingEvent, RoofOpenedEvent
9
+ from pyobs.interfaces import AltAzState, IDome, IPointingAltAz
10
+ from pyobs.modules import timeout
11
+ from pyobs.modules.roof.basedome import BaseDome
12
+ from pyobs.utils.enums import MotionStatus
14
13
 
15
14
  log = logging.getLogger(__name__)
16
15
 
17
16
 
18
17
  class BrotDome(BaseDome, IDome):
19
-
20
18
  def __init__(
21
19
  self,
22
20
  host: str,
@@ -28,7 +26,6 @@ class BrotDome(BaseDome, IDome):
28
26
  BaseDome.__init__(self, **kwargs)
29
27
  self.mqtt = MQTTTransport(host, port)
30
28
  self.brot = BROT(self.mqtt, name)
31
- # add thread for pulling the status constantly
32
29
  self.add_background_task(self._update_status)
33
30
 
34
31
  async def open(self) -> None:
@@ -37,7 +34,7 @@ class BrotDome(BaseDome, IDome):
37
34
  await asyncio.sleep(2)
38
35
  await self.comm.register_event(RoofOpenedEvent)
39
36
  await self.comm.register_event(RoofClosingEvent)
40
- # check whats up
37
+ await self.comm.set_state(IPointingAltAz, AltAzState(alt=0.0, az=self.brot.dome.azimuth))
41
38
  if self.brot.dome.status == DomeStatus.ERROR:
42
39
  await self._error_state()
43
40
  elif self.brot.dome.in_motion:
@@ -50,20 +47,11 @@ class BrotDome(BaseDome, IDome):
50
47
  await self._change_motion_status(MotionStatus.POSITIONED)
51
48
  log.info("Dome is already online. Please make sure it is not used by another instance!")
52
49
 
53
- async def get_altaz(self, **kwargs: Any) -> tuple[float, float]:
54
- """Returns current Alt and Az.
55
-
56
- Returns:
57
- Tuple of current Alt and Az in degrees.
58
- """
59
- return 0.0, self.brot.dome.azimuth
60
-
61
50
  async def _update_status(self) -> None:
62
51
  while True:
63
52
  try:
64
- current_state = await self.get_motion_status()
53
+ current_state = self.motion_status()
65
54
  new_state = current_state
66
- # first two can only be updated by the init/park method
67
55
  if current_state == MotionStatus.INITIALIZING:
68
56
  pass
69
57
  elif current_state == MotionStatus.PARKING:
@@ -78,9 +66,10 @@ class BrotDome(BaseDome, IDome):
78
66
  new_state = MotionStatus.POSITIONED
79
67
  if new_state != current_state:
80
68
  await self._change_motion_status(new_state)
69
+ await self.comm.set_state(IPointingAltAz, AltAzState(alt=0.0, az=self.brot.dome.azimuth))
81
70
  except asyncio.CancelledError:
82
71
  return
83
- except:
72
+ except Exception:
84
73
  pass
85
74
  await asyncio.sleep(1)
86
75
 
@@ -95,9 +84,7 @@ class BrotDome(BaseDome, IDome):
95
84
 
96
85
  await self._change_motion_status(MotionStatus.INITIALIZING)
97
86
 
98
- # send open command
99
87
  await self.brot.dome.open()
100
-
101
88
  while True:
102
89
  match self.brot.dome.shutter:
103
90
  case DomeShutterStatus.OPEN:
@@ -106,7 +93,6 @@ class BrotDome(BaseDome, IDome):
106
93
  case _:
107
94
  pass
108
95
  await asyncio.sleep(1)
109
- # send tracking command
110
96
  await self.brot.dome.start_tracking()
111
97
  while True:
112
98
  match self.brot.dome.status:
@@ -124,7 +110,7 @@ class BrotDome(BaseDome, IDome):
124
110
 
125
111
  @timeout(300)
126
112
  async def park(self, **kwargs: Any) -> None:
127
- if self.brot.dome.status == DomeStatus.PARKED and self.brot.some.shutter == DomeShutterStatus.CLOSED:
113
+ if self.brot.dome.status == DomeStatus.PARKED and self.brot.dome.shutter == DomeShutterStatus.CLOSED:
128
114
  return
129
115
  elif self.brot.dome.status == DomeStatus.ERROR:
130
116
  await self._error_state("Dome is in error state. Cannot close/park.")
@@ -132,7 +118,6 @@ class BrotDome(BaseDome, IDome):
132
118
 
133
119
  await self._change_motion_status(MotionStatus.PARKING)
134
120
  await self.comm.send_event(RoofClosingEvent())
135
- # stop tracking
136
121
  await self.brot.dome.stop_tracking()
137
122
  while True:
138
123
  match self.brot.dome.status:
@@ -144,7 +129,6 @@ class BrotDome(BaseDome, IDome):
144
129
  case _:
145
130
  break
146
131
  await asyncio.sleep(1)
147
- # close shutter
148
132
  await self.brot.dome.close()
149
133
  while True:
150
134
  match self.brot.dome.shutter:
@@ -154,8 +138,6 @@ class BrotDome(BaseDome, IDome):
154
138
  case _:
155
139
  pass
156
140
  await asyncio.sleep(1)
157
-
158
- # go to parking position
159
141
  await self.brot.dome.park()
160
142
  while True:
161
143
  match self.brot.dome.status:
@@ -174,16 +156,7 @@ class BrotDome(BaseDome, IDome):
174
156
  pass # no stopping of the roof possible
175
157
 
176
158
  async def move_altaz(self, alt: float, az: float, **kwargs: Any) -> None:
177
- """Moves to given coordinates.
178
-
179
- Args:
180
- alt: Alt in deg to move to.
181
- az: Az in deg to move to.
182
-
183
- Raises:
184
- MoveError: If device could not be moved.
185
- """
186
- pass
159
+ pass # dome tracks telescope automatically
187
160
 
188
161
  async def _error_state(self, mess: str = "Dome is in error state.") -> None:
189
162
  log.error(mess)
@@ -2,14 +2,13 @@ import asyncio
2
2
  import logging
3
3
  from typing import Any
4
4
 
5
- from pyobs.events import RoofOpenedEvent, RoofClosingEvent
6
- from pyobs.modules.roof.baseroof import BaseRoof
7
- from pyobs.modules import timeout
8
- from pyobs.utils.enums import MotionStatus
9
-
10
5
  from pybrotlib import BROT
11
- from pybrotlib.transport import MQTTTransport
12
6
  from pybrotlib.components.roof import RoofStatus
7
+ from pybrotlib.transport import MQTTTransport
8
+ from pyobs.events import RoofClosingEvent, RoofOpenedEvent
9
+ from pyobs.modules import timeout
10
+ from pyobs.modules.roof.baseroof import BaseRoof
11
+ from pyobs.utils.enums import MotionStatus
13
12
 
14
13
  log = logging.getLogger(__name__)
15
14