pyBROTlib 0.1.4__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.
- pybrotlib-0.1.4/PKG-INFO +11 -0
- pybrotlib-0.1.4/README.md +0 -0
- pybrotlib-0.1.4/pyproject.toml +26 -0
- pybrotlib-0.1.4/src/pybrotlib/__init__.py +0 -0
- pybrotlib-0.1.4/src/pybrotlib/base.py +11 -0
- pybrotlib-0.1.4/src/pybrotlib/brot.py +16 -0
- pybrotlib-0.1.4/src/pybrotlib/dome.py +87 -0
- pybrotlib-0.1.4/src/pybrotlib/focus.py +21 -0
- pybrotlib-0.1.4/src/pybrotlib/mirrorcovers.py +24 -0
- pybrotlib-0.1.4/src/pybrotlib/mqtttransport.py +70 -0
- pybrotlib-0.1.4/src/pybrotlib/py.typed +0 -0
- pybrotlib-0.1.4/src/pybrotlib/telemetry.py +161 -0
- pybrotlib-0.1.4/src/pybrotlib/telescope.py +186 -0
- pybrotlib-0.1.4/src/pybrotlib/transport.py +24 -0
pybrotlib-0.1.4/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyBROTlib
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: MQTT interface to BROT telescopes
|
|
5
|
+
Author: Tim-Oliver Husser, Lukas Melzig
|
|
6
|
+
Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>, Lukas Melzig <lukas.melzig@stud.uni-goettingen.de>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Dist: aiomqtt>=2.4.0
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyBROTlib"
|
|
3
|
+
version = "0.1.4"
|
|
4
|
+
description = "MQTT interface to BROT telescopes"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }, { name = "Lukas Melzig", email = "lukas.melzig@stud.uni-goettingen.de" }]
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"aiomqtt>=2.4.0",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[dependency-groups]
|
|
14
|
+
dev = [
|
|
15
|
+
"mypy>=1.15.0,<2",
|
|
16
|
+
"black>=25.1.0,<26",
|
|
17
|
+
"pre-commit>=4.2.0,<5",
|
|
18
|
+
"flake8>=7.3.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["uv_build>=0.9.8,<0.10.0"]
|
|
23
|
+
build-backend = "uv_build"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.package-data]
|
|
26
|
+
"pyobs" = ["py.typed"]
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .transport import Transport
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BROTBase:
|
|
5
|
+
def __init__(self, transport: Transport, telescope_name: str):
|
|
6
|
+
self._transport = transport
|
|
7
|
+
self._telemetry = self._transport.telemetry
|
|
8
|
+
self._telescope_name = telescope_name
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["BROTBase"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .dome import BROTDome
|
|
2
|
+
from .focus import BROTFocus
|
|
3
|
+
from .mirrorcovers import BROTMirrorCovers
|
|
4
|
+
from .telescope import BROTTelescope
|
|
5
|
+
from .transport import Transport
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BROT:
|
|
9
|
+
def __init__(self, transport: Transport, telescope_name: str):
|
|
10
|
+
self.dome = BROTDome(transport, telescope_name)
|
|
11
|
+
self.focus = BROTFocus(transport, telescope_name)
|
|
12
|
+
self.mirrorcovers = BROTMirrorCovers(transport, telescope_name)
|
|
13
|
+
self.telescope = BROTTelescope(transport, telescope_name)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["BROT"]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from .base import BROTBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DomeShutterStatus(Enum):
|
|
7
|
+
CLOSED = "closed"
|
|
8
|
+
MOVING = "moving"
|
|
9
|
+
OPEN = "open"
|
|
10
|
+
UNKNOWN = "unknown"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DomeStatus(Enum):
|
|
14
|
+
PARKED = "parked"
|
|
15
|
+
ERROR = "error"
|
|
16
|
+
TRACKING = "tracking"
|
|
17
|
+
UNKNOWN = "unknown"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BROTDome(BROTBase):
|
|
21
|
+
@property
|
|
22
|
+
def shutter(self) -> DomeShutterStatus:
|
|
23
|
+
match self._telemetry.AUXILIARY.DOME.REALPOS:
|
|
24
|
+
case 0.0:
|
|
25
|
+
return DomeShutterStatus.CLOSED
|
|
26
|
+
case 0.5:
|
|
27
|
+
return DomeShutterStatus.MOVING
|
|
28
|
+
case 1.0:
|
|
29
|
+
return DomeShutterStatus.OPEN
|
|
30
|
+
case _:
|
|
31
|
+
return DomeShutterStatus.UNKNOWN
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def in_motion(self) -> bool:
|
|
35
|
+
return (self._telemetry.AUXILIARY.DOME.REALPOS == 0.5) or (self._telemetry.AUXILIARY.DOME.MOTION_STATE == 1.0)
|
|
36
|
+
@property
|
|
37
|
+
def azimuth(self) -> float:
|
|
38
|
+
return self._telemetry.AUXILIARY.DOME.AZ
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def status(self) -> DomeStatus:
|
|
42
|
+
match self._telemetry.AUXILIARY.DOME.READY_STATE:
|
|
43
|
+
case 0.0:
|
|
44
|
+
return DomeStatus.PARKED
|
|
45
|
+
case -1.0:
|
|
46
|
+
return DomeStatus.ERROR
|
|
47
|
+
case 8.0:
|
|
48
|
+
return DomeStatus.TRACKING
|
|
49
|
+
case _:
|
|
50
|
+
return DomeStatus.UNKNOWN
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def error_state(self) -> bool:
|
|
54
|
+
return self._telemetry.AUXILIARY.DOME.ERROR_STATE != 0
|
|
55
|
+
|
|
56
|
+
async def open(self) -> None:
|
|
57
|
+
await self._transport.publish(
|
|
58
|
+
f"{self._telescope_name}/Telescope/SET", "command dome_open=1"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async def close(self) -> None:
|
|
62
|
+
await self._transport.publish(
|
|
63
|
+
f"{self._telescope_name}/Telescope/SET", "command dome_close=1"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
async def start_tracking(self) -> None:
|
|
67
|
+
await self._transport.publish(
|
|
68
|
+
f"{self._telescope_name}/Telescope/SET", "command dome_track=1"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async def stop_tracking(self) -> None:
|
|
72
|
+
await self._transport.publish(
|
|
73
|
+
f"{self._telescope_name}/Telescope/SET", "command dome_track=0"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def park(self) -> None:
|
|
77
|
+
await self._transport.publish(
|
|
78
|
+
f"{self._telescope_name}/Telescope/SET", "command dome_park=1"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
async def reset(self) -> None:
|
|
82
|
+
await self._transport.publish(
|
|
83
|
+
f"{self._telescope_name}/Telescope/SET", "command dome_reset=1"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = ["BROTDome", "DomeStatus", "DomeShutterStatus"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .base import BROTBase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BROTFocus(BROTBase):
|
|
5
|
+
@property
|
|
6
|
+
def position(self) -> float:
|
|
7
|
+
return self._telemetry.POSITION.INSTRUMENTAL.FOCUS.CURRPOS
|
|
8
|
+
|
|
9
|
+
async def set(self, focus: float) -> None:
|
|
10
|
+
await self._transport.publish(f"{self._telescope_name}/Telescope/SET", f"command focus={focus}")
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def powered(self) -> bool:
|
|
14
|
+
return self._telemetry.POSITION.INSTRUMENTAL.FOCUS.POWER_STATE == 1.0
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def referenced(self) -> bool:
|
|
18
|
+
return self._telemetry.POSITION.INSTRUMENTAL.FOCUS.REFERENCED == 1.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["BROTFocus"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from .base import BROTBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MirrorCoverStatus(Enum):
|
|
7
|
+
CLOSED = "closed"
|
|
8
|
+
MOVING = "moving"
|
|
9
|
+
OPEN = "open"
|
|
10
|
+
UNKNOWN = "unknown"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BROTMirrorCovers(BROTBase):
|
|
14
|
+
@property
|
|
15
|
+
def status(self) -> MirrorCoverStatus:
|
|
16
|
+
match self._telemetry.AUXILIARY.COVER.REALPOS:
|
|
17
|
+
case 0.0:
|
|
18
|
+
return MirrorCoverStatus.CLOSED
|
|
19
|
+
case 0.5:
|
|
20
|
+
return MirrorCoverStatus.MOVING
|
|
21
|
+
case 1.0:
|
|
22
|
+
return MirrorCoverStatus.OPEN
|
|
23
|
+
case _:
|
|
24
|
+
return MirrorCoverStatus.UNKNOWN
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import get_type_hints
|
|
2
|
+
from aiomqtt import Client, Message # type: ignore
|
|
3
|
+
|
|
4
|
+
from .transport import Transport
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MQTTTransport(Transport):
|
|
8
|
+
def __init__(self, host: str, port: int):
|
|
9
|
+
super().__init__()
|
|
10
|
+
|
|
11
|
+
self.host = host
|
|
12
|
+
self.port = port
|
|
13
|
+
|
|
14
|
+
def __str__(self) -> str:
|
|
15
|
+
return f"MQTT(host={self.host}, port={self.port})"
|
|
16
|
+
|
|
17
|
+
async def publish(self, topic: str, message: str) -> None:
|
|
18
|
+
async with Client(self.host, self.port) as client:
|
|
19
|
+
await client.publish(topic, payload=message.encode("utf-8"))
|
|
20
|
+
|
|
21
|
+
async def run(self) -> None:
|
|
22
|
+
async with Client(self.host, self.port) as client:
|
|
23
|
+
self._connected = True
|
|
24
|
+
await client.subscribe("#")
|
|
25
|
+
async for message in client.messages:
|
|
26
|
+
await self._process_message(message)
|
|
27
|
+
|
|
28
|
+
async def _process_message(self, msg: Message) -> None:
|
|
29
|
+
# Telemetry handling
|
|
30
|
+
if "Telemetry" in msg.topic.value:
|
|
31
|
+
# we only want bytes...
|
|
32
|
+
if not isinstance(msg.payload, bytes):
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# analyse message
|
|
36
|
+
key, value = msg.payload.decode("utf-8").split(" ")[1].split("=")
|
|
37
|
+
s = key.upper().split(".")
|
|
38
|
+
obj = self.telemetry
|
|
39
|
+
|
|
40
|
+
# dict with ALL telemetry
|
|
41
|
+
self.data[key] = str(value)
|
|
42
|
+
|
|
43
|
+
# find object in telemetry tree
|
|
44
|
+
for token in s[:-1]:
|
|
45
|
+
if hasattr(obj, token):
|
|
46
|
+
obj = getattr(obj, token)
|
|
47
|
+
else:
|
|
48
|
+
print("Unknown variable:", key)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
# does it exist?
|
|
52
|
+
val: bool | int | float | str
|
|
53
|
+
if hasattr(obj, s[-1]):
|
|
54
|
+
typ = get_type_hints(obj)[s[-1]]
|
|
55
|
+
if typ == bool:
|
|
56
|
+
val = value.lower() == "true"
|
|
57
|
+
elif typ == int:
|
|
58
|
+
val = int(value)
|
|
59
|
+
elif typ == float:
|
|
60
|
+
val = float(value)
|
|
61
|
+
else:
|
|
62
|
+
val = value
|
|
63
|
+
setattr(obj, s[-1], val)
|
|
64
|
+
|
|
65
|
+
if "Log" in msg.topic.value:
|
|
66
|
+
pass
|
|
67
|
+
# payload = str(msg.payload)[2:-2].split(' message="')
|
|
68
|
+
# log_message = payload[1]
|
|
69
|
+
# log_level = payload[0].split("level=")[1]
|
|
70
|
+
# self.logMessageReceived.emit(log_level, log_message)
|
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class TelescopeInfo:
|
|
6
|
+
NAME: str = "Unknown"
|
|
7
|
+
DIAMETER: float = 0.0
|
|
8
|
+
CABINET: str = "Unknown"
|
|
9
|
+
MANUFACTURER: str = "Unknown"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TelescopeConfig:
|
|
14
|
+
CAPABILITIES: int = 0
|
|
15
|
+
MOUNTOPTIONS: str = "Unknown"
|
|
16
|
+
MOUNT: str = "Unknown"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class TelescopeStatus:
|
|
21
|
+
GLOBAL: int = 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Telescope:
|
|
26
|
+
READY_STATE: float = 0.0
|
|
27
|
+
MOTION_STATE: float = 0.0
|
|
28
|
+
INFO: TelescopeInfo = field(default_factory=TelescopeInfo)
|
|
29
|
+
CONFIG: TelescopeConfig = field(default_factory=TelescopeConfig)
|
|
30
|
+
STATUS: TelescopeStatus = field(default_factory=TelescopeStatus)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ObjectInstrumental:
|
|
35
|
+
RA: float = 0.0
|
|
36
|
+
DEC: float = 0.0
|
|
37
|
+
HA: float = 0.0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ObjectHorizontal:
|
|
42
|
+
ALT: float = 0.0
|
|
43
|
+
AZ: float = 0.0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ObjectEquatorial:
|
|
48
|
+
EPOCH: str = "Unknown"
|
|
49
|
+
EQUINOX: str = "Unknown"
|
|
50
|
+
RA_PM: float = 0.0
|
|
51
|
+
DEC_PM: float = 0.0
|
|
52
|
+
RA_RATE: float = 0.0
|
|
53
|
+
DEC_RATE: float = 0.0
|
|
54
|
+
RA: float = 0.0
|
|
55
|
+
DEC: float = 0.0
|
|
56
|
+
HA: float = 0.0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class Object:
|
|
61
|
+
INSTRUMENTAL: ObjectInstrumental = field(default_factory=ObjectInstrumental)
|
|
62
|
+
HORIZONTAL: ObjectHorizontal = field(default_factory=ObjectHorizontal)
|
|
63
|
+
EQUATORIAL: ObjectEquatorial = field(default_factory=ObjectEquatorial)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class PointingOffsets:
|
|
68
|
+
HA: float = 0.0
|
|
69
|
+
DEC: float = 0.0
|
|
70
|
+
ALT: float = 0.0
|
|
71
|
+
AZ: float = 0.0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class Pointing:
|
|
76
|
+
OFFSETS: PointingOffsets = field(default_factory=PointingOffsets)
|
|
77
|
+
SLEWTIME: float = 0.0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class PositionLocal:
|
|
82
|
+
SIDEREAL_TIME: float = 0.0
|
|
83
|
+
JD: float = 0.0
|
|
84
|
+
LATITUDE: float = 0.0
|
|
85
|
+
LONGITUDE: float = 0.0
|
|
86
|
+
HEIGHT: float = 0.0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class PositionAxis:
|
|
91
|
+
POWER_STATE: float = 0.0
|
|
92
|
+
ERROR_STATE: str = ""
|
|
93
|
+
MOTION_STATE: int = 0
|
|
94
|
+
REFERENCED: float = 0.0
|
|
95
|
+
REALPOS: float = 0.0
|
|
96
|
+
CURRPOS: float = 0.0
|
|
97
|
+
TARGETPOS: float = 0.0
|
|
98
|
+
CURRSPEED: float = 0.0
|
|
99
|
+
TARGETDISTANCE: float = 0.0
|
|
100
|
+
OFFSET: float = 0.0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class PositionInstrumental:
|
|
105
|
+
HA: PositionAxis = field(default_factory=PositionAxis)
|
|
106
|
+
DEC: PositionAxis = field(default_factory=PositionAxis)
|
|
107
|
+
ALT: PositionAxis = field(default_factory=PositionAxis)
|
|
108
|
+
AZ: PositionAxis = field(default_factory=PositionAxis)
|
|
109
|
+
FOCUS: PositionAxis = field(default_factory=PositionAxis)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class PositionHorizontal:
|
|
114
|
+
ALT: float = 0.0
|
|
115
|
+
AZ: float = 0.0
|
|
116
|
+
DOME: float = 0.0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class PositionEquatorial:
|
|
121
|
+
RA_J2000: float = 0.0
|
|
122
|
+
DEC_J2000: float = 0.0
|
|
123
|
+
HA_J2000: float = 0.0
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class Position:
|
|
128
|
+
LOCAL: PositionLocal = field(default_factory=PositionLocal)
|
|
129
|
+
INSTRUMENTAL: PositionInstrumental = field(default_factory=PositionInstrumental)
|
|
130
|
+
HORIZONTAL: PositionHorizontal = field(default_factory=PositionHorizontal)
|
|
131
|
+
EQUATORIAL: PositionEquatorial = field(default_factory=PositionEquatorial)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class Dome:
|
|
136
|
+
REALPOS: float = 0.0
|
|
137
|
+
TARGETPOS: float = 0.0
|
|
138
|
+
ERROR_STATE: int = 0
|
|
139
|
+
READY_STATE: float = 0.0
|
|
140
|
+
MOTION_STATE: float = 0.0
|
|
141
|
+
AZ: float = 0.0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class Cover:
|
|
146
|
+
REALPOS: float = 0.0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass
|
|
150
|
+
class Auxiliary:
|
|
151
|
+
DOME: Dome = field(default_factory=Dome)
|
|
152
|
+
COVER: Cover = field(default_factory=Cover)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass
|
|
156
|
+
class Telemetry:
|
|
157
|
+
TELESCOPE: Telescope = field(default_factory=Telescope)
|
|
158
|
+
OBJECT: Object = field(default_factory=Object)
|
|
159
|
+
POINTING: Pointing = field(default_factory=Pointing)
|
|
160
|
+
POSITION: Position = field(default_factory=Position)
|
|
161
|
+
AUXILIARY: Auxiliary = field(default_factory=Auxiliary)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .base import BROTBase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TelescopeStatus(Enum):
|
|
8
|
+
PARKED = "parked"
|
|
9
|
+
ONLINE = "online"
|
|
10
|
+
ERROR = "error"
|
|
11
|
+
INITPARK = "initpark"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GlobalTelescopeStatus(Enum):
|
|
15
|
+
NOTELESCOPE = "notelescope"
|
|
16
|
+
OPERATIONAL = "operational"
|
|
17
|
+
PANIC = "panic"
|
|
18
|
+
ERROR = "error"
|
|
19
|
+
WARNING = "warning"
|
|
20
|
+
INFO = "info"
|
|
21
|
+
UNKNOWN = "unknown"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BROTAxis(BROTBase):
|
|
25
|
+
def __init__(self, name: str, *args: Any, **kwargs: Any):
|
|
26
|
+
super().__init__(*args, **kwargs)
|
|
27
|
+
self._axis_name = name
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def error_state(self) -> int:
|
|
31
|
+
return int(
|
|
32
|
+
getattr(self._telemetry.POSITION.INSTRUMENTAL, self._axis_name).ERROR_STATE
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BROTTelescope(BROTBase):
|
|
37
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
38
|
+
super().__init__(*args, **kwargs)
|
|
39
|
+
self.alt = BROTAxis("ALT", *args, **kwargs)
|
|
40
|
+
self.az = BROTAxis("AZ", *args, **kwargs)
|
|
41
|
+
self.ha = BROTAxis("HA", *args, **kwargs)
|
|
42
|
+
self.dec = BROTAxis("DEC", *args, **kwargs)
|
|
43
|
+
self.focus = BROTAxis("FOCUS", *args, **kwargs)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def name(self) -> str:
|
|
47
|
+
return self._telemetry.TELESCOPE.INFO.NAME
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def diameter(self) -> float:
|
|
51
|
+
return self._telemetry.TELESCOPE.INFO.DIAMETER
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def cabinet(self) -> str:
|
|
55
|
+
return self._telemetry.TELESCOPE.INFO.CABINET
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def manufacturer(self) -> str:
|
|
59
|
+
return self._telemetry.TELESCOPE.INFO.MANUFACTURER
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def mount_options(self) -> str:
|
|
63
|
+
return self._telemetry.TELESCOPE.CONFIG.MOUNTOPTIONS
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def mount(self) -> str:
|
|
67
|
+
return self._telemetry.TELESCOPE.CONFIG.MOUNT
|
|
68
|
+
|
|
69
|
+
async def track(self, ra: float, dec: float) -> None:
|
|
70
|
+
await self._transport.publish(
|
|
71
|
+
f"{self._telescope_name}/Telescope/SET", f"command rightascension={ra}"
|
|
72
|
+
)
|
|
73
|
+
await self._transport.publish(
|
|
74
|
+
f"{self._telescope_name}/Telescope/SET", f"command declination={dec}"
|
|
75
|
+
)
|
|
76
|
+
await self._transport.publish(
|
|
77
|
+
f"{self._telescope_name}/Telescope/SET", "command track=1"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def move(self, alt: float, az: float) -> None:
|
|
81
|
+
await self._transport.publish(
|
|
82
|
+
f"{self._telescope_name}/Telescope/SET", f"command elevation={alt}"
|
|
83
|
+
)
|
|
84
|
+
await self._transport.publish(
|
|
85
|
+
f"{self._telescope_name}/Telescope/SET", f"command azimuth={az}"
|
|
86
|
+
)
|
|
87
|
+
await self._transport.publish(
|
|
88
|
+
f"{self._telescope_name}/Telescope/SET", "command slew=1"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def offset_ha(self) -> float:
|
|
93
|
+
return self._telemetry.POSITION.INSTRUMENTAL.HA.OFFSET * 3600.0
|
|
94
|
+
|
|
95
|
+
async def set_offset_ha(self, offset: float) -> None:
|
|
96
|
+
await self._transport.publish(
|
|
97
|
+
f"{self._telescope_name}/Telescope/SET",
|
|
98
|
+
f"command hourangleoffset={offset/3600.}",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def offset_dec(self) -> float:
|
|
103
|
+
return self._telemetry.POSITION.INSTRUMENTAL.DEC.OFFSET * 3600.0
|
|
104
|
+
|
|
105
|
+
async def set_offset_dec(self, offset: float) -> None:
|
|
106
|
+
await self._transport.publish(
|
|
107
|
+
f"{self._telescope_name}/Telescope/SET",
|
|
108
|
+
f"command declinationoffset={offset/3600.}",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def offset_alt(self) -> float:
|
|
113
|
+
return self._telemetry.POSITION.INSTRUMENTAL.ALT.OFFSET * 3600.0
|
|
114
|
+
|
|
115
|
+
async def set_offset_alt(self, offset: float) -> None:
|
|
116
|
+
await self._transport.publish(
|
|
117
|
+
f"{self._telescope_name}/Telescope/SET",
|
|
118
|
+
f"command elevationoffset={offset/3600.}",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def offset_az(self) -> float:
|
|
123
|
+
return self._telemetry.POSITION.INSTRUMENTAL.AZ.OFFSET * 3600.0
|
|
124
|
+
|
|
125
|
+
async def set_offset_az(self, offset: float) -> None:
|
|
126
|
+
await self._transport.publish(
|
|
127
|
+
f"{self._telescope_name}/Telescope/SET",
|
|
128
|
+
f"command azimuthoffset={offset/3600.}",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def status(self) -> TelescopeStatus:
|
|
133
|
+
match self._telemetry.TELESCOPE.READY_STATE:
|
|
134
|
+
case 0.0:
|
|
135
|
+
return TelescopeStatus.PARKED
|
|
136
|
+
case 1.0:
|
|
137
|
+
return TelescopeStatus.ONLINE
|
|
138
|
+
case -1.0:
|
|
139
|
+
return TelescopeStatus.ERROR
|
|
140
|
+
case _:
|
|
141
|
+
return TelescopeStatus.INITPARK
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def global_status(self) -> GlobalTelescopeStatus:
|
|
145
|
+
match self._telemetry.TELESCOPE.STATUS.GLOBAL:
|
|
146
|
+
case -1.0:
|
|
147
|
+
return GlobalTelescopeStatus.NOTELESCOPE
|
|
148
|
+
case 0.0:
|
|
149
|
+
return GlobalTelescopeStatus.OPERATIONAL
|
|
150
|
+
case 1.0:
|
|
151
|
+
return GlobalTelescopeStatus.PANIC
|
|
152
|
+
case 2.0:
|
|
153
|
+
return GlobalTelescopeStatus.ERROR
|
|
154
|
+
case 4.0:
|
|
155
|
+
return GlobalTelescopeStatus.WARNING
|
|
156
|
+
case 8.0:
|
|
157
|
+
return GlobalTelescopeStatus.INFO
|
|
158
|
+
case _:
|
|
159
|
+
return GlobalTelescopeStatus.UNKNOWN
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def initpark(self) -> float:
|
|
163
|
+
return self._telemetry.TELESCOPE.READY_STATE * 100.0
|
|
164
|
+
|
|
165
|
+
async def power_on(self) -> None:
|
|
166
|
+
await self._transport.publish(
|
|
167
|
+
f"{self._telescope_name}/Telescope/SET", "command power=true"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
async def stop(self) -> None:
|
|
171
|
+
await self._transport.publish(
|
|
172
|
+
f"{self._telescope_name}/Telescope/SET", "command stop=TRUE"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
async def park(self) -> None:
|
|
176
|
+
await self._transport.publish(
|
|
177
|
+
f"{self._telescope_name}/Telescope/SET", "command park=true"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
async def reset(self) -> None:
|
|
181
|
+
await self._transport.publish(
|
|
182
|
+
f"{self._telescope_name}/Telescope/SET", "command reset=1"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
__all__ = ["BROTTelescope", "TelescopeStatus", "GlobalTelescopeStatus"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .telemetry import Telemetry
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Transport(metaclass=ABCMeta):
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.data: dict[str, Any] = {}
|
|
11
|
+
self.telemetry = Telemetry()
|
|
12
|
+
self._connected = False
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
async def run(self) -> None:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
async def publish(self, topic: str, message: str) -> None:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def connected(self) -> bool:
|
|
24
|
+
return self._connected
|