pyobs-brot 0.2.0__tar.gz → 2.0.0.dev1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-brot
3
- Version: 0.2.0
3
+ Version: 2.0.0.dev1
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
@@ -8,5 +8,5 @@ License-File: LICENSE
8
8
  Requires-Python: >=3.11
9
9
  Requires-Dist: astropy<8,>=7.0.1
10
10
  Requires-Dist: paho-mqtt>=2.1.0
11
- Requires-Dist: pybrotlib>=0.2.0
12
- Requires-Dist: pyobs-core<2,>=1.42.0
11
+ Requires-Dist: pybrotlib>=1.0.0
12
+ Requires-Dist: pyobs-core<3,>=2.0.0.dev6
@@ -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,16 +26,15 @@ 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:
35
32
  await BaseDome.open(self)
36
33
  asyncio.create_task(self.mqtt.run())
37
34
  await asyncio.sleep(2)
38
- await self._comm.register_event(RoofOpenedEvent)
39
- await self._comm.register_event(RoofClosingEvent)
40
- # check whats up
35
+ await self.comm.register_event(RoofOpenedEvent)
36
+ await self.comm.register_event(RoofClosingEvent)
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:
@@ -120,19 +106,18 @@ class BrotDome(BaseDome, IDome):
120
106
  pass
121
107
  await asyncio.sleep(1)
122
108
  await self._change_motion_status(MotionStatus.POSITIONED)
123
- await self._comm.send_event(RoofOpenedEvent())
109
+ await self.comm.send_event(RoofOpenedEvent())
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.")
131
117
  return
132
118
 
133
119
  await self._change_motion_status(MotionStatus.PARKING)
134
- await self._comm.send_event(RoofClosingEvent())
135
- # stop tracking
120
+ await self.comm.send_event(RoofClosingEvent())
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
 
@@ -33,8 +32,8 @@ class BrotRoof(BaseRoof):
33
32
  await BaseRoof.open(self)
34
33
  asyncio.create_task(self.mqtt.run())
35
34
 
36
- await self._comm.register_event(RoofOpenedEvent)
37
- await self._comm.register_event(RoofClosingEvent)
35
+ await self.comm.register_event(RoofOpenedEvent)
36
+ await self.comm.register_event(RoofClosingEvent)
38
37
 
39
38
  async def _update_status_task(self) -> None:
40
39
  while True:
@@ -76,7 +75,7 @@ class BrotRoof(BaseRoof):
76
75
  log.info("Roof is open.")
77
76
 
78
77
  await self._change_motion_status(MotionStatus.POSITIONED)
79
- await self._comm.send_event(RoofOpenedEvent())
78
+ await self.comm.send_event(RoofOpenedEvent())
80
79
 
81
80
  @timeout(300)
82
81
  async def park(self, **kwargs: Any) -> None:
@@ -87,7 +86,7 @@ class BrotRoof(BaseRoof):
87
86
  return
88
87
 
89
88
  await self._change_motion_status(MotionStatus.PARKING)
90
- await self._comm.send_event(RoofClosingEvent())
89
+ await self.comm.send_event(RoofClosingEvent())
91
90
 
92
91
  # close roof
93
92
  await self.brot.roof.close()
@@ -1,30 +1,36 @@
1
1
  import asyncio
2
2
  import logging
3
3
  from typing import Any
4
- import numpy as np
5
4
 
6
- from pyobs.mixins import FitsNamespaceMixin
5
+ import numpy as np
6
+ from pybrotlib import BROT # type: ignore
7
+ from pybrotlib.components.telescope import MotionState, TelescopeStatus # type: ignore
8
+ from pybrotlib.transport import MQTTTransport # type: ignore
7
9
  from pyobs.interfaces import (
8
- IRoof,
9
- IDome,
10
+ AltAzOffsetState,
11
+ AltAzState,
12
+ FocuserState,
10
13
  IFocuser,
11
- ITemperatures,
12
14
  IOffsetsAltAz,
13
15
  IOffsetsRaDec,
14
- IPointingSeries,
15
- IPointingRaDec,
16
16
  IPointingAltAz,
17
+ IPointingRaDec,
18
+ IPointingSeries,
19
+ IReady,
20
+ ITemperatures,
21
+ RaDecOffsetState,
22
+ RaDecState,
23
+ ReadyState,
24
+ SensorReading,
25
+ TemperaturesState,
17
26
  )
18
- from pyobs.modules.telescope.basetelescope import BaseTelescope
27
+ from pyobs.mixins import FitsNamespaceMixin
19
28
  from pyobs.modules import timeout
29
+ from pyobs.modules.telescope.basetelescope import BaseTelescope
20
30
  from pyobs.utils.enums import MotionStatus
21
31
  from pyobs.utils.publisher import CsvPublisher
22
32
  from pyobs.utils.time import Time
23
33
 
24
- from pybrotlib.transport import MQTTTransport # type: ignore
25
- from pybrotlib import BROT # type: ignore
26
- from pybrotlib.components.telescope import TelescopeStatus, GlobalTelescopeStatus, MotionState # type: ignore
27
-
28
34
  log = logging.getLogger(__name__)
29
35
 
30
36
 
@@ -48,41 +54,47 @@ class BrotBaseTelescope(
48
54
  dome: str = "None",
49
55
  **kwargs: Any,
50
56
  ):
51
- BaseTelescope.__init__(self, **kwargs, motion_status_interfaces=["ITelescope", "IFocuser"])
57
+ BaseTelescope.__init__(
58
+ self,
59
+ **kwargs,
60
+ motion_status_interfaces=["ITelescope", "IFocuser"],
61
+ wait_for_dome=None if dome == "None" else dome,
62
+ )
52
63
 
53
64
  self.mqtt = MQTTTransport(host, port)
54
65
  self.brot = BROT(self.mqtt, name)
55
66
  self.temperatures: dict[str, str] = {} if temperatures is None else temperatures
56
67
  self.focus_offset = 0.0
57
- self._dome = dome
58
68
  self._roof = roof
59
69
 
60
- # mixins
61
70
  FitsNamespaceMixin.__init__(self, **kwargs)
71
+ self.add_background_task(self._update_task)
72
+
73
+ @property
74
+ def _position_radec(self) -> tuple[float, float] | None:
75
+ try:
76
+ ra = self.brot.telescope._telemetry.POSITION.EQUATORIAL.RA_ICRS * 15
77
+ dec = self.brot.telescope._telemetry.POSITION.EQUATORIAL.DEC_ICRS
78
+ return ra, dec
79
+ except Exception:
80
+ return None
62
81
 
