Skip to content

Commit 880bd51

Browse files
author
shakuevda
committed
update_init
1 parent 203bde2 commit 880bd51

File tree

1 file changed

+149
-158
lines changed

1 file changed

+149
-158
lines changed

src/stingray_core_control/stingray_core_control/stingray_core_control_node.py

Lines changed: 149 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -36,114 +36,141 @@
3636
class StingrayCoreControlNode(Node):
3737
def __init__(self):
3838
super().__init__('stingray_core_control_node')
39+
self._init_node_params()
40+
self._init_topics()
41+
self._init_axes_and_thrusters()
42+
self._init_mixer()
43+
self._init_controllers()
44+
self._init_state()
45+
self._init_axis_controllers()
46+
self._init_subscriptions()
47+
self._init_publishers()
48+
self._init_param_callbacks()
49+
self._init_timer()
3950

40-
# --- параметры ---
51+
self.get_logger().info(
52+
f"Started at {self.rate_hz} Hz | axes={self.axes} | thrusters={len(self.thrusters)}"
53+
)
54+
55+
def control_loop(self):
56+
now = time.time()
57+
dt = now - self.last_time if self.last_time is not None else 0.0
58+
self.last_time = now
59+
self.last_dt = dt
60+
61+
# === 1. Определяем управляющие воздействия ===
62+
u: dict[str, float] = {}
63+
64+
for axis in self.axes:
65+
if axis in self.axis_ctrl and self.control.enabled[axis]:
66+
u[axis] = self.axis_ctrl[axis].compute(
67+
target=self.control.impact[axis],
68+
dt=self.last_dt,
69+
)
70+
else:
71+
u[axis] = self.control.impact[axis]
72+
73+
# === 2. Преобразуем в команды thrusters ===
74+
control_list = [u[a] for a in self.axes]
75+
thruster_cmds = self.mixer.mix_from_list(control_list)
76+
77+
# === 3. Публикуем thruster_cmd ===
78+
msg = UInt8MultiArray()
79+
msg.data = thruster_cmds
80+
self.pub_thruster_cmd.publish(msg)
81+
82+
# === 4. Публикуем ориентацию ===
83+
self.pub_yaw.publish(Float64(data=self.imu.yaw))
84+
self.pub_pitch.publish(Float64(data=self.imu.pitch))
85+
self.pub_roll.publish(Float64(data=self.imu.roll))
86+
87+
def normalize_angle_deg(self, angle_deg):
88+
"""Нормализует угол в градусах в диапазон [-180, 180)."""
89+
a = (angle_deg + 180.0) % 360.0 - 180.0
90+
return a
91+
92+
def _init_node_params(self):
4193
self.declare_parameter('rate_hz', 100.0)
42-
self.rate_hz = float(self.get_parameter(
43-
'rate_hz').get_parameter_value().double_value)
44-
94+
self.rate_hz = float(self.get_parameter('rate_hz').value)
95+
4596
self.declare_parameter('flag_setup_feedback_speed', False)
46-
self.flag_setup_feedback_speed = self.get_parameter(
47-
'flag_setup_feedback_speed').get_parameter_value().bool_value
48-
49-
self.declare_parameter('topic_imu_angular', '/vectornav/raw/common')
50-
self.declare_parameter('topic_imu_linear_accel',
51-
'/vectornav/imu_accel')
52-
self.declare_parameter('topic_imu_angular_rate', '/vectornav/imu')
53-
self.declare_parameter('topic_loop_flags', '/control/loop_flags')
54-
self.declare_parameter('topic_pressure_sensor', '/sensors/pressure')
55-
self.declare_parameter('topic_control_data', '/control/data')
56-
self.declare_parameter('vectornav_yaw_offset_deg', 0.0)
57-
58-
self.topic_imu_angular = self.get_parameter(
59-
'topic_imu_angular').get_parameter_value().string_value
60-
self.topic_imu_linear_accel = self.get_parameter(
61-
'topic_imu_linear_accel').get_parameter_value().string_value
62-
self.topic_imu_angular_rate = self.get_parameter(
63-
'topic_imu_angular_rate').get_parameter_value().string_value
64-
self.topic_loop_flags = self.get_parameter(
65-
'topic_loop_flags').get_parameter_value().string_value
66-
self.topic_pressure_sensor = self.get_parameter(
67-
'topic_pressure_sensor').get_parameter_value().string_value
68-
self.topic_control_data = self.get_parameter(
69-
'topic_control_data').get_parameter_value().string_value
70-
self.vectornav_yaw_offset = float(self.get_parameter(
71-
'vectornav_yaw_offset_deg').get_parameter_value().double_value)
72-
73-
# --- yaw zeroing ---
74-
self.yaw_zero_offset = 0.0
75-
self.imu_yaw_raw = 0.0
97+
self.flag_setup_feedback_speed = bool(self.get_parameter('flag_setup_feedback_speed').value)
7698

