Coverage for core / src / sensorkit / std / mount.py: 100%

88 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-09-02 00:03 +0000

1# SPDX-License-Identifier: Apache-2.0 

2"""Standard mount commands, keywords, traits, and archetype.""" 

3 

4import enum 

5from dataclasses import dataclass 

6 

7from pydantic import BaseModel, Field 

8 

9import sensorkit.api as sk 

10from sensorkit.astro.common import RADecPointing 

11from sensorkit.astro.target import Target 

12from sensorkit.std.traits import Deinit, Home, Init, MoveToPark, SetParkPosition, Stop 

13 

14 

15class MountAxis(enum.StrEnum): 

16 """Identifies a physical axis of a telescope mount.""" 

17 

18 ALTITUDE = "altitude" 

19 AZIMUTH = "azimuth" 

20 RIGHT_ASCENSION = "right_ascension" 

21 DECLINATION = "declination" 

22 

23 

24class EnableAxis(sk.DeviceCommand): 

25 """Enable motion control for a mount axis.""" 

26 

27 axis: MountAxis 

28 

29 

30class DisableAxis(sk.DeviceCommand): 

31 """Disable motion control for a mount axis.""" 

32 

33 axis: MountAxis 

34 

35 

36@sk.declare_keyword 

37class AxisEnabled(BaseModel): 

38 """Keyword reporting whether a specific mount axis is enabled.""" 

39 

40 enabled: bool 

41 axis: MountAxis 

42 

43 

44@sk.declare_keyword 

45class AxisRate(BaseModel): 

46 """Keyword reporting position, velocity, and acceleration for a single mount axis.""" 

47 

48 axis: MountAxis 

49 

50 mechanical_position: float | None = Field(None, description="Current Mechanical position (degrees)") 

51 min_mechanical_position: float | None = Field(None, description="Maximum mechanical position (degrees)") 

52 max_mechanical_position: float | None = Field(None, description="Maximum mechanical position (degrees)") 

53 

54 velocity: float | None = Field(None, description="Current Velocity (Degrees per Second)") 

55 max_velocity: float | None = Field(None, description="Max Velocity (Degrees per Second)") 

56 

57 acceleration: float | None = Field(None, description="Current Acceleration (Degrees per Second ^2)") 

58 max_acceleration: float | None = Field(None, description="Max Acceleration (Degrees per Second ^2)") 

59 

60 measured_current: float | None = Field(None, description="Measured current (Amps)") 

61 

62 

63@sk.declare_keyword 

64class AxisRates(BaseModel): 

65 """Aggregated AxisRate data for all mount axes.""" 

66 

67 azimuth: AxisRate | None = None 

68 altitude: AxisRate | None = None 

69 right_ascension: AxisRate | None = None 

70 declination: AxisRate | None = None 

71 

72 

73@sk.declare_keyword 

74class AxisTargetDistance(BaseModel): 

75 """Distance between current and target axis position, in arcseconds.""" 

76 

77 distance_arcseconds: float 

78 rms_error_arcseconds: float | None = None 

79 axis: MountAxis 

80 

81 

82@sk.declare_keyword 

83class Slewing(BaseModel): 

84 """Indicates whether a mount is slewing.""" 

85 

86 is_slewing: bool 

87 

88 

89@sk.declare_keyword 

90class Tracking(BaseModel): 

91 """Indicates whether a mount is tracking.""" 

92 

93 is_tracking: bool 

94 

95 

96@sk.declare_keyword 

97class MountStatus: 

98 """Mount status keyword.""" 

99 is_moving: bool 

100 target_acquired: bool 

101 target: Target 

102 

103 

104@dataclass 

105class AltAzArcseconds: 

106 """An angular offset expressed in altitude and azimuth arcseconds.""" 

107 

108 azimuth_arcseconds: float 

109 altitude_arcseconds: float 

110 

111 

112@dataclass 

113class RADecArcseconds: 

114 """An angular offset expressed in right-ascension and declination arcseconds.""" 

115 

116 right_ascension_arcseconds: float 

117 declination_arcseconds: float 

118 

119 

120class ApplyOffset(sk.DeviceCommand): 

121 """Apply an angular offset to the mount's current pointing.""" 

122 

123 offset: AltAzArcseconds | RADecArcseconds 

124 

125 

126class FollowTarget(sk.DeviceCommand): 

127 """Slew to a target and continuously track it.""" 

128 

129 target: Target 

130 

131 

132@sk.declare_keyword 

133class AzimuthWrapRange(BaseModel): 

134 """Keyword reporting the configured azimuth wrap range for the mount.""" 

135 

136 min: float 

137 max: float | None = None 

138 

139 

140class SetAzimuthWrapRangeMin(sk.DeviceCommand): 

141 """Set the minimum azimuth of the mount's wrap range.""" 

142 

143 min: float 

144 

145 

146class ModelAddPoint(sk.DeviceCommand): 

147 """Add a calibration point to the mount's pointing model.""" 

148 

149 point: RADecPointing 

150 

151 

152class ModelDeletePoint(sk.DeviceCommand): 

153 """Delete pointing-model points by index.""" 

154 

155 indexes: list[int] 

156 

157 

158class ModelEnablePoint(sk.DeviceCommand): 

159 """Enable pointing-model points by index.""" 

160 

161 indexes: list[int] 

162 

163 

164class ModelDisablePoint(sk.DeviceCommand): 

165 """Disable pointing-model points by index.""" 

166 

167 indexes: list[int] 

168 

169 

170class ModelClearPoints(sk.DeviceCommand): 

171 """Remove all points from the mount's pointing model.""" 

172 

173 

174class ModelSave(sk.DeviceCommand): 

175 """Save the mount's pointing model to a named file.""" 

176 

177 filename: str 

178 

179 

180class ModelLoad(sk.DeviceCommand): 

181 """Load a mount pointing model from a named file.""" 

182 

183 filename: str 

184 

185 

186HasPointingModel = sk.declare_trait( 

187 "HasPointingModel", 

188 required_commands=( 

189 ModelAddPoint, 

190 ModelDeletePoint, 

191 ModelEnablePoint, 

192 ModelDisablePoint, 

193 ModelClearPoints, 

194 ModelSave, 

195 ModelLoad, 

196 ), 

197) 

198"""A mount that maintains a persistent pointing model.""" 

199 

200 

201StandardMount = sk.declare_archetype( 

202 "mount", 

203 required_commands=( 

204 Init, 

205 Deinit, 

206 MoveToPark, 

207 SetParkPosition, 

208 Stop, 

209 Home, 

210 FollowTarget, 

211 ), 

212 optional_commands=( 

213 EnableAxis, 

214 DisableAxis, 

215 ApplyOffset, 

216 ), 

217) 

218"""A telescope mount that can follow targets and park."""