63
82
  async def open(self) -> None:
64
83
  await BaseTelescope.open(self)
65
84
  asyncio.create_task(self.mqtt.run())
66
85
  await asyncio.sleep(2)
67
- await self._update_task()
68
86
 
69
- if self._dome != "None":
70
- # check dome
71
- try:
72
- await self.proxy(self._dome, IDome)
73
- log.info("Dome was found.")
74
- except ValueError:
75
- log.warning("Dome does not exist or is not of correct type at the moment.")
76
- if self._roof != "None":
77
- # check dome
78
- try:
79
- await self.proxy(self._roof, IRoof)
80
- except ValueError:
81
- log.warning("Roof does not exist or is not of correct type at the moment.")
87
+ # publish initial states
88
+ await self.comm.set_state(IReady, ReadyState(ready=False))
89
+ await self.comm.set_state(
90
+ IFocuser, FocuserState(focus=float(self.brot.focus.position), focus_offset=self.focus_offset)
91
+ )
92
+ await self.comm.set_state(IOffsetsRaDec, RaDecOffsetState(ra=0.0, dec=0.0))
93
+ await self.comm.set_state(IOffsetsAltAz, AltAzOffsetState(alt=0.0, az=0.0))
82
94
 
83
95
  async def close(self) -> None:
84
96
  await BaseTelescope.close(self)
85
- self.mqtt.close()
97
+ await self.mqtt.close()
86
98
 
87
99
  async def _update_task(self) -> None:
88
100
  await asyncio.sleep(2)