77-
self.declare_parameter('topic_zero_yaw', '/imu/zero_yaw')
78-
self.topic_zero_yaw = self.get_parameter(
79-
'topic_zero_yaw').get_parameter_value().string_value
99+
self.declare_parameter('debug_publish', False)
80100

81-
self.sub_zero_yaw = self.create_subscription(
82-
Bool, self.topic_zero_yaw, self.zero_yaw_callback, 10)
83101

84-
85-
if not self.has_parameter('thrusters'):
86-
self.declare_parameter('thrusters', ["",""])
102+
def _init_topics(self):
103+
defaults = {
104+
'topic_imu_angular': '/vectornav/raw/common',
105+
'topic_imu_linear_accel': '/vectornav/imu_accel',
106+
'topic_imu_angular_rate': '/vectornav/imu',
107+
'topic_loop_flags': '/control/loop_flags',
108+
'topic_pressure_sensor': '/sensors/pressure',
109+
'topic_control_data': '/control/data',
110+
'topic_zero_yaw': '/imu/zero_yaw',
111+
}
87112

88-
self.thrusters = list(self.get_parameter('thrusters').get_parameter_value().string_array_value)
89-
113+
for name, default in defaults.items():
114+
self.declare_parameter(name, default)
115+
setattr(self, name, self.get_parameter(name).value)
116+
117+
def _init_axes_and_thrusters(self):
90118
self.declare_parameter('axes', ["surge", "sway", "heave", "roll", "pitch", "yaw"])
91-
self.axes = list(self.get_parameter('axes').get_parameter_value().string_array_value)
119+
self.axes = list(self.get_parameter('axes').value)
92120

121+
self.declare_parameter('thrusters', [])
122+
self.thrusters = list(self.get_parameter('thrusters').value)
123+
124+
def _init_mixer(self):
93125
coeffs = {}
94-
for t in self.thrusters:
95-
row = []
96-
for a in self.axes:
97-
param_name = f"{t}_{a}"
98-
if not self.has_parameter(param_name):
99-
self.declare_parameter(param_name, 0)
100-
val = float(self.get_parameter(param_name).value)
101-
row.append(val)
102-
coeffs[t] = row
103-
104-
# create pure mixer
105-
self.mixer = ThrusterMixer(self.thrusters, self.axes, coeffs)
106-
self.get_logger().info(f"ThrusterMixer initialized: thrusters={self.thrusters}, axes={self.axes}, coeffs= {coeffs}")
107126

108-
# subscribe to parameter changes to update coeffs dynamically
127+
for thr in self.thrusters:
128+
row = []
129+
for axis in self.axes:
130+
pname = f"{thr}_{axis}"
131+
if not self.has_parameter(pname):
132+
self.declare_parameter(pname, 0.0)
133+
row.append(float(self.get_parameter(pname).value))
134+
coeffs[thr] = row
109135

110-
self.controllers = {}
136+
self.mixer = ThrusterMixer(self.thrusters, self.axes, coeffs)
111137

138+
def _init_controllers(self):
112139
axis_class_map = {
113140
'yaw': YawController,
114141
'pitch': PitchController,
115142
'roll': RollController,
116143
'heave': DepthController,
117144
'surge': SurgeController,
118-
'sway': SwayController
145+
'sway': SwayController,
119146
}
120147

121-
148+
self.controllers = {}
122149
self.param_keys = ["K_p", "K_1", "K_2", "K_i", "I_min", "I_max", "out_sat", "ap_K", "ap_T"]
123150

124-
# внутри ноды
125151
for axis in self.axes:
126-
# Получаем список ключей для оси
127-
self.declare_parameter(axis, ["K_p", "K_1", "K_2", "K_i", "I_min", "I_max", "out_sat", "ap_K", "ap_T"])
128-
129-
# Загружаем параметры
130-
params_dict = {}
152+
params = {}
131153
for key in self.param_keys:
132-
param_name = f'controllers.{axis}.{key}'
133-
self.declare_parameter(param_name, 0.0)
134-
params_dict[key] = self.get_parameter(param_name).value
135-
136-
# Инициализируем контроллер конкретного класса
137-
ctrl_class = axis_class_map[axis] # всегда берём нужный класс
138-
self.controllers[axis] = ctrl_class(**params_dict)
154+
pname = f"controllers.{axis}.{key}"
155+
self.declare_parameter(pname, 0.0)
156+
params[key] = float(self.get_parameter(pname).value)
139157

