agrotechsimapi 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,695 @@
1
+ from inavmspapi import MultirotorControl
2
+ from inavmspapi.transmitter import TCPTransmitter
3
+ from inavmspapi.msp_codes import MSPCodes
4
+ from agrotechsimapi.client import SimClient
5
+
6
+ from agrotechsimapi.pid import PID
7
+ from typing import Iterable
8
+
9
+ from agrotechsimapi.utils.utils import LoopingTimer, sim_to_api_distance, vel_to_rc_signal
10
+ from agrotechsimapi.utils.vision import process_aruco, process_blob, resolution_changes
11
+
12
+ from transforms3d.euler import quat2euler
13
+
14
+ import time
15
+ import math
16
+ import threading # Добавлен импорт
17
+
18
+
19
+ def main():
20
+ pass
21
+
22
+
23
+ if __name__ == "__main__":
24
+ main()
25
+
26
+
27
+ class HighLevelSimClient:
28
+ def __init__(self):
29
+
30
+ self.__alt_pid = PID(2.5, 0.015, 7.5)
31
+
32
+ self._odom = (0.0, 0.0)
33
+
34
+ self._altitude = 0.0
35
+
36
+ self._target_height = 0.0
37
+
38
+ self._armed_flag = False
39
+ self._poshold_flag = False
40
+
41
+ self._sim_img = None
42
+ self._blob_img = None
43
+ self._aruco_img = None
44
+ self._aruco_data = []
45
+ self._camera_pose_aruco_data = []
46
+ self._blob_data = []
47
+
48
+ self._odom0_xy = (0.0, 0.0) # (x0, y0) в мировой СК на момент «сброса одометрии»
49
+
50
+ self._sim_kinematics = None
51
+
52
+ self._sim_ultrasonic = None
53
+
54
+ # Блокировка для безопасного доступа к клиенту из нескольких потоков
55
+ self._client_lock = threading.Lock()
56
+
57
+ print("process started")
58
+
59
+ def connect(self, ip, port):
60
+ self.__HOST = ip
61
+ self.__SIM_PORT = 8080
62
+ self.__TCP_PORT = 5762
63
+ self.__TCP_ADDRESS = (self.__HOST, self.__TCP_PORT)
64
+
65
+
66
+
67
+ self.__tcp_transmitter = TCPTransmitter(self.__TCP_ADDRESS)
68
+ self.__tcp_transmitter.connect()
69
+ self._control = MultirotorControl(self.__tcp_transmitter)
70
+ time.sleep(2)
71
+
72
+ # Инициализируем клиент с блокировкой
73
+ with self._client_lock:
74
+ self._client = SimClient(address=self.__HOST, port=self.__SIM_PORT)
75
+
76
+ self._height_timer = LoopingTimer(interval=1/50, callback=self.calculate_height_rc, name="height_timer")
77
+ self._rc_timer = LoopingTimer(interval=1/50, callback=self.transmit_rc_to_sim, name="rc_timer")
78
+ self._sim_kinematics_timer = LoopingTimer(interval=1/50, callback=self.sim_kinematics_callback, name="sim_kinematics_timer")
79
+ self._image_processing_timer = LoopingTimer(interval=1/10, callback=self.image_processing_callback, name="image_processing_timer") #new
80
+
81
+ self.sim_kinematics_callback()
82
+
83
+ self.initDrone()
84
+
85
+ self._sim_kinematics_timer.start()
86
+ self._height_timer.start()
87
+ self._rc_timer.start()
88
+ self._image_processing_timer.start()
89
+
90
+ time.sleep(1)
91
+ self._armed_flag = True
92
+ self._arm_data = 2000
93
+ time.sleep(1)
94
+ self._poshold_flag = True
95
+ self._nav_mode = 1500
96
+
97
+
98
+ def disconnect(self):
99
+ self._sim_kinematics_timer.stop()
100
+ self._height_timer.stop()
101
+ self._rc_timer.stop()
102
+ self._image_processing_timer.stop() #new
103
+
104
+
105
+ def _world_xy(self, kin=None) -> tuple[float, float]:
106
+ """Мировые координаты XY в твоей API-конвенции."""
107
+ if kin is None:
108
+ kin = self.get_sim_kinematics()
109
+ x_w = sim_to_api_distance(kin["location"][0])
110
+ y_w = sim_to_api_distance(kin["location"][1])
111
+ return x_w, y_w
112
+
113
+ def _odom_xy(self, kin=None) -> tuple[float, float]:
114
+ """Одометрия (XY), отн. сохранённого нуля. Если ноль не задан — возвращаем мировые XY."""
115
+ x_w, y_w = self._world_xy(kin)
116
+ if self._odom0_xy == (0.0, 0.0):
117
+ return x_w, y_w
118
+ x0, y0 = self._odom0_xy
119
+ return x_w - x0, y_w - y0
120
+
121
+ def getHeightRange(self):
122
+ return self._altitude
123
+
124
+ def getHeightBarometer(self):
125
+ return self._altitude
126
+
127
+ def getArm(self):
128
+ return self._armed_flag
129
+
130
+ def setZeroOdomOpticflow(self) -> bool:
131
+ """
132
+ Запомнить текущие мировые координаты (x0,y0) как ноль одометрии.
133
+ Дальше frame='odom' трактуется как координаты относительно этого нуля.
134
+ """
135
+ kin = self.get_sim_kinematics()
136
+ if kin is None:
137
+ raise RuntimeError("Нет кэша кинематики: setZeroOdomOpticflow() вызван слишком рано")
138
+ self._odom0_xy = self._world_xy(kin)
139
+ return True
140
+
141
+ def getUltrasonic(self):
142
+ return self._sim_ultrasonic
143
+
144
+
145
+
146
+ def calculate_height_rc(self):
147
+ self._altitude = sim_to_api_distance(self.get_sim_kinematics()["location"][2])
148
+
149
+ error = self._target_height - self._altitude
150
+ # print(f"target height: {self._target_height}, error: {error}")
151
+
152
+ self.__alt_pid.update_control(error)
153
+ alt_rc_output = self.__alt_pid.get_control()
154
+
155
+ throttle_output = self.clamp_rc(1500 + alt_rc_output * 100)
156
+
157
+ if self._armed_flag and self._poshold_flag:
158
+ self._throttle_data = throttle_output
159
+
160
+ def getRPY(self):
161
+ kin = self.get_sim_kinematics()
162
+ qx, qy, qz, qw = kin["orientation"]
163
+ roll, pitch, yaw = quat2euler((qw, qx, qy, qz), axes='sxyz')
164
+ return [roll, pitch, yaw]
165
+
166
+ def go_to_xy(self, frame: str, x: float, y: float) -> bool:
167
+ """
168
+ Блокирующий полёт в точку.
169
+ frame: 'odom'|'map' (абсолют) или 'base_link'|'body' (относительный сдвиг в корпусной СК: x вперёд, y влево).
170
+ Возвращает True (достиг) / False (таймаут).
171
+ """
172
+ # ---- алиасы кадров ----
173
+ if frame == "odom":
174
+ use_body_shift = False
175
+ use_odom_frame = True
176
+ elif frame == "base_link":
177
+ use_body_shift = True
178
+ use_odom_frame = False
179
+ else:
180
+ raise ValueError("frame must be 'odom' or 'base_link'")
181
+
182
+ # ===== ХАРДКОД НАСТРОЕК =====
183
+ PERIOD = 1/50.0 # цикл управления, c
184
+ X_THR = 0.2 # допуск по оси вперёд (м)
185
+ Y_THR = 0.2 # допуск по оси влево (м)
186
+ RC_PER_MPS = 100.0 # м/с -> PWM вокруг 1500
187
+ VMAX_AXIS = 0.3 # ограничение скорости на ось, м/с
188
+
189
+ K_LIMIT = 0.5
190
+
191
+ T_MIN = 1.0 # минимум таймаута, c
192
+ T_MAX = 30.0 # максимум таймаута, c
193
+ # PID-коэффициенты по осям (скорости в м/с)
194
+ PITCH_KP, PITCH_KI, PITCH_KD = 0.05, 0.0001, 0.1 # вперёд (err_x) 0.05, 0.0002, 0.1
195
+ ROLL_KP, ROLL_KI, ROLL_KD = 0.05, 0.0001, 0.1 # влево (err_y) 0.05, 0.0002, 0.1
196
+
197
+ # PITCH_KP, PITCH_KI, PITCH_KD = 0.15, 0.0, 0.0 # вперёд (err_x)
198
+ # ROLL_KP, ROLL_KI, ROLL_KD = 0.15, 0.0, 0.0 # влево (err_y)
199
+
200
+ FF_PITCH = FF_ROLL = 0.105
201
+
202
+ # PITCH_I_LIMIT = K_LIMIT * VMAX_AXIS / PITCH_KI
203
+ # ROLL_I_LIMIT = K_LIMIT * VMAX_AXIS / ROLL_KI
204
+
205
+ PITCH_I_LIMIT = None
206
+ ROLL_I_LIMIT = None
207
+ # ===========================
208
+
209
+ # --- PIDs ---
210
+ pid_pitch = PID(PITCH_KP, PITCH_KI, PITCH_KD, max_control=VMAX_AXIS, i_limit=PITCH_I_LIMIT)
211
+ pid_roll = PID(ROLL_KP, ROLL_KI, ROLL_KD, max_control=VMAX_AXIS, i_limit=ROLL_I_LIMIT)
212
+ pid_pitch.reset(); pid_roll.reset()
213
+ first_iter = True
214
+
215
+ mono = time.monotonic
216
+ next_t = mono()
217
+
218
+ # --- текущая поза (мировая) ---
219
+ kin = self.get_sim_kinematics()
220
+ cx, cy = self._world_xy(kin)
221
+
222
+ qx, qy, qz, qw = kin["orientation"]
223
+ _, _, yaw = quat2euler((qw, qx, qy, qz), axes='sxyz')
224
+
225
+ # --- цель в МИРОВОЙ СК ---
226
+ if not use_body_shift:
227
+ # odom → world: просто добавляем сохранённый сдвиг
228
+ if use_odom_frame and (self._odom0_xy is not None):
229
+ x0, y0 = self._odom0_xy
230
+ tgt_x, tgt_y = x0 + float(x), y0 + float(y)
231
+ else:
232
+ # если ноль не задан — трактуем как world как есть
233
+ tgt_x, tgt_y = float(x), float(y)
234
+ else:
235
+ # base_link → world (как у тебя было, без изменений)
236
+ tgt_x = cx + math.cos(yaw) * x - math.sin(yaw) * y
237
+ tgt_y = cy + math.sin(yaw) * x + math.cos(yaw) * y
238
+
239
+ # --- стартовая дистанция и таймаут ---
240
+ dx0 = tgt_x - cx
241
+ dy0 = tgt_y - cy # инверсия мирового y, как в файле
242
+ dist0 = math.hypot(dx0, dy0)
243
+ timeout = max(T_MIN, min((dist0 / 0.1) * 2.0, T_MAX))
244
+ deadline = mono() + timeout
245
+
246
+ try:
247
+ while True:
248
+ kin = self.get_sim_kinematics() # кэш!
249
+ x_w = sim_to_api_distance(kin["location"][0])
250
+ y_w = sim_to_api_distance(kin["location"][1])
251
+
252
+ qx, qy, qz, qw = kin["orientation"]
253
+ _, _, yaw = quat2euler((qw, qx, qy, qz), axes='sxyz') # как в файле
254
+ # print(yaw)
255
+ # yaw = -yaw
256
+ # print(x_w, y_w, yaw)
257
+
258
+ # yaw += 1.57
259
+ # print(yaw)
260
+ # print()
261
+
262
+ # --- мировая разность до цели ---
263
+ dx = tgt_x - x_w
264
+ dy = tgt_y - y_w # инвертируем мировой y (как в файле)
265
+
266
+ # print(f"dx: {dx}, dy: {dy}")
267
+
268
+ # --- локальная ошибка (корпусная СК: x вперёд, y влево) ---
269
+ err_x = math.cos(yaw) * dx + math.sin(yaw) * dy
270
+ err_y = -math.sin(yaw) * dx + math.cos(yaw) * dy
271
+
272
+ # print(f"err_x: {err_x}, err_y: {err_y}")
273
+
274
+ # --- условие достижения (по осям) ---
275
+ if abs(err_x) < X_THR and abs(err_y) < Y_THR:
276
+ self._rpy_vel_data = (1500, 1500, 1500)
277
+ return True
278
+
279
+ # --- таймаут ---
280
+ if mono() > deadline:
281
+ self._rpy_vel_data = (1500, 1500, 1500)
282
+ return False
283
+
284
+ sign_pitch = 1
285
+ sign_roll = 1
286
+
287
+ if err_x > 0:
288
+ sign_pitch = 1
289
+ elif err_x < 0:
290
+ sign_pitch = -1
291
+ else:
292
+ sign_pitch = 0
293
+
294
+ if err_y > 0:
295
+ sign_roll = 1
296
+ elif err_y < 0:
297
+ sign_roll = -1
298
+ else:
299
+ sign_roll = 0
300
+
301
+ # print()
302
+ # print(FF_PITCH * sign_roll)
303
+ # print(pid_roll.current_error * pid_roll.kp)
304
+ # print(pid_roll.integral * pid_roll.ki)
305
+ # print(pid_roll.derivative * pid_roll.kd)
306
+ # print(pid_roll.get_control())
307
+
308
+ # --- PID по осям с порогом (как generate_control в файле) ---
309
+ # вперёд (pitch)
310
+ if abs(err_x) > X_THR:
311
+ pid_pitch.update_control(err_x, reset_prev=first_iter)
312
+ v_fwd = max(-VMAX_AXIS, min(FF_PITCH * sign_pitch + pid_pitch.get_control(), VMAX_AXIS))
313
+
314
+ else:
315
+ v_fwd = 0.0
316
+
317
+ # влево (roll)
318
+ if abs(err_y) > Y_THR:
319
+ pid_roll.update_control(err_y, reset_prev=first_iter)
320
+ v_left = -max(-VMAX_AXIS, min(FF_ROLL * sign_roll + pid_roll.get_control(), VMAX_AXIS))
321
+ else:
322
+ v_left = 0.0
323
+
324
+
325
+ first_iter = False
326
+
327
+
328
+
329
+ # --- PWM маппинг (вперёд=+pitch, влево=+roll) ---
330
+
331
+
332
+
333
+
334
+ pitch_pwm = vel_to_rc_signal(v_fwd)
335
+ roll_pwm = vel_to_rc_signal(v_left)
336
+
337
+
338
+
339
+
340
+ # print(f"###########################")
341
+ # print(v_fwd)
342
+ # print(v_left)
343
+ # print()
344
+
345
+ self._rpy_vel_data = (roll_pwm, pitch_pwm, 1500)
346
+
347
+
348
+
349
+ # периодичность
350
+ next_t += PERIOD
351
+ time.sleep(max(0.0, next_t - mono()))
352
+ finally:
353
+ # стоп по XY в любом случае
354
+ self._rpy_vel_data = (1500, 1500, 1500)
355
+
356
+ def getOdomOpticflow(self):
357
+ kin = self.get_sim_kinematics()
358
+ x, y = self._world_xy(kin)
359
+ last_x, last_y = self._odom0_xy
360
+ odom_x = x - last_x
361
+ odom_y = y - last_y
362
+ return [odom_x, odom_y, self._altitude]
363
+
364
+ def gotoXYdrone(self, x, y):
365
+ return self.go_to_xy("base_link", x, y)
366
+
367
+ def gotoXYodom(self, x, y):
368
+ return self.go_to_xy("odom", x, y)
369
+
370
+
371
+ @staticmethod
372
+ def _wrap_pi(a: float) -> float:
373
+ """Нормализация угла в интервал [-pi, pi)."""
374
+ return (a + math.pi) % (2 * math.pi) - math.pi
375
+
376
+ def _get_yaw_cw(self) -> float:
377
+ """
378
+ Текущий курс в РАДИАНАХ с положительным направлением ПО ЧАСОВОЙ.
379
+ IMU/сим даёт yaw CCW -> инвертируем знак.
380
+ """
381
+ kin = self.get_sim_kinematics() # ДОЛЖЕН быть кэш (см. твой sim_kinematics_callback)
382
+ qx, qy, qz, qw = kin["orientation"] # (x,y,z,w)
383
+ _, _, yaw_ccw = quat2euler((qw, qx, qy, qz), axes='sxyz')
384
+ return -yaw_ccw # переводим в CW
385
+
386
+ def setYaw(self, yaw: float) -> bool:
387
+ """
388
+ БЛОКИРУЮЩИЙ поворот до абсолютного угла в ГЛОБАЛЬНОЙ СК.
389
+ Вход: yaw — радианы, ПО ЧАСОВОЙ (CW), нормализуется в [-pi, pi).
390
+ Возврат: True (достиг в допуске) / False (таймаут).
391
+ """
392
+ # ===== жёсткие настройки =====
393
+ PERIOD = 1/20.0 # 20 Гц
394
+ TOL = 0.025 # рад, допуск по углу
395
+ MAX_TIME = 10.0 # сек, таймаут
396
+ MAX_RATE = 0.75 # рад/с, ограничение выхода PID
397
+ KP, KI, KD = 3.0, 0.001, 0.30 # PID по курсу
398
+ I_LIMIT = None # можно поставить число (напр. 600) для ограничения интеграла
399
+ # =============================
400
+
401
+ pid = PID(KP, KI, KD, max_control=MAX_RATE, i_limit=I_LIMIT)
402
+ pid.reset()
403
+
404
+ mono = time.monotonic
405
+ next_t = mono()
406
+ deadline = mono() + MAX_TIME
407
+
408
+ # цель сразу нормализуем в CW
409
+ goal = self._wrap_pi(yaw)
410
+
411
+ try:
412
+ while True:
413
+ curr = self._get_yaw_cw() # текущее CW
414
+ err = self._wrap_pi(goal - curr) # кратчайшая дуга
415
+
416
+ # завершение
417
+ if abs(err) < TOL:
418
+ r, p, _ = self._rpy_vel_data
419
+ self._rpy_vel_data = (r, p, 1500) # отпустить руддер
420
+ return True
421
+ if mono() > deadline:
422
+ r, p, _ = self._rpy_vel_data
423
+ self._rpy_vel_data = (r, p, 1500)
424
+ return False
425
+
426
+ # PID -> угловая скорость (рад/с), внутри уже есть сатурация MAX_RATE
427
+ pid.update_control(err)
428
+ rate_cw = pid.get_control()
429
+
430
+ # рад/с -> PWM (если «вправо» у тебя даёт yaw<1500 — просто поставь минус)
431
+ yaw_pwm = vel_to_rc_signal(rate_cw) # или vel_to_rc_signal(-rate_cw), если знак канала другой
432
+
433
+ # пишем только yaw
434
+ r, p, _ = self._rpy_vel_data
435
+ self._rpy_vel_data = (r, p, yaw_pwm) # оставь именно (r, p, yaw_pwm)
436
+
437
+ next_t += PERIOD
438
+ time.sleep(max(0.0, next_t - mono()))
439
+ finally:
440
+ # гарантированно нейтраль по yaw
441
+ r, p, _ = self._rpy_vel_data
442
+ self._rpy_vel_data = (r, p, 1500)
443
+
444
+ def _wrap(self, a: float) -> float: # на будущее (не обязателен)
445
+ return (a + math.pi) % (2*math.pi) - math.pi
446
+
447
+ def _get_height(self) -> float:
448
+ # Текущая высота из твоего кэша (calculate_height_rc её обновляет)
449
+ return float(self._altitude)
450
+
451
+ def _clamp_h(self, h: float, lo: float, hi: float) -> float:
452
+ return max(lo, min(h, hi))
453
+
454
+ def _sleep_until(self, t_deadline: float, period: float) -> bool:
455
+ # true -> живём; false -> вышел таймаут
456
+ now = time.monotonic()
457
+ if now >= t_deadline:
458
+ return False
459
+ time.sleep(max(0.0, period - (time.monotonic() - now)))
460
+ return True
461
+
462
+ def takeoff(self) -> bool:
463
+ """
464
+ ВЗЛЁТ: мгновенно кидаем целевую высоту takeoff_h и ждём достижения.
465
+ Блокирующий. True/False = достиг/таймаут.
466
+ """
467
+ # ---- жёсткие настройки ----
468
+ PERIOD = 0.05 # 20 Гц
469
+ MIN_H = 0.00
470
+ TAKEOFF_H = 1.00
471
+ MAX_H = 5.00
472
+ REACH_COEF = 0.70 # считаем «достигли», если >= 70% цели
473
+ TIMEOUT_COEF = 10.0 # с/м
474
+ # ---------------------------
475
+
476
+ h_now = self._get_height()
477
+ tgt = self._clamp_h(TAKEOFF_H, MIN_H, MAX_H)
478
+
479
+ # Мгновенно ставим финальный сетпоинт
480
+ self.set_target_height(tgt)
481
+
482
+ # Таймаут пропорционален требуемому подъёму (как в твоём ROS-коде)
483
+ climb = max(0.0, tgt - h_now)
484
+ deadline = time.monotonic() + (TIMEOUT_COEF * climb if climb > 0.0 else TIMEOUT_COEF)
485
+
486
+ while True:
487
+ h = self._get_height()
488
+ if h >= REACH_COEF * tgt:
489
+ return True
490
+ if time.monotonic() >= deadline:
491
+ return False
492
+ if not self._sleep_until(deadline, PERIOD):
493
+ return False
494
+
495
+ def boarding(self) -> bool:
496
+ """
497
+ ПОСАДКА: плавно снижаем целевой сетпоинт до MIN_H лесенкой.
498
+ Блокирующий. True/False = завершил/таймаут.
499
+ """
500
+ # ---- жёсткие настройки ----
501
+ PERIOD = 0.05 # 20 Гц
502
+ MIN_H = 0.00
503
+ MAX_H = 5.00
504
+ STEP = 0.01 # м за шаг сетпоинта
505
+ TIMEOUT_MIN = 15.0 # базовый таймаут
506
+ TIMEOUT_COEF = 10.0 # доп. таймаут пропорционален высоте
507
+ # ---------------------------
508
+
509
+ # Стартуем от текущего командного сетпоинта (если он меньше факта — ок)
510
+ curr_cmd = float(getattr(self, "_target_height", 0.0))
511
+ curr_cmd = self._clamp_h(curr_cmd, MIN_H, MAX_H)
512
+
513
+ total_drop = max(0.0, curr_cmd - MIN_H)
514
+ deadline = time.monotonic() + TIMEOUT_MIN + TIMEOUT_COEF * total_drop
515
+
516
+ while curr_cmd > MIN_H:
517
+ curr_cmd = max(MIN_H, curr_cmd - STEP)
518
+ self.set_target_height(curr_cmd)
519
+ if time.monotonic() >= deadline:
520
+ return False
521
+ if not self._sleep_until(deadline, PERIOD):
522
+ return False
523
+
524
+ # На земле — успех (ждать фактического нуля не обязательно: контур дотянет сам)
525
+ return True
526
+
527
+ def setHeight(self, target_height: float) -> bool:
528
+ """
529
+ Установить высоту.
530
+ - Если цель ВЫШЕ текущей фактической высоты → сразу ставим конечный сетпоинт и ждём достижения.
531
+ - Если цель НИЖЕ текущей фактической высоты → плавно опускаем сетпоинт лесенкой до цели.
532
+ Блокирующий. True/False = достиг/таймаут/пределы.
533
+ """
534
+ # ---- жёсткие настройки ----
535
+ PERIOD = 0.05
536
+ MIN_H = 0.00
537
+ MAX_H = 5.00
538
+ STEP = 0.01
539
+ REACH_COEF = 0.70
540
+ TIMEOUT_MIN = 15.0
541
+ TIMEOUT_COEF = 10.0
542
+ # ---------------------------
543
+
544
+ tgt = self._clamp_h(float(target_height), MIN_H, MAX_H)
545
+ h0 = self._get_height()
546
+
547
+ # ВВЕРХ: сразу кидаем финальный сетпоинт
548
+ if tgt >= h0:
549
+ self.set_target_height(tgt)
550
+ deadline = time.monotonic() + TIMEOUT_MIN + TIMEOUT_COEF * max(0.0, tgt - h0)
551
+ while True:
552
+ h = self._get_height()
553
+ if h >= REACH_COEF * tgt:
554
+ return True
555
+ if time.monotonic() >= deadline:
556
+ return False
557
+ if not self._sleep_until(deadline, PERIOD):
558
+ return False
559
+
560
+ # ВНИЗ: плавная лесенка от текущего командного
561
+ curr_cmd = float(getattr(self, "_target_height", h0))
562
+ curr_cmd = self._clamp_h(curr_cmd, MIN_H, MAX_H)
563
+ deadline = time.monotonic() + TIMEOUT_MIN + TIMEOUT_COEF * max(0.0, curr_cmd - tgt)
564
+
565
+ while curr_cmd > tgt:
566
+ curr_cmd = max(tgt, curr_cmd - STEP)
567
+ self.set_target_height(curr_cmd)
568
+ if time.monotonic() >= deadline:
569
+ return False
570
+ if not self._sleep_until(deadline, PERIOD):
571
+ return False
572
+
573
+ return True
574
+
575
+
576
+ def sim_kinematics_callback(self):
577
+ # Используем блокировку при обращении к клиенту
578
+ with self._client_lock:
579
+ self._sim_kinematics = self._client.get_kinametics_data()
580
+ self._sim_img = self._client.get_camera_capture(camera_id=1) #выбор камеры 0-передняя 1-нижняя
581
+ self._sim_ultrasonic = self._client.get_range_data(rangefinder_id = 0, range_min = 0.15, range_max = 4, is_clear = True, range_error = 0.0003) * 100
582
+
583
+
584
+ def get_sim_kinematics(self):
585
+ return self._sim_kinematics
586
+
587
+
588
+ def transmit_rc_to_sim(self):
589
+ roll, pitch, yaw = self._rpy_vel_data
590
+ raw_rc = [roll, pitch, self._throttle_data, yaw, self._arm_data, self._fliyng_mode, self._nav_mode]
591
+ raw_rc = self.clamp_rc_list(raw_rc)
592
+ msg = self._control.send_RAW_RC(raw_rc)
593
+ data_handler = self._control.receive_msg()
594
+
595
+ def setVelXY(self, x, y):
596
+ rpy = self._rpy_vel_data
597
+ rc_x = vel_to_rc_signal(x)
598
+ rc_y = vel_to_rc_signal(y)
599
+ self._rpy_vel_data = (rc_y, rc_x, rpy[2])
600
+
601
+ def setVelXYYaw(self, x, y, yaw):
602
+ rc_x = vel_to_rc_signal(x)
603
+ rc_y = vel_to_rc_signal(y)
604
+ rc_yaw = vel_to_rc_signal(yaw)
605
+ self._rpy_vel_data = (rc_y, rc_x, rc_yaw)
606
+
607
+ def armDrone(self):
608
+ self._armed_flag = True
609
+ self._arm_data = 2000
610
+
611
+ def disarmDrone(self):
612
+ self._armed_flag = False
613
+ self._arm_data = 1000
614
+
615
+ def initDrone(self):
616
+ self._rpy_vel_data = (1500, 1500, 1500)
617
+ self._throttle_data = 1000
618
+ self._arm_data = 1000
619
+ self._fliyng_mode = 1000
620
+ self._nav_mode = 1000
621
+
622
+ def posholdOn(self):
623
+ self._poshold_flag = True
624
+ self._nav_mode = 1500
625
+
626
+ def posholdOff(self):
627
+ self._poshold_flag = False
628
+ self._nav_mode = 1000
629
+
630
+ def clamp_rc(self, data):
631
+ return max(min(data, 2000), 1000)
632
+
633
+ def clamp_rc_list(self, data: Iterable):
634
+ return [self.clamp_rc(rc) for rc in data]
635
+
636
+ def round_data(self, iterable):
637
+ return map(lambda x: round(x, 3), iterable)
638
+
639
+ def set_target_height(self, height):
640
+ self._target_height = height
641
+ self.__alt_pid.reset()
642
+
643
+ #new
644
+ def getImage(self):
645
+ return self._sim_img
646
+
647
+ def getArucos(self):
648
+ return self._aruco_data
649
+
650
+ def getCameraPoseAruco(self):
651
+ return self._camera_pose_aruco_data
652
+
653
+ def getBlobs(self):
654
+ return self._blob_data
655
+
656
+ def getBlobsImage(self):
657
+ return self._blob_img
658
+
659
+ def getArucosImage(self):
660
+ return self._aruco_img
661
+
662
+ def image_processing_callback(self):
663
+ # Обработка изображения не требует доступа к клиенту,
664
+ # используем уже полученное изображение из кэша
665
+
666
+ sim_image = self._sim_img.copy() if self._sim_img is not None else None
667
+ camera_img = resolution_changes(sim_image, (320, 240))
668
+
669
+ img_aruco = camera_img.copy() if self._sim_img is not None else None
670
+ img_blob = camera_img.copy() if self._sim_img is not None else None
671
+
672
+ if img_aruco is not None:
673
+ self._aruco_data, self._camera_pose_aruco_data, aruco_img = process_aruco(img_aruco)
674
+
675
+ if aruco_img is None:
676
+ self._aruco_img = sim_image
677
+ else:
678
+ self._aruco_img = resolution_changes(aruco_img, (640, 480))
679
+
680
+
681
+ if img_blob is not None:
682
+ self._blob_data, blob_img = process_blob(img_blob)
683
+ if blob_img is None:
684
+ self._blob_img = sim_image
685
+ else:
686
+ self._blob_img = resolution_changes(blob_img, (640, 480))
687
+
688
+ def setDiod(self, r, g, b):
689
+ # Используем блокировку при обращении к клиенту
690
+ with self._client_lock:
691
+ return self._client.set_Diod(float(r), float(g), float(b))
692
+
693
+ def setShoot(self, time):
694
+ with self._client_lock:
695
+ return self._client.call_event_action()