@@ -94,14 +106,16 @@ class BrotBaseTelescope(
94
106
  break
95
107
 
96
108
  async def _update(self) -> None:
97
- # check what's up
98
109
  match self.brot.telescope.status:
99
110
  case TelescopeStatus.PARKED:
100
111
  await self._change_motion_status(MotionStatus.PARKED)
112
+ await self.comm.set_state(IReady, ReadyState(ready=False))
101
113
  case TelescopeStatus.INITIALIZING:
102
114
  await self._change_motion_status(MotionStatus.INITIALIZING)
115
+ await self.comm.set_state(IReady, ReadyState(ready=False))
103
116
  case TelescopeStatus.PARKING:
104
117
  await self._change_motion_status(MotionStatus.PARKING)
118
+ await self.comm.set_state(IReady, ReadyState(ready=False))
105
119
  case TelescopeStatus.ONLINE:
106
120
  if self.brot.telescope.motion_state == MotionState.SLEWING:
107
121
  await self._change_motion_status(MotionStatus.SLEWING)
@@ -109,26 +123,41 @@ class BrotBaseTelescope(
109
123
  await self._change_motion_status(MotionStatus.TRACKING)
110
124
  else:
111
125
  await self._change_motion_status(MotionStatus.POSITIONED)
126
+ await self.comm.set_state(IReady, ReadyState(ready=True))
112
127
  case TelescopeStatus.ERROR:
113
128
  await self._error_state()
129
+ await self.comm.set_state(IReady, ReadyState(ready=False))
130
+
131
+ # publish pointing state
132
+ ra = self.brot.telescope._telemetry.POSITION.EQUATORIAL.RA_ICRS * 15
133
+ dec = self.brot.telescope._telemetry.POSITION.EQUATORIAL.DEC_ICRS
134
+ await self.comm.set_state(IPointingRaDec, RaDecState(ra=ra, dec=dec))
135
+ alt = self.brot.telescope._telemetry.POSITION.HORIZONTAL.ALT
136
+ az = self.brot.telescope._telemetry.POSITION.HORIZONTAL.AZ
137
+ await self.comm.set_state(IPointingAltAz, AltAzState(alt=alt, az=az))
138
+
139
+ # publish temperatures
140
+ readings = []
141
+ for name, loc in self.temperatures.items():
142
+ for sensor in self.mqtt.telemetry.AUXILIARY.SENSOR.values():
143
+ if sensor.NAME == loc:
144
+ readings.append(SensorReading(name=name, value=sensor.VALUE))
145
+ if readings:
146
+ await self.comm.set_state(ITemperatures, TemperaturesState(readings=readings))
114
147
 
115
148
  async def _error_state(self, mess: str = "Telescope is in error state.") -> None:
116
149
  log.error(mess)
117
150
  await self._change_motion_status(MotionStatus.ERROR)
118
- return
119
151
 
120
152
  async def _wait_for_tracking(self) -> None:
121
153
  await asyncio.sleep(2)
122
154
  while True:
123
155
  match self.brot.telescope._telemetry.TELESCOPE.MOTION_STATE:
124
156
  case 0.0, 1.0:
125
- # still moving
126
157
  pass
127
158
  case 8.0:
128
- # tracking -> exit
129
159
  return
130
160
  case -1.0:
131
- # something went wrong
132
161
  await self._error_state("The telescope experienced an error during track command.")
133
162
  return
134
163
  case _:
@@ -137,30 +166,12 @@ class BrotBaseTelescope(
137
166
 
138
167
  @timeout(5 * 60)
139
168
  async def _move_radec(self, ra: float, dec: float, abort_event: asyncio.Event) -> None:
140
- # send command
141
169
  await self.brot.telescope.track(ra, dec)
142
170
  await asyncio.sleep(10)
143
171
  await self._wait_for_tracking()
144
- if self._dome != "None":
145
- try:
146
- dome = await self.proxy(self._dome, IDome)
147
- while True:
148
- match await dome.get_motion_status():
149
- case MotionStatus.POSITIONED:
150
- return
151
- case MotionStatus.ERROR:
152
- await self._error_state("The dome experienced an error during track command.")
153
- return
154
- case MotionStatus.PARKED:
155
- log.info("The dome is parked, tracking but not onsky.")
156
- return
157
- await asyncio.sleep(1)
158
- except:
159
- log.warning("Dome module cannot be reached.")
160
172
 
161
173
  @timeout(120)
162
174
  async def _move_altaz(self, alt: float, az: float, abort_event: asyncio.Event) -> None:
163
- # send command
164
175
  await self.brot.telescope.move(alt, az)
165
176
  await asyncio.sleep(5)
166
177
  while True:
@@ -168,87 +179,40 @@ class BrotBaseTelescope(
168
179
  case 0.0, 1.0:
169
180
  break
170
181
  case 1.0, 8.0:
171
- # still moving
172
182
  pass
173
183
  case _:
174
- # something went wrong
175
184
  await self._change_motion_status(MotionStatus.ERROR)
176
185
  return
177
186
  await asyncio.sleep(1)
178
- if self._dome != "None":
179
- try:
180
- dome = await self.proxy(self._dome, IDome)
181
- while True:
182
- match await dome.get_motion_status():
183
- case MotionStatus.POSITIONED:
184
- await self._change_motion_status(MotionStatus.POSITIONED)
185
- return
186
- case MotionStatus.ERROR:
187
- await self._error_state("The dome experienced an error during track command.")
188
- return
189
- case MotionStatus.PARKED:
190
- await self._change_motion_status(MotionStatus.POSITIONED)
191
- log.info("The dome is parked, tracking but not onsky.")
192
- return
193
- await asyncio.sleep(1)
194
- except:
195
- log.warning("Dome module cannot be reached.")
196
-
197
- async def get_altaz(self, **kwargs: Any) -> tuple[float, float]:
198
- return (
199
- self.brot.telescope._telemetry.POSITION.HORIZONTAL.ALT,
200
- self.brot.telescope._telemetry.POSITION.HORIZONTAL.AZ,
201
- )
202
-
203
- async def get_radec(self, **kwargs: Any) -> tuple[float, float]:
204
- return (
205
- self.brot.telescope._telemetry.POSITION.EQUATORIAL.RA_ICRS * 15,
206
- self.brot.telescope._telemetry.POSITION.EQUATORIAL.DEC_ICRS,
207
- )
208
-
209
- async def get_temperatures(self, **kwargs: Any) -> dict[str, float]:
210
- """Returns all temperatures measured by this module.
211
-
212
- Returns:
213
- Dict containing temperatures.
214
- """
215
- data: dict[str, float] = {}
216
- for name, loc in self.temperatures.items():
217
- for sensor in self.mqtt.telemetry.AUXILIARY.SENSOR.values():
218
- if sensor.NAME == loc:
219
- data[name] = sensor.VALUE
220
- return data
221
-
222
- async def _wait_for_focus(self) -> None:
223
- await asyncio.sleep(2.0)
224
- MAX_TARGET_DISTANCE = 0.01
225
- while self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.FOCUS.TARGETDISTANCE > MAX_TARGET_DISTANCE:
226
- await asyncio.sleep(0.1)
227
187
 
228
188
  async def set_focus(self, focus: float, **kwargs: Any) -> None:
229
189
  await self._change_motion_status(MotionStatus.SLEWING, interface="IFocuser")
230
190
  await self.brot.focus.set(focus + self.focus_offset)
231
191
  await self._wait_for_focus()
232
192
  await self._change_motion_status(MotionStatus.POSITIONED, interface="IFocuser")
193
+ await self.comm.set_state(IFocuser, FocuserState(focus=focus, focus_offset=self.focus_offset))
233
194
 
234
195
  async def set_focus_offset(self, offset: float, **kwargs: Any) -> None:
235
196
  await self._change_motion_status(MotionStatus.SLEWING, interface="IFocuser")
236
197
  focus = self.brot.focus.position
237
198
  await self.brot.focus.set(focus + offset)
238
199
  await self._wait_for_focus()
200
+ self.focus_offset = offset
239
201
  await self._change_motion_status(MotionStatus.POSITIONED, interface="IFocuser")
202
+ await self.comm.set_state(
203
+ IFocuser, FocuserState(focus=float(self.brot.focus.position - self.focus_offset), focus_offset=offset)
204
+ )
240
205
 
241
- async def get_focus(self, **kwargs: Any) -> float:
242
- return float(self.brot.focus.position - self.focus_offset)
243
-
244
- async def get_focus_offset(self, **kwargs: Any) -> float:
245
- return self.focus_offset
206
+ async def _wait_for_focus(self) -> None:
207
+ await asyncio.sleep(2.0)
208
+ MAX_TARGET_DISTANCE = 0.01
209
+ while self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.FOCUS.TARGETDISTANCE > MAX_TARGET_DISTANCE:
210
+ await asyncio.sleep(0.1)
246
211
 
247
212
  @timeout(120)
248
213
  async def init(self, **kwargs: Any) -> None:
249
- # check whats up
250
214
  match self.brot.telescope.status:
251
- case TelescopeStatus.PARKED, TelescopeStatus.INITPARK:
215
+ case TelescopeStatus.PARKED:
252
216
  pass
253
217
  case TelescopeStatus.ONLINE:
254
218
  log.info("Telescope is already online.")
@@ -258,7 +222,6 @@ class BrotBaseTelescope(
258
222
  return
259
223
 
260
224
  log.info("Initializing telescope...")
261
- # send command
262
225
  await self.brot.telescope.power_on()
263
226
  while True:
264
227
  match self.brot.telescope._telemetry.TELESCOPE.READY_STATE:
@@ -266,19 +229,16 @@ class BrotBaseTelescope(
266
229
  log.info("Telescope powered up and initialized.")
267
230
  return
268
231
  case -1.0:
269
- # something went wrong
270
232
  await self._error_state("Error during powerup of telescope.")
271
233
  return
272
234
  case 0.0:
273
- # still moving
274
235
  pass
275
236
  await asyncio.sleep(1)
276
237
 
277
238
  @timeout(180)
278
239
  async def park(self, **kwargs: Any) -> None:
279
- # check whats up
280
240
  match self.brot.telescope.status:
281
- case TelescopeStatus.PARKED, TelescopeStatus.INITPARK:
241
+ case TelescopeStatus.PARKED:
282
242
  log.info("Telescope is already parked.")
283
243
  return
284
244
  case TelescopeStatus.ONLINE:
@@ -288,7 +248,6 @@ class BrotBaseTelescope(
288
248
  return
289
249
 
290
250
  log.info("Parking telescope...")
291
- # send command
292
251
  await self.brot.telescope.park()
293
252
  while True:
294
253
  match self.brot.telescope._telemetry.TELESCOPE.READY_STATE:
@@ -296,17 +255,14 @@ class BrotBaseTelescope(
296
255
  log.info("Parked telescope.")
297
256
  return
298
257
  case -1.0:
299
- # something went wrong
300
258
  await self._error_state("Error during parking of the telescope.")
301
259
  return
302
260
  case _:
303
- # still moving
304
261
  pass
305
262
  await asyncio.sleep(1)
306
263
 
307
264
  @timeout(20)
308
265
  async def stop_motion(self, device: str | None = None, **kwargs: Any) -> None:
309
- # send command
310
266
  await self.brot.telescope.stop()
311
267
  while True:
312
268
  match self.brot.telescope._telemetry.TELESCOPE.MOTION_STATE:
@@ -314,29 +270,17 @@ class BrotBaseTelescope(
314
270
  log.info("Stopped telescope.")
315
271
  return
316
272
  case 1.0 | 8.0:
317
- # still going
318
273
  pass
319
274
  case _:
320
- # error
321
275
  await self._error_state("Error during stopping of the telescope.")
322
276
  return
323
277
  await asyncio.sleep(1)
324
278
 
325
- async def is_ready(self, **kwargs: Any) -> bool:
326
- """Returns the device is "ready", whatever that means for the specific device.
327
-
328
- Returns:
329
- Whether device is ready
330
- """
279
+ async def start_pointing_series(self, **kwargs: Any) -> None:
280
+ pass
331
281
 
332
- # check that motion is not in one of the states listed below
333
- return await self.get_motion_status() not in [
334
- MotionStatus.PARKED,
335
- MotionStatus.INITIALIZING,
336
- MotionStatus.PARKING,
337
- MotionStatus.ERROR,
338
- MotionStatus.UNKNOWN,
339
- ]
282
+ async def stop_pointing_series(self, **kwargs: Any) -> None:
283
+ pass
340
284
 
341
285
 
342
286
  class BrotRaDecTelescope(BrotBaseTelescope, IOffsetsRaDec):
@@ -349,41 +293,23 @@ class BrotRaDecTelescope(BrotBaseTelescope, IOffsetsRaDec):
349
293
  self._pointing_log = None if pointing_file is None else CsvPublisher(pointing_file)
350
294
 
351
295
  async def set_offsets_radec(self, dra: float, ddec: float, **kwargs: Any) -> None:
352
- # send dra as dha
353
296
  await self.brot.telescope.set_offset_ha(-1.0 * dra * 3600)
354
297
  await self.brot.telescope.set_offset_dec(ddec * 3600)
355
298
  MAX_TARGET_DISTANCE = 2.0 / 3600.0
356
- while (self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.HA.TARGETDISTANCE < MAX_TARGET_DISTANCE and
357
- self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.DEC.TARGETDISTANCE < MAX_TARGET_DISTANCE):
299
+ while (
300
+ self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.HA.TARGETDISTANCE < MAX_TARGET_DISTANCE
301
+ and self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.DEC.TARGETDISTANCE < MAX_TARGET_DISTANCE
302
+ ):
358
303
  await asyncio.sleep(0.1)
359
-
360
- async def get_offsets_radec(self, **kwargs: Any) -> tuple[float, float]:
361
- return (
362
- self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.HA.OFFSET * -1.0,
363
- self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.DEC.OFFSET,
364
- )
304
+ await self.comm.set_state(IOffsetsRaDec, RaDecOffsetState(ra=dra, dec=ddec))
365
305
 
366
306
  async def get_fits_header_before(
367
307
  self, namespaces: list[str] | None = None, **kwargs: Any
368
308
  ) -> dict[str, tuple[Any, str]]:
369
- """Returns FITS header for the current status of this module.
370
-
371
- Args:
372
- namespaces: If given, only return FITS headers for the given namespaces.
373
-
374
- Returns:
375
- Dictionary containing FITS headers.
376
- """
377
-
378
- # get headers from base
379
309
  hdr = await BrotBaseTelescope.get_fits_header_before(self)
380
-
381
- # define values to request
382
310
  hdr["TEL-FOCU"] = (self.brot.focus.position, "Focus position [mm]")
383
311
  hdr["HAOFF"] = (self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.HA.OFFSET, "Hour Angle offset")
384
312
  hdr["DECOFF"] = (self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.DEC.OFFSET, "Declination offset")
385
-
386
- # return it
387
313
  return self._filter_fits_namespace(hdr, namespaces=namespaces, **kwargs)
388
314
 
389
315
  async def add_pointing_measurement(self, **kwargs: Any) -> None:
@@ -392,16 +318,14 @@ class BrotRaDecTelescope(BrotBaseTelescope, IOffsetsRaDec):
392
318
  "Time": Time.now().isot,
393
319
  "Ha": telemetry.OBJECT.EQUATORIAL.HA_APPARENT,
394
320
  "Dec": telemetry.OBJECT.EQUATORIAL.DEC_APPARENT,
395
- "HaOff": telemetry.POSITION.INSTRUMENTAL.HA.OFFSET / np.cos(np.radians(telemetry.OBJECT.EQUATORIAL.DEC_APPARENT))
321
+ "HaOff": telemetry.POSITION.INSTRUMENTAL.HA.OFFSET
322
+ / np.cos(np.radians(telemetry.OBJECT.EQUATORIAL.DEC_APPARENT))
396
323
  + telemetry.POINTING.OFFSETS.HA,
397
324
  "DecOff": telemetry.POSITION.INSTRUMENTAL.DEC.OFFSET + telemetry.POINTING.OFFSETS.DEC,
398
325
  }
399
- await self._pointing_log(**data)
326
+ if self._pointing_log is not None:
327
+ await self._pointing_log(**data)
400
328
  log.info("Pointing measurement written.")
401
- async def start_pointing_series(self, **kwargs: Any) -> None:
402
- pass
403
- async def stop_pointing_series(self, **kwargs: Any) -> None:
404
- pass
405
329
 
406
330
 
407
331
  class BrotAltAzTelescope(BrotBaseTelescope, IOffsetsAltAz, IPointingSeries):
@@ -415,18 +339,8 @@ class BrotAltAzTelescope(BrotBaseTelescope, IOffsetsAltAz, IPointingSeries):
415
339
 
416
340
  @timeout(120)
417
341
  async def set_offsets_altaz(self, dalt: float, daz: float, **kwargs: Any) -> None:
418
- """Move an Alt/Az offset.
419
-
420
- Args:
421
- dalt: Altitude offset in degrees.
422
- daz: Azimuth offset in degrees.
423
-
424
- Raises:
425
- MoveError: If device could not be moved.
426
- """
427
342
  await self.brot.telescope.set_offset_alt(dalt * 3600)
428
343
  await self.brot.telescope.set_offset_az(daz * 3600)
429
-
430
344
  await asyncio.sleep(1.0)
431
345
  MAX_TARGET_DISTANCE = 2.0 / 3600.0
432
346
  while (
@@ -434,39 +348,15 @@ class BrotAltAzTelescope(BrotBaseTelescope, IOffsetsAltAz, IPointingSeries):
434
348
  or self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.AZ.TARGETDISTANCE > MAX_TARGET_DISTANCE
435
349
  ):
436
350
  await asyncio.sleep(0.1)
437
-
438
- async def get_offsets_altaz(self, **kwargs: Any) -> tuple[float, float]:
439
- """Get Alt/Az offset.
440
-
441
- Returns:
442
- Tuple with alt and az offsets.
443
- """
444
- return (
445
- self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.ALT.OFFSET,
446
- self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.AZ.OFFSET,
447
- )
351
+ await self.comm.set_state(IOffsetsAltAz, AltAzOffsetState(alt=dalt, az=daz))
448
352
 
449
353
  async def get_fits_header_before(
450
354
  self, namespaces: list[str] | None = None, **kwargs: Any
451
355
  ) -> dict[str, tuple[Any, str]]:
452
- """Returns FITS header for the current status of this module.
453
-
454
- Args:
455
- namespaces: If given, only return FITS headers for the given namespaces.
456
-
457
- Returns:
458
- Dictionary containing FITS headers.
459
- """
460
-
461
- # get headers from base
462
356
  hdr = await BrotBaseTelescope.get_fits_header_before(self)
463
-
464
- # define values to request
465
357
  hdr["TEL-FOCU"] = (self.brot.focus.position, "Focus position [mm]")
466
358
  hdr["HAOFF"] = (self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.HA.OFFSET, "Hour Angle offset")
467
359
  hdr["DECOFF"] = (self.brot.telescope._telemetry.POSITION.INSTRUMENTAL.DEC.OFFSET, "Declination offset")
468
-
469
- # return it
470
360
  return self._filter_fits_namespace(hdr, namespaces=namespaces, **kwargs)
471
361
 
472
362
  async def add_pointing_measurement(self, **kwargs: Any) -> None:
@@ -479,7 +369,8 @@ class BrotAltAzTelescope(BrotBaseTelescope, IOffsetsAltAz, IPointingSeries):
479
369
  + telemetry.POINTING.OFFSETS.AZ,
480
370
  "AltOff": telemetry.POSITION.INSTRUMENTAL.ALT.OFFSET + telemetry.POINTING.OFFSETS.ALT,
481
371
  }
482
- await self._pointing_log(**data)
372
+ if self._pointing_log is not None:
373
+ await self._pointing_log(**data)
483
374
  log.info("Pointing measurement written.")
484
375
 
485
376
 
@@ -1,22 +1,23 @@
1
1
  [project]
2
2
  name = "pyobs-brot"
3
- version = "0.2.0"
3
+ version = "2.0.0.dev1"
4
4
  description = "pyobs module for BROTlib telescopes"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }, { name = "Lukas Melzig", email = "lukas.melzig@stud.uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"
7
7
  license = "MIT"
8
8
  dependencies = [
9
9
  "astropy>=7.0.1,<8",
10
- "pyobs-core>=1.42.0,<2",
10
+ "pyobs-core>=2.0.0.dev6,<3",
11
11
  "paho-mqtt>=2.1.0",
12
- "pybrotlib>=0.2.0",
12
+ "pybrotlib>=1.0.0",
13
13
  ]
14
14
 
15
15
  [dependency-groups]
16
16
  dev = [
17
17
  "black>=23.7.0,<24",
18
+ "ruff>=0.9.0",
19
+ "pyrefly>=1.1.1",
18
20
  "pre-commit>=3.3.3,<4",
19
- "flake8>=7.3.0",
20
21
  ]
21
22
 
22
23
  [build-system]
@@ -26,3 +27,13 @@ build-backend = "hatchling.build"
26
27
  [tool.black]
27
28
  line-length = 120
28
29
  target-version = ['py311']
30
+
31
+ [tool.ruff]
32
+ target-version = "py311"
33
+ line-length = 120
34
+
35
+ [tool.ruff.lint]
36
+ select = ["E", "F", "W", "I", "UP", "G"]
37
+
38
+ [tool.pyrefly]
39
+ python-version = "3.11"
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- revision = 2
2
+ revision = 3
3
3
  requires-python = ">=3.11"
4
4
  resolution-markers = [
5
5
  "python_full_version >= '3.14' and sys_platform == 'win32'",
@@ -204,11 +204,11 @@ wheels = [
204
204
 
205
205
  [[package]]
206
206
  name = "astropy-iers-data"
207
- version = "0.2026.5.11.1.8.52"
207
+ version = "0.2026.6.22.1.23.34"
208
208
  source = { registry = "https://pypi.org/simple" }
209
- sdist = { url = "https://files.pythonhosted.org/packages/1e/a1/186ae52e05fe90cb9a02f6fa27201e066b91d951fe6d32cd5220def2f1dd/astropy_iers_data-0.2026.5.11.1.8.52.tar.gz", hash = "sha256:11d7d6caa141d1ab14196a645672989f25014cbf97d97940c407f278e26b400d", size = 1933045, upload-time = "2026-05-11T01:09:38.884Z" }
209
+ sdist = { url = "https://files.pythonhosted.org/packages/67/3c/534eb2ec8c29efbb46cd4986392e92177a055780468a86d5e3f921f75bca/astropy_iers_data-0.2026.6.22.1.23.34.tar.gz", hash = "sha256:15e12a6863a366ff4a4d246087afb75f71a00d1fed86b9fd6d86f29a71809d38", size = 1939323, upload-time = "2026-06-22T01:24:09.888Z" }
210
210
  wheels = [
211
- { url = "https://files.pythonhosted.org/packages/a8/77/22a4fd8774e04aa2599fe02637b3cea107ac5234745ae91dbd3e7a8349dd/astropy_iers_data-0.2026.5.11.1.8.52-py3-none-any.whl", hash = "sha256:40c449c35bd8deabc20053f024f3d4c0cbea2947adf620fcbb6f4242b55e2090", size = 1989748, upload-time = "2026-05-11T01:09:36.91Z" },
211
+ { url = "https://files.pythonhosted.org/packages/97/1e/75767a712dd23740273560df5b9dcd203362f5e5c6bea846d99a7175e233/astropy_iers_data-0.2026.6.22.1.23.34-py3-none-any.whl", hash = "sha256:d96f5102426e7ba5f28d7aabf417a547d912482afa97c24430f5c9fe9b6a884d", size = 1994066, upload-time = "2026-06-22T01:24:08.609Z" },
212
212
  ]
213
213
 
214
214
  [[package]]
@@ -669,20 +669,6 @@ wheels = [
669
669
  { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" },
670
670
  ]
671
671
 
672
- [[package]]
673
- name = "flake8"
674
- version = "7.3.0"
675
- source = { registry = "https://pypi.org/simple" }
676
- dependencies = [
677
- { name = "mccabe" },
678
- { name = "pycodestyle" },
679
- { name = "pyflakes" },
680
- ]
681
- sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" }
682
- wheels = [
683
- { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" },
684
- ]
685
-
686
672
  [[package]]
687
673
  name = "frozenlist"
688
674
  version = "1.8.0"
@@ -919,12 +905,12 @@ wheels = [
919
905
  ]
920
906
 
921
907
  [[package]]
922
- name = "mccabe"
923
- version = "0.7.0"
908
+ name = "logging-journald"
909
+ version = "0.6.11"
924
910
  source = { registry = "https://pypi.org/simple" }
925
- sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" }
911
+ sdist = { url = "https://files.pythonhosted.org/packages/cc/07/77567e0001d3acffddc80470497f0eec3ccb544b72e672550b3c1f02e586/logging_journald-0.6.11.tar.gz", hash = "sha256:1741ae42cf8953e435489a82acf0e88bc21ad23351e867ca4453a354fc6d09e1", size = 5432, upload-time = "2024-12-13T08:17:10.147Z" }
926
912
  wheels = [
927
- { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" },
913
+ { url = "https://files.pythonhosted.org/packages/27/7b/033a15aea43108af5049d8c0a09c3ea30519295aee1f3ed74c9efa1ee4cb/logging_journald-0.6.11-py3-none-any.whl", hash = "sha256:4d280eb0bddb4bf616297ee2428592b037618b6f0169c873f655aff92496c232", size = 5619, upload-time = "2024-12-13T08:17:09.127Z" },
928
914
  ]
929
915
 
930
916
  [[package]]
@@ -1475,6 +1461,34 @@ wheels = [
1475
1461
  { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" },
1476
1462
  ]
1477
1463
 
1464
+ [[package]]
1465
+ name = "psutil"
1466
+ version = "7.2.2"
1467
+ source = { registry = "https://pypi.org/simple" }
1468
+ sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
1469
+ wheels = [
1470
+ { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" },
1471
+ { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" },
1472
+ { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" },
1473
+ { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" },
1474
+ { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" },
1475
+ { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" },
1476
+ { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" },
1477
+ { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" },
1478
+ { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" },
1479
+ { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" },
1480
+ { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" },
1481
+ { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" },
1482
+ { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
1483
+ { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
1484
+ { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
1485
+ { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
1486
+ { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
1487
+ { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
1488
+ { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
1489
+ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
1490
+ ]
1491
+
1478
1492
  [[package]]
1479
1493
  name = "py-expression-eval"
1480
1494
  version = "0.3.14"
@@ -1507,14 +1521,14 @@ wheels = [
1507
1521
 
1508
1522
  [[package]]
1509
1523
  name = "pybrotlib"
1510
- version = "0.3.11"
1524
+ version = "1.0.0"
1511
1525
  source = { registry = "https://pypi.org/simple" }
1512
1526
  dependencies = [
1513
1527
  { name = "aiomqtt" },
1514
1528
  ]
1515
- sdist = { url = "https://files.pythonhosted.org/packages/b7/32/72dde3983c3257846c6c71c0351772a31b894425f160374b0dc40404ed3d/pybrotlib-0.3.11.tar.gz", hash = "sha256:81d3aa902b598ea6350ced3f1dc820b070301814f67d64de6b0e5698ce3a8357", size = 4946, upload-time = "2026-03-22T13:31:38.115Z" }
1529
+ sdist = { url = "https://files.pythonhosted.org/packages/18/72/bd0880c7512761e3afdd96d4eec0453cae9858c3ed7426e92d6a735ce60b/pybrotlib-1.0.0.tar.gz", hash = "sha256:958d14780d81f6b3362021aaab34be15bff41bfb7dfddb81e3164b9bee878220", size = 4968, upload-time = "2026-05-26T16:38:34.291Z" }
1516
1530
  wheels = [
1517
- { url = "https://files.pythonhosted.org/packages/64/f1/dd5d0fe8d2d32b089f779155d5c6f5466abcd9f5e2161798007ab4f27126/pybrotlib-0.3.11-py3-none-any.whl", hash = "sha256:5dfb80c48d50131d1219aca7a2b64f996e7cc4e53c8259ed40a6c916160172fd", size = 9485, upload-time = "2026-03-22T13:31:37.18Z" },
1531
+ { url = "https://files.pythonhosted.org/packages/af/2c/f0ab674fbb5d0ae4ea8914e59adb4cdac31ada95cd91741a44c52a4479eb/pybrotlib-1.0.0-py3-none-any.whl", hash = "sha256:03363136f6062cd678953dcba61db402126d15aa39d6d6b5f11a3760adfeead1", size = 9481, upload-time = "2026-05-26T16:38:33.447Z" },
1518
1532
  ]
1519
1533
 
1520
1534
  [[package]]
@@ -1588,15 +1602,6 @@ wheels = [
1588
1602
  { url = "https://files.pythonhosted.org/packages/b2/c4/8bfffecd08b9b198113fcff5f0ab84bbe696f07dec46dd1ccae0e7b28c23/pycares-5.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:e0a86eff6bf9e91d5dd8876b1b82ee45704f46b1104c24291d3dea2c1fc8ebcb", size = 113043, upload-time = "2026-01-01T12:36:37.895Z" },
1589
1603
  ]
1590
1604
 
1591
- [[package]]
1592
- name = "pycodestyle"
1593
- version = "2.14.0"
1594
- source = { registry = "https://pypi.org/simple" }
1595
- sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" }
1596
- wheels = [
1597
- { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" },
1598
- ]
1599
-
1600
1605
  [[package]]
1601
1606
  name = "pycparser"
1602
1607
  version = "3.0"
@@ -1741,15 +1746,6 @@ wheels = [
1741
1746
  { url = "https://files.pythonhosted.org/packages/b4/11/97233cf23ad5411ac6f13b1d6ee3888f90ace4f974d9bf9db887aa428912/pyerfa-2.0.1.5-cp39-abi3-win_amd64.whl", hash = "sha256:66292d437dcf75925b694977aa06eb697126e7b86553e620371ed3e48b5e0ad0", size = 349410, upload-time = "2024-11-11T15:22:24.817Z" },
1742
1747
  ]
1743
1748
 
1744
- [[package]]
1745
- name = "pyflakes"
1746
- version = "3.4.0"
1747
- source = { registry = "https://pypi.org/simple" }
1748
- sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" }
1749
- wheels = [
1750
- { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" },
1751
- ]
1752
-
1753
1749
  [[package]]
1754
1750
  name = "pynacl"
1755
1751
  version = "1.6.2"
@@ -1787,7 +1783,7 @@ wheels = [
1787
1783
 
1788
1784
  [[package]]
1789
1785
  name = "pyobs-brot"
1790
- version = "0.2.0"
1786
+ version = "2.0.0.dev1"
1791
1787
  source = { editable = "." }
1792
1788
  dependencies = [
1793
1789
  { name = "astropy" },
@@ -1799,43 +1795,48 @@ dependencies = [
1799
1795
  [package.dev-dependencies]
1800
1796
  dev = [
1801
1797
  { name = "black" },
1802
- { name = "flake8" },
1803
1798
  { name = "pre-commit" },
1799
+ { name = "pyrefly" },
1800
+ { name = "ruff" },
1804
1801
  ]
1805
1802
 
1806
1803
  [package.metadata]
1807
1804
  requires-dist = [
1808
1805
  { name = "astropy", specifier = ">=7.0.1,<8" },
1809
1806
  { name = "paho-mqtt", specifier = ">=2.1.0" },
1810
- { name = "pybrotlib", specifier = ">=0.2.0" },
1811
- { name = "pyobs-core", specifier = ">=1.42.0,<2" },
1807
+ { name = "pybrotlib", specifier = ">=1.0.0" },
1808
+ { name = "pyobs-core", specifier = ">=2.0.0.dev6,<3" },
1812
1809
  ]
1813
1810
 
1814
1811
  [package.metadata.requires-dev]
1815
1812
  dev = [
1816
1813
  { name = "black", specifier = ">=23.7.0,<24" },
1817
- { name = "flake8", specifier = ">=7.3.0" },
1818
1814
  { name = "pre-commit", specifier = ">=3.3.3,<4" },
1815
+ { name = "pyrefly", specifier = ">=1.1.1" },
1816
+ { name = "ruff", specifier = ">=0.9.0" },
1819
1817
  ]
1820
1818
 
1821
1819
  [[package]]
1822
1820
  name = "pyobs-core"
1823
- version = "1.42.0"
1821
+ version = "2.0.0.dev6"
1824
1822
  source = { registry = "https://pypi.org/simple" }
1825
1823
  dependencies = [
1826
1824
  { name = "aiohttp" },
1827
1825
  { name = "astroplan" },
1828
1826
  { name = "astropy" },
1827
+ { name = "astropy-iers-data" },
1829
1828
  { name = "astropydantic" },
1830
1829
  { name = "astroquery" },
1831
1830
  { name = "colour" },
1832
1831
  { name = "dacite" },
1833
1832
  { name = "dbus-next" },
1834
1833
  { name = "influxdb-client" },
1834
+ { name = "logging-journald" },
1835
1835
  { name = "numpy" },
1836
1836
  { name = "pandas" },
1837
1837
  { name = "paramiko" },
1838
1838
  { name = "pillow" },
1839
+ { name = "psutil" },
1839
1840
  { name = "py-expression-eval" },
1840
1841
  { name = "pydantic" },
1841
1842
  { name = "pytz" },
@@ -1844,11 +1845,31 @@ dependencies = [
1844
1845
  { name = "scipy" },
1845
1846
  { name = "single-source" },
1846
1847
  { name = "slixmpp" },
1848
+ { name = "tenacity" },
1847
1849
  { name = "typing-extensions" },
1848
1850
  ]
1849
- sdist = { url = "https://files.pythonhosted.org/packages/d5/be/dbad84fd77f329bc9cdbaf3a20b324a4de884ca4977987ea85a2fa02ebc9/pyobs_core-1.42.0.tar.gz", hash = "sha256:9f6d01ab32fb718eaeab8c07ea62cf35836f32b4e0391b851eb45e7e63cd7cf2", size = 280243, upload-time = "2026-05-16T12:49:44.302Z" }
1851
+ sdist = { url = "https://files.pythonhosted.org/packages/ca/1f/f3ff220ff8cc908198a130f98aacb2bd0c9963befd9d0757bcff13800f84/pyobs_core-2.0.0.dev6.tar.gz", hash = "sha256:ee8f98c4299064a2383666b349cf87af98f9c3de437048e8b56dcb2817e0024b", size = 299899, upload-time = "2026-06-30T17:39:36.203Z" }
1852
+ wheels = [
1853
+ { url = "https://files.pythonhosted.org/packages/17/b6/3875a77f1206ef5d02d5ac43ae9fd90ba96be7857fe4308dfc0c599eec16/pyobs_core-2.0.0.dev6-py3-none-any.whl", hash = "sha256:542a78b069cac525cc72b9fc5fce8983629f8f0a240ff5ee722617886a8c9c13", size = 508486, upload-time = "2026-06-30T17:39:34.739Z" },
1854
+ ]
1855
+
1856
+ [[package]]
1857
+ name = "pyrefly"
1858
+ version = "1.1.1"
1859
+ source = { registry = "https://pypi.org/simple" }
1860
+ sdist = { url = "https://files.pythonhosted.org/packages/8e/20/976165fa4b1517a1a92f393b3f4d4badabfff1165eff09d4cd4908428183/pyrefly-1.1.1.tar.gz", hash = "sha256:6deda959f8603a7dbdf112c48983e2275b2903cf33c8c739ed65d7e71a4fd520", size = 5880491, upload-time = "2026-06-18T23:45:43.785Z" }
1850
1861
  wheels = [
1851
- { url = "https://files.pythonhosted.org/packages/e5/9e/ec848b355b3d288c0dc1d7d919140fe892bff2e0e1d53348f0b793326105/pyobs_core-1.42.0-py3-none-any.whl", hash = "sha256:265550778f0854e6676155dda025f6f68bee763e9a65da993319bbc4d63f581b", size = 480750, upload-time = "2026-05-16T12:49:45.804Z" },
1862
+ { url = "https://files.pythonhosted.org/packages/b5/d6/02ba666018c6a1cb4ddfa2db98ada721adddd374db5c29ba47a0bf2637fa/pyrefly-1.1.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f4b8595f91885bc8b5e3c282ab68d1df21201668a84e6508b1e15f2feec0bb8d", size = 13631867, upload-time = "2026-06-18T23:45:13.923Z" },
1863
+ { url = "https://files.pythonhosted.org/packages/71/47/7a3457dbbddb513a83cf4fe527d5d5ebda5201a1010ad2a6034030e3e358/pyrefly-1.1.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6b238e1362622d47a6eb5af704fd8b613c94e8c303386efd6350e3da59fecc8", size = 13075304, upload-time = "2026-06-18T23:45:16.865Z" },
1864
+ { url = "https://files.pythonhosted.org/packages/84/df/70f4b3f42d58ed686a80df31e04eca54d88036cea4f9b96195c64ad0b2b5/pyrefly-1.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b50d4510e4f8aaea79e2c4b343a4d7a060c9451c0b2aa9bfe10d7ca1ef33d68d", size = 13446966, upload-time = "2026-06-18T23:45:19.644Z" },
1865
+ { url = "https://files.pythonhosted.org/packages/3c/53/12a19bd6c7af985bcbc13c6910d0f9f6684069ead2282a5c08c2bfbb5d03/pyrefly-1.1.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f330cf039ef3da3b910c84f3a7e431f0cf8d0c1d2dad26491d6cadf3c7cd4759", size = 14449222, upload-time = "2026-06-18T23:45:22.252Z" },
1866
+ { url = "https://files.pythonhosted.org/packages/93/f0/e55c48a50076fc0f9ecf4bdedec50456db383e01162f5e2121f8468be071/pyrefly-1.1.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6342d87c52b04f72156da04f554c4d57f3616f2b32d1763969efb22d05a1407", size = 14472947, upload-time = "2026-06-18T23:45:24.858Z" },
1867
+ { url = "https://files.pythonhosted.org/packages/b6/e7/30e085b31fed978ecb675bdbb54df566673ab550469e5af2d350f6af0be6/pyrefly-1.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c08b814ad03175e9cf47111390537161828b472044c39ab3320252b3ac6b2edd", size = 13975252, upload-time = "2026-06-18T23:45:27.247Z" },
1868
+ { url = "https://files.pythonhosted.org/packages/47/58/49c3e67641133d3fe5d8d9a660dc0826c6c37ca197d86cad05fa7dd8bfd6/pyrefly-1.1.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d50cad97f19fc893b04deff7239626cffff5dd27ffb29b7d303a1b770247b208", size = 13471780, upload-time = "2026-06-18T23:45:29.775Z" },
1869
+ { url = "https://files.pythonhosted.org/packages/71/1e/65a7ba8355e2c39d8331832905fb74dcc85fc122a3f1dfd6dbf2a88907ad/pyrefly-1.1.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2150b450ee6a6bcbe69b2d45d9a4ebc934a609e1abcf65e490433f38eb873d84", size = 13989306, upload-time = "2026-06-18T23:45:32.576Z" },
1870
+ { url = "https://files.pythonhosted.org/packages/37/de/b7ee1ab2392c36945738246fba7524439810befa3cfcc03cb6157567fc10/pyrefly-1.1.1-py3-none-win32.whl", hash = "sha256:5ffd8a8ed62fe4e6bf0afe1837d1bad149bb3b9f80e928ef248c96b836db3742", size = 12608469, upload-time = "2026-06-18T23:45:35.419Z" },
1871
+ { url = "https://files.pythonhosted.org/packages/a6/9c/a0f5b52934bf80e9c7eff08222e7caf318287b9aef76acb8d9ac5740581b/pyrefly-1.1.1-py3-none-win_amd64.whl", hash = "sha256:4e0430f3ef69c8ac73505fd6584db70ed504665a9f0816fef7f723de510f26cb", size = 13502172, upload-time = "2026-06-18T23:45:38.375Z" },
1872
+ { url = "https://files.pythonhosted.org/packages/42/3d/4c6bcb3d456835f51445d3662a428f56c3ea5643ec798c577030ae34298c/pyrefly-1.1.1-py3-none-win_arm64.whl", hash = "sha256:83baf0db71e172665db1fca0ced50b8f7773f5192ca57e8ac6773a772b6d2fc5", size = 12895979, upload-time = "2026-06-18T23:45:41.026Z" },
1852
1873
  ]
1853
1874
 
1854
1875
  [[package]]
@@ -1989,6 +2010,31 @@ wheels = [
1989
2010
  { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
1990
2011
  ]
1991
2012
 
2013
+ [[package]]
2014
+ name = "ruff"
2015
+ version = "0.15.20"
2016
+ source = { registry = "https://pypi.org/simple" }
2017
+ sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" }
2018
+ wheels = [
2019
+ { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" },
2020
+ { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" },
2021
+ { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" },
2022
+ { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" },
2023
+ { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" },
2024
+ { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" },
2025
+ { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" },
2026
+ { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" },
2027
+ { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" },
2028
+ { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" },
2029
+ { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" },
2030
+ { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" },
2031
+ { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" },
2032
+ { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" },
2033
+ { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" },
2034
+ { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" },
2035
+ { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" },
2036
+ ]
2037
+
1992
2038
  [[package]]
1993
2039
  name = "scipy"
1994
2040
  version = "1.17.1"
@@ -2129,6 +2175,15 @@ wheels = [
2129
2175
  { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" },
2130
2176
  ]
2131
2177
 
2178
+ [[package]]
2179
+ name = "tenacity"
2180
+ version = "9.1.4"
2181
+ source = { registry = "https://pypi.org/simple" }
2182
+ sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" }
2183
+ wheels = [
2184
+ { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" },
2185
+ ]
2186
+
2132
2187
  [[package]]
2133
2188
  name = "typing-extensions"
2134
2189
  version = "4.15.0"
@@ -1,2 +0,0 @@
1
- from .brottelescope import BrotBaseTelescope, BrotRaDecTelescope
2
- from .brotdome import BrotDome
File without changes
File without changes
File without changes