158+
self.controllers[axis] = axis_class_map[axis](**params)
159+
# self.controllers["yaw"].set_debug_hook(self.debug_cb)
160+
# self.controllers["heave"].set_debug_hook(self.debug_cb)
140161

141-
self.get_logger().info(f"Controllers initialized: {self.controllers}")
142162

163+
def _init_state(self):
143164
self.imu = ImuState()
144165
self.control = ControlState.from_axes(self.axes)
145166

146-
# --- Axis controllers (angular / linear split) ---
167+
self.depth = 0.0
168+
169+
# yaw zeroing
170+
self.yaw_zero_offset = 0.0
171+
self.imu_yaw_raw = 0.0
172+
173+
def _init_axis_controllers(self):
147174
self.axis_ctrl: dict[str, AxisController] = {
148175
"yaw": AngularAxisController(
149176
controller=self.controllers["yaw"],
@@ -160,114 +187,78 @@ def __init__(self):
160187
"heave": LinearAxisController(
161188
controller=self.controllers["heave"],
162189
pos_fn=lambda: self.depth,
163-
vel_fn=lambda: 0.0, # пока нет оценки вертикальной скорости
190+
vel_fn=lambda: 0.0,
164191
accel_fn=lambda: 0.0,
165192
feedback_flag_fn=lambda: self.flag_setup_feedback_speed,
166193
),
167194
}
168195

196+
# fallback для остальных осей
197+
# for axis in self.axes:
198+
# if axis not in self.axis_ctrl:
199+
# self.axis_ctrl[axis] = PassthroughAxisController()
200+
201+
def _init_subscriptions(self):
169202
qos = QoSProfile(depth=10)
170203

171-
# --- подписки ---
172204
self.sub_imu_angular = self.create_subscription(
173-
CommonGroup, "/vectornav/raw/common", self.imu_angular_callback, qos_profile_sensor_data)
205+
CommonGroup, self.topic_imu_angular,
206+
self.imu_angular_callback, qos_profile_sensor_data
207+
)
208+
174209
self.sub_imu_linear_accel = self.create_subscription(
175-
Vector3, self.topic_imu_linear_accel, self.imu_linear_accel_callback, qos)
176-
self.sub_imu_angular_rate = self.create_subscription(
177-
Imu, self.topic_imu_angular_rate, self.imu_angular_rate_callback, qos)
178-
179-
# --- my orientation publishers ---
180-
self.pub_yaw = self.create_publisher(Float64, '~/orientation/yaw', 10)
181-
self.pub_pitch = self.create_publisher(Float64, '~/orientation/pitch', 10)
182-
self.pub_roll = self.create_publisher(Float64, '~/orientation/roll', 10)
210+
Vector3, self.topic_imu_linear_accel,
211+
self.imu_linear_accel_callback, qos
212+
)
183213

214+
self.sub_imu_angular_rate = self.create_subscription(
215+
Imu, self.topic_imu_angular_rate,
216+
self.imu_angular_rate_callback, qos
217+
)
184218

185-
# loop flags — используем UInt8 (битфлаги)
186219
self.sub_control_mode_flags = self.create_subscription(
187-
UInt8, self.topic_loop_flags, self.control_mode_flags_callback, qos)
220+
UInt8, self.topic_loop_flags,
221+
self.control_mode_flags_callback, qos
222+
)
188223

189-
# pressure: обычно Float32
190224
self.sub_pressure_sensor = self.create_subscription(
191-
Float32, self.topic_pressure_sensor, self.pressure_sensor_callback, qos)
225+
Float32, self.topic_pressure_sensor,
226+
self.pressure_sensor_callback, qos
227+
)
192228

193-
# control data (входные "impact" команды)
194229
self.sub_control_data = self.create_subscription(
195-
Twist, self.topic_control_data, self.control_data_callback, qos)
230+
Twist, self.topic_control_data,
231+
self.control_data_callback, qos
232+
)
233+
234+
self.sub_zero_yaw = self.create_subscription(
235+
Bool, self.topic_zero_yaw,
236+
self.zero_yaw_callback, 10
237+
)
196238

197-
# паблишер
239+
def _init_publishers(self):
198240
self.pub_thruster_cmd = self.create_publisher(
199-
UInt8MultiArray, '/thruster/cmd', qos)
241+
UInt8MultiArray, '/thruster/cmd', 10
242+
)
200243

201-
self.depth = 0.0
202-
self.yaw_ctrl = self.controllers["yaw"]
203-
self.depth_ctrl = self.controllers["heave"]
204-
self.pitch_ctrl = self.controllers["pitch"]
244+
self.pub_yaw = self.create_publisher(Float64, '~/orientation/yaw', 10)
245+
self.pub_pitch = self.create_publisher(Float64, '~/orientation/pitch', 10)
246+
self.pub_roll = self.create_publisher(Float64, '~/orientation/roll', 10)
205247

206-
self.declare_parameter('debug_publish', False)
207-
self.pub_err_position = self.create_publisher(Float64, "~/debug/err_position", 10)
208-
self.pub_output_pi = self.create_publisher(Float64, "~/debug/output_pi", 10)
248+
self.pub_err_position = self.create_publisher(Float64, "~/debug/err_position", 10)
249+
self.pub_output_pi = self.create_publisher(Float64, "~/debug/output_pi", 10)
209250
self.pub_feedback_speed = self.create_publisher(Float64, "~/debug/feedback_speed", 10)
210-
self.pub_measurement_rate = self.create_publisher(Float64, "~/debug/measurement_rate", 10)
211-
self.pub_out = self.create_publisher(Float64, "~/debug/out", 10)
212-
self.yaw_ctrl.set_debug_hook(self.debug_cb)
213-
self.depth_ctrl.set_debug_hook(self.debug_cb)
214-
215-
251+
self.pub_measurement_rate = self.create_publisher(Float64, "~/debug/measurement_rate", 10)
252+
self.pub_out = self.create_publisher(Float64, "~/debug/out", 10)
253+
254+
def _init_param_callbacks(self):
216255
self.add_on_set_parameters_callback(self._on_params_changed)
217256

218-
# таймер
257+
def _init_timer(self):
219258
self.last_time = time.time()
220259
self.timer = self.create_timer(1.0 / self.rate_hz, self.control_loop)
221260

222-
self._last_status_log = 0.0
223-
self._status_log_interval = 1.0 # сек
224-
225-
self.get_logger().info(
226-
f"StingrayCoreControlNode started at {self.rate_hz} Hz")
227-
228-
def normalize_angle_deg(self, angle_deg):
229-
"""Нормализует угол в градусах в диапазон [-180, 180)."""
230-
a = (angle_deg + 180.0) % 360.0 - 180.0
231-
return a
232-
233-
def control_loop(self):
234-
now = time.time()
235-
dt = now - self.last_time if self.last_time is not None else 0.0
236-
self.last_time = now
237-
self.last_dt = dt
238-
239-
# === 1. Определяем управляющие воздействия ===
240-
u: dict[str, float] = {}
241-
242-
for axis in self.axes:
243-
if axis in self.axis_ctrl and self.control.enabled[axis]:
244-
u[axis] = self.axis_ctrl[axis].compute(
245-
target=self.control.impact[axis],
246-
dt=self.last_dt,
247-
)
248-
else:
249-
u[axis] = self.control.impact[axis]
250-
251-
# === 2. Преобразуем в команды thrusters ===
252-
control_list = [u[a] for a in self.axes]
253-
thruster_cmds = self.mixer.mix_from_list(control_list)
254-
255-
# === 3. Публикуем UInt8MultiArray ===
256-
msg = UInt8MultiArray()
257-
msg.data = thruster_cmds
258-
self.pub_thruster_cmd.publish(msg)
259-
260-
# === 4. Публикуем ориентацию ===
261-
self.pub_yaw.publish(Float64(data=self.imu.yaw))
262-
self.pub_pitch.publish(Float64(data=self.imu.pitch))
263-
self.pub_roll.publish(Float64(data=self.imu.roll))
264-
265261

266-
# Логируем статус 1 раз в секунду, а не каждый тик
267-
if now - self._last_status_log > self._status_log_interval:
268-
# self.get_logger().info(f"Thrusters command: {thruster_cmds}")
269-
# self.get_logger().debug(f"Control loop tick: dt={dt:.6f} s")
270-
self._last_status_log = now
271262

272263
# --- Колбэки ---
273264
def imu_angular_callback(self, msg: CommonGroup):

0 commit comments

Comments
 (0)