FiiPython 1.0.0__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.
@@ -0,0 +1,786 @@
1
+ # CaculateState.py (添加相对移动功能)
2
+ import json
3
+ import math
4
+ from typing import Dict, List, Tuple, Optional, Any
5
+ from copy import deepcopy
6
+ import os
7
+
8
+
9
+ class LightEffect:
10
+ """灯光效果基类"""
11
+
12
+ def __init__(self, start_time: int):
13
+ self.start_time = start_time
14
+
15
+ def get_color(self, current_time: int) -> Tuple[int, int, int]:
16
+ return (0, 0, 0)
17
+
18
+
19
+ class BlinkEffect(LightEffect):
20
+ """闪烁效果:亮dur毫秒,灭delay毫秒,循环"""
21
+
22
+ def __init__(self, start_time: int, color: Tuple[int, int, int],
23
+ brightness: float, dur: int, delay: int):
24
+ super().__init__(start_time)
25
+ self.color = color
26
+ self.brightness = brightness
27
+ self.dur = dur
28
+ self.delay = delay
29
+ self.cycle = dur + delay
30
+
31
+ def get_color(self, current_time: int) -> Tuple[int, int, int]:
32
+ time_in_cycle = (current_time - self.start_time) % self.cycle
33
+ if time_in_cycle < self.dur:
34
+ # 亮灯
35
+ return tuple(int(min(255, c * self.brightness)) for c in self.color)
36
+ else:
37
+ # 灭灯
38
+ return (0, 0, 0)
39
+
40
+
41
+ class BreathEffect(LightEffect):
42
+ """呼吸效果:time1内从0变到颜色,time2内从颜色变到0,循环"""
43
+
44
+ def __init__(self, start_time: int, color: Tuple[int, int, int],
45
+ time1: int, time2: int, brightness: float):
46
+ super().__init__(start_time)
47
+ self.color = color
48
+ self.time1 = time1
49
+ self.time2 = time2
50
+ self.brightness = brightness
51
+ self.cycle = time1 + time2
52
+
53
+ def get_color(self, current_time: int) -> Tuple[int, int, int]:
54
+ time_in_cycle = (current_time - self.start_time) % self.cycle
55
+
56
+ if time_in_cycle < self.time1:
57
+ # 亮度上升
58
+ ratio = time_in_cycle / self.time1
59
+ else:
60
+ # 亮度下降
61
+ ratio = 1 - (time_in_cycle - self.time1) / self.time2
62
+
63
+ return tuple(int(min(255, c * self.brightness * ratio)) for c in self.color)
64
+
65
+
66
+ class MotorHorseEffect(LightEffect):
67
+ """跑马灯效果:四个马达依次变色"""
68
+
69
+ def __init__(self, start_time: int, colors: List[Tuple[int, int, int]],
70
+ clock: bool, delay: int):
71
+ super().__init__(start_time)
72
+ self.colors = colors
73
+ self.clock = clock # True:顺时针, False:逆时针
74
+ self.delay = delay
75
+ self.num_colors = len(colors)
76
+
77
+ def get_motor_colors(self, current_time: int) -> Dict[int, Tuple[int, int, int]]:
78
+ time_offset = (current_time - self.start_time) // self.delay
79
+ phase = time_offset % self.num_colors
80
+
81
+ result = {}
82
+ if self.clock: # 顺时针: 1->2->3->4
83
+ for i in range(4):
84
+ color_index = (phase + i) % self.num_colors
85
+ result[i + 1] = self.colors[color_index]
86
+ else: # 逆时针: 4->3->2->1
87
+ for i in range(4):
88
+ color_index = (phase + (3 - i)) % self.num_colors
89
+ result[i + 1] = self.colors[color_index]
90
+
91
+ return result
92
+
93
+
94
+ class MovementState:
95
+ """运动状态 - 单位:厘米(cm)"""
96
+
97
+ def __init__(self, takeoff_pos: List[float]):
98
+ """
99
+ :param takeoff_pos: 起飞点坐标 [x, y] (cm)
100
+ """
101
+ self.takeoff_pos = takeoff_pos # 起飞点
102
+ self.pos = [takeoff_pos[0], takeoff_pos[1], 0.0] # 当前位置
103
+ self.target_pos = [takeoff_pos[0], takeoff_pos[1], 0.0] # 目标位置
104
+
105
+ # 默认速度 [速度(cm/s), 加速度(cm/s²)]
106
+ self.xy_speed = [100.0, 100.0] # [速度, 加速度]
107
+ self.z_speed = [100.0, 100.0] # [速度, 加速度]
108
+
109
+ self.is_flying = False # 是否在飞行中
110
+
111
+ # 运动状态
112
+ self.start_move_time = 0 # 开始移动的时间(ms)
113
+ self.start_pos = [takeoff_pos[0], takeoff_pos[1], 0.0] # 开始移动的位置
114
+
115
+ # 当前运动的速度 (cm/s) - 用于连续运动
116
+ self.current_vx = 0.0
117
+ self.current_vy = 0.0
118
+ self.current_vz = 0.0
119
+
120
+ # 运动参数 - 简化为每个轴的目标速度和加速度
121
+ self.target_vx = 0.0
122
+ self.target_vy = 0.0
123
+ self.target_vz = 0.0
124
+ self.accel_x = 0.0
125
+ self.accel_y = 0.0
126
+ self.accel_z = 0.0
127
+
128
+ self.estimated_duration = 0 # 预计移动总时间 (s)
129
+ self.estimated_end_time = 0 # 预计结束时间 (ms)
130
+ self.last_command_time = 0 # 最后指令时间
131
+
132
+ # 运动标志
133
+ self.is_moving = False
134
+
135
+ def _get_current_velocity(self) -> Tuple[float, float, float]:
136
+ """获取当前时刻的实际速度"""
137
+ return (self.current_vx, self.current_vy, self.current_vz)
138
+
139
+ def takeoff(self, height: float, current_time: int):
140
+ """起飞到指定高度"""
141
+ self.is_flying = True
142
+ self.is_moving = True
143
+
144
+ # 记录开始运动时的状态
145
+ self.start_move_time = current_time
146
+ self.start_pos = self.pos.copy()
147
+ self.target_pos = [self.takeoff_pos[0], self.takeoff_pos[1], height]
148
+
149
+ # 设置运动参数
150
+ distance = height - self.pos[2]
151
+ direction = 1 if distance > 0 else -1
152
+
153
+ # 目标速度(最大速度)
154
+ self.target_vz = self.z_speed[0] * direction
155
+ # 加速度
156
+ self.accel_z = self.z_speed[1] * direction
157
+
158
+ # 估算时间
159
+ abs_distance = abs(distance)
160
+ v_max = self.z_speed[0]
161
+ a = self.z_speed[1]
162
+
163
+ if a > 0:
164
+ # 加速到最大速度所需距离
165
+ accel_dist = v_max * v_max / (2 * a)
166
+ if abs_distance <= 2 * accel_dist:
167
+ # 三角形速度曲线
168
+ v_peak = math.sqrt(a * abs_distance)
169
+ self.estimated_duration = 2 * v_peak / a
170
+ else:
171
+ # 梯形速度曲线
172
+ accel_time = v_max / a
173
+ const_dist = abs_distance - 2 * accel_dist
174
+ const_time = const_dist / v_max
175
+ self.estimated_duration = 2 * accel_time + const_time
176
+ else:
177
+ self.estimated_duration = abs_distance / v_max if v_max > 0 else 0
178
+
179
+ self.estimated_end_time = current_time + int(self.estimated_duration * 1000)
180
+
181
+ print(f" 起飞: 从{self.start_pos}到{self.target_pos}, 需要{self.estimated_duration:.2f}s")
182
+
183
+ def land(self, current_time: int):
184
+ """降落 - 有运动过程"""
185
+ if self.pos[2] <= 0:
186
+ print(f" 已经在 ground")
187
+ return
188
+
189
+ self.is_moving = True
190
+ self.start_move_time = current_time
191
+ self.start_pos = self.pos.copy()
192
+ self.target_pos = [self.pos[0], self.pos[1], 0.0]
193
+
194
+ # 设置运动参数(减速下降)
195
+ distance = -self.pos[2] # 负数
196
+ direction = -1 # 向下
197
+
198
+ # 目标速度(负值表示向下)
199
+ self.target_vz = -self.z_speed[0]
200
+ # 加速度(负值表示减速)
201
+ self.accel_z = -self.z_speed[1]
202
+
203
+ # 估算时间
204
+ abs_distance = self.pos[2]
205
+ v_max = self.z_speed[0]
206
+ a = self.z_speed[1]
207
+
208
+ if a > 0:
209
+ # 减速到0所需距离
210
+ decel_dist = v_max * v_max / (2 * a)
211
+ if abs_distance <= decel_dist:
212
+ # 直接减速
213
+ self.estimated_duration = v_max / a
214
+ else:
215
+ # 匀速然后减速
216
+ const_dist = abs_distance - decel_dist
217
+ const_time = const_dist / v_max
218
+ decel_time = v_max / a
219
+ self.estimated_duration = const_time + decel_time
220
+ else:
221
+ self.estimated_duration = abs_distance / v_max if v_max > 0 else 0
222
+
223
+ self.estimated_end_time = current_time + int(self.estimated_duration * 1000)
224
+
225
+ print(f" 降落: 从高度{self.pos[2]:.1f}cm到地面, 需要{self.estimated_duration:.2f}s")
226
+
227
+ def move_to(self, target: List[float], current_time: int):
228
+ """移动到目标点(绝对移动)"""
229
+ self.is_moving = True
230
+ self.start_move_time = current_time
231
+ self.start_pos = self.pos.copy()
232
+ self.target_pos = target
233
+
234
+ # 计算各轴位移
235
+ dx = target[0] - self.pos[0]
236
+ dy = target[1] - self.pos[1]
237
+ dz = target[2] - self.pos[2]
238
+
239
+ print(f" 绝对移动: 从{self.start_pos}到{self.target_pos}")
240
+ print(f" 当前速度: vx={self.current_vx:.1f}, vy={self.current_vy:.1f}, vz={self.current_vz:.1f}")
241
+
242
+ # 计算各轴的目标速度和加速度
243
+ # 对于有位移的轴,设置目标速度
244
+ if abs(dx) > 0.001:
245
+ self.target_vx = self.xy_speed[0] if dx > 0 else -self.xy_speed[0]
246
+ self.accel_x = self.xy_speed[1] if dx > 0 else -self.xy_speed[1]
247
+ else:
248
+ self.target_vx = 0
249
+ self.accel_x = 0
250
+
251
+ if abs(dy) > 0.001:
252
+ self.target_vy = self.xy_speed[0] if dy > 0 else -self.xy_speed[0]
253
+ self.accel_y = self.xy_speed[1] if dy > 0 else -self.xy_speed[1]
254
+ else:
255
+ self.target_vy = 0
256
+ self.accel_y = 0
257
+
258
+ if abs(dz) > 0.001:
259
+ self.target_vz = self.z_speed[0] if dz > 0 else -self.z_speed[0]
260
+ self.accel_z = self.z_speed[1] if dz > 0 else -self.z_speed[1]
261
+ else:
262
+ self.target_vz = 0
263
+ self.accel_z = 0
264
+
265
+ # 估算各轴所需时间(简单估算)
266
+ time_x = abs(dx) / self.xy_speed[0] if abs(dx) > 0.001 else 0
267
+ time_y = abs(dy) / self.xy_speed[0] if abs(dy) > 0.001 else 0
268
+ time_z = abs(dz) / self.z_speed[0] if abs(dz) > 0.001 else 0
269
+
270
+ self.estimated_duration = max(time_x, time_y, time_z)
271
+ if self.estimated_duration <= 0:
272
+ self.estimated_duration = 0.001
273
+
274
+ self.estimated_end_time = current_time + int(self.estimated_duration * 1000)
275
+
276
+ print(f" 目标速度: vx={self.target_vx:.1f}, vy={self.target_vy:.1f}, vz={self.target_vz:.1f}")
277
+ print(f" 总时间: {self.estimated_duration:.2f}s")
278
+
279
+ self.last_command_time = current_time
280
+
281
+ def move(self, offset: List[float], current_time: int):
282
+ """
283
+ 相对移动 - 从当前位置移动指定的偏移量
284
+
285
+ Args:
286
+ offset: 相对偏移量 [dx, dy, dz] (整数,可正可负)
287
+ current_time: 当前时间戳
288
+ """
289
+ # 计算目标位置 = 当前位置 + 偏移量
290
+ target = [
291
+ self.pos[0] + offset[0],
292
+ self.pos[1] + offset[1],
293
+ self.pos[2] + offset[2]
294
+ ]
295
+
296
+ print(f" 相对移动: 偏移{offset} -> 目标位置{target}")
297
+
298
+ # 调用绝对移动逻辑
299
+ self.move_to(target, current_time)
300
+
301
+ def calculate_position(self, current_time: int) -> List[float]:
302
+ """
303
+ 根据简化的运动学公式计算当前位置
304
+ 使用速度控制,避免复杂的二次方程
305
+ """
306
+ dt = (current_time - self.start_move_time) / 1000.0
307
+
308
+ # 如果还没开始移动,返回起始位置
309
+ if dt <= 0:
310
+ return self.pos.copy()
311
+
312
+ # 计算运动进度
313
+ progress = min(1.0, dt / self.estimated_duration) if self.estimated_duration > 0 else 1.0
314
+
315
+ # 简单的线性插值(但保留速度信息用于连续运动)
316
+ if progress < 1.0:
317
+ # 位置:起始位置 + 进度 * 位移
318
+ x = self.start_pos[0] + (self.target_pos[0] - self.start_pos[0]) * progress
319
+ y = self.start_pos[1] + (self.target_pos[1] - self.start_pos[1]) * progress
320
+ z = self.start_pos[2] + (self.target_pos[2] - self.start_pos[2]) * progress
321
+
322
+ # 速度:基于目标速度的简单估算
323
+ self.current_vx = self.target_vx * (1 - progress) if abs(self.target_vx) > 0 else 0
324
+ self.current_vy = self.target_vy * (1 - progress) if abs(self.target_vy) > 0 else 0
325
+ self.current_vz = self.target_vz * (1 - progress) if abs(self.target_vz) > 0 else 0
326
+ else:
327
+ # 到达目标
328
+ x, y, z = self.target_pos
329
+ self.current_vx = 0
330
+ self.current_vy = 0
331
+ self.current_vz = 0
332
+ self.is_moving = False
333
+ if self.target_pos[2] == 0:
334
+ self.is_flying = False
335
+
336
+ self.pos = [x, y, z]
337
+ return [x, y, z]
338
+
339
+ def update_speed(self, xy_speed: List[float] = None, z_speed: List[float] = None):
340
+ """更新速度设置"""
341
+ if xy_speed:
342
+ self.xy_speed = xy_speed
343
+ if z_speed:
344
+ self.z_speed = z_speed
345
+
346
+
347
+ class LightState:
348
+ """灯光状态管理器"""
349
+
350
+ def __init__(self):
351
+ # 各部分的常亮颜色
352
+ self.body_color = (0, 0, 0)
353
+ self.motor_colors = {i: (0, 0, 0) for i in range(1, 5)}
354
+
355
+ # 各部分的灯光效果
356
+ self.body_effect: Optional[LightEffect] = None
357
+ self.motor_effects: Dict[int, Optional[LightEffect]] = {i: None for i in range(1, 5)}
358
+ self.motor_horse_effect: Optional[MotorHorseEffect] = None
359
+
360
+ # 记录最后一次设置的常亮颜色
361
+ self.last_body_color = (0, 0, 0)
362
+ self.last_motor_colors = {i: (0, 0, 0) for i in range(1, 5)}
363
+
364
+ def handle_command(self, cmd_key: str, cmd_value: Any, current_time: int):
365
+ """处理灯光指令"""
366
+
367
+ # 全局灯光指令
368
+ if cmd_key == 'AllOn':
369
+ color = cmd_value
370
+ self.body_color = color
371
+ self.last_body_color = color
372
+ for i in range(1, 5):
373
+ self.motor_colors[i] = color
374
+ self.last_motor_colors[i] = color
375
+ self.body_effect = None
376
+ self.motor_horse_effect = None
377
+ for i in range(1, 5):
378
+ self.motor_effects[i] = None
379
+ print(f" 灯光: 全部亮 {color}")
380
+
381
+ elif cmd_key == 'AllOff':
382
+ self.body_color = (0, 0, 0)
383
+ self.last_body_color = (0, 0, 0)
384
+ for i in range(1, 5):
385
+ self.motor_colors[i] = (0, 0, 0)
386
+ self.last_motor_colors[i] = (0, 0, 0)
387
+ self.body_effect = None
388
+ self.motor_horse_effect = None
389
+ for i in range(1, 5):
390
+ self.motor_effects[i] = None
391
+ print(f" 灯光: 全部灭")
392
+
393
+ elif cmd_key == 'AllBlink':
394
+ effect = BlinkEffect(
395
+ current_time,
396
+ cmd_value['color'],
397
+ cmd_value.get('brightness', 1.0),
398
+ cmd_value['dur'],
399
+ cmd_value['delay']
400
+ )
401
+ self.body_effect = effect
402
+ self.motor_horse_effect = None
403
+ for i in range(1, 5):
404
+ self.motor_effects[i] = effect
405
+ print(f" 灯光: 全部闪烁 {cmd_value['color']}, dur={cmd_value['dur']}ms, delay={cmd_value['delay']}ms")
406
+
407
+ elif cmd_key == 'AllBreath':
408
+ effect = BreathEffect(
409
+ current_time,
410
+ cmd_value['color'],
411
+ cmd_value['time1'],
412
+ cmd_value['time2'],
413
+ cmd_value.get('brightness', 1.0)
414
+ )
415
+ self.body_effect = effect
416
+ self.motor_horse_effect = None
417
+ for i in range(1, 5):
418
+ self.motor_effects[i] = effect
419
+ print(f" 灯光: 全部呼吸 {cmd_value['color']}, time1={cmd_value['time1']}ms, time2={cmd_value['time2']}ms")
420
+
421
+ # 机身灯光指令
422
+ elif cmd_key == 'BodyOn':
423
+ self.body_color = cmd_value
424
+ self.last_body_color = cmd_value
425
+ self.body_effect = None
426
+ print(f" 灯光: 机身亮 {cmd_value}")
427
+
428
+ elif cmd_key == 'BodyOff':
429
+ self.body_color = (0, 0, 0)
430
+ self.last_body_color = (0, 0, 0)
431
+ self.body_effect = None
432
+ print(f" 灯光: 机身灭")
433
+
434
+ elif cmd_key == 'BodyBlink':
435
+ effect = BlinkEffect(
436
+ current_time,
437
+ cmd_value['color'],
438
+ cmd_value.get('brightness', 1.0),
439
+ cmd_value['dur'],
440
+ cmd_value['delay']
441
+ )
442
+ self.body_effect = effect
443
+ print(f" 灯光: 机身闪烁 {cmd_value['color']}")
444
+
445
+ elif cmd_key == 'BodyBreath':
446
+ effect = BreathEffect(
447
+ current_time,
448
+ cmd_value['color'],
449
+ cmd_value['time1'],
450
+ cmd_value['time2'],
451
+ cmd_value.get('brightness', 1.0)
452
+ )
453
+ self.body_effect = effect
454
+ print(f" 灯光: 机身呼吸 {cmd_value['color']}")
455
+
456
+ # 马达灯光指令
457
+ elif cmd_key == 'MotorOn':
458
+ motor = cmd_value['motor']
459
+ color = cmd_value['color']
460
+ if motor == 0:
461
+ for i in range(1, 5):
462
+ self.motor_colors[i] = color
463
+ self.last_motor_colors[i] = color
464
+ self.motor_effects[i] = None
465
+ else:
466
+ self.motor_colors[motor] = color
467
+ self.last_motor_colors[motor] = color
468
+ self.motor_effects[motor] = None
469
+ self.motor_horse_effect = None
470
+ motor_str = "全部马达" if motor == 0 else f"马达{motor}"
471
+ print(f" 灯光: {motor_str}亮 {color}")
472
+
473
+ elif cmd_key == 'MotorOff':
474
+ motor = cmd_value
475
+ if motor == 0:
476
+ for i in range(1, 5):
477
+ self.motor_colors[i] = (0, 0, 0)
478
+ self.last_motor_colors[i] = (0, 0, 0)
479
+ self.motor_effects[i] = None
480
+ else:
481
+ self.motor_colors[motor] = (0, 0, 0)
482
+ self.last_motor_colors[motor] = (0, 0, 0)
483
+ self.motor_effects[motor] = None
484
+ self.motor_horse_effect = None
485
+ motor_str = "全部马达" if motor == 0 else f"马达{motor}"
486
+ print(f" 灯光: {motor_str}灭")
487
+
488
+ elif cmd_key == 'MotorBlink':
489
+ motor = cmd_value['motor']
490
+ effect = BlinkEffect(
491
+ current_time,
492
+ cmd_value['color'],
493
+ cmd_value.get('brightness', 1.0),
494
+ cmd_value['dur'],
495
+ cmd_value['delay']
496
+ )
497
+ if motor == 0:
498
+ for i in range(1, 5):
499
+ self.motor_effects[i] = effect
500
+ self.motor_horse_effect = None
501
+ else:
502
+ self.motor_effects[motor] = effect
503
+ motor_str = "全部马达" if motor == 0 else f"马达{motor}"
504
+ print(f" 灯光: {motor_str}闪烁 {cmd_value['color']}")
505
+
506
+ elif cmd_key == 'MotorBreath':
507
+ motor = cmd_value['motor']
508
+ effect = BreathEffect(
509
+ current_time,
510
+ cmd_value['color'],
511
+ cmd_value['time1'],
512
+ cmd_value['time2'],
513
+ cmd_value.get('brightness', 1.0)
514
+ )
515
+ if motor == 0:
516
+ for i in range(1, 5):
517
+ self.motor_effects[i] = effect
518
+ self.motor_horse_effect = None
519
+ else:
520
+ self.motor_effects[motor] = effect
521
+ motor_str = "全部马达" if motor == 0 else f"马达{motor}"
522
+ print(f" 灯光: {motor_str}呼吸 {cmd_value['color']}")
523
+
524
+ elif cmd_key == 'MotorHorse':
525
+ effect = MotorHorseEffect(
526
+ current_time,
527
+ cmd_value['colors'],
528
+ cmd_value['clock'],
529
+ cmd_value['delay']
530
+ )
531
+ self.motor_horse_effect = effect
532
+ # 清除单独的马达效果
533
+ for i in range(1, 5):
534
+ self.motor_effects[i] = None
535
+ direction = "顺时针" if cmd_value['clock'] else "逆时针"
536
+ print(f" 灯光: 跑马灯 {direction}, delay={cmd_value['delay']}ms")
537
+
538
+ def get_colors(self, current_time: int) -> Dict[str, Tuple[int, int, int]]:
539
+ """获取当前所有灯光颜色"""
540
+ result = {
541
+ 'body': (0, 0, 0),
542
+ 'motor1': (0, 0, 0),
543
+ 'motor2': (0, 0, 0),
544
+ 'motor3': (0, 0, 0),
545
+ 'motor4': (0, 0, 0)
546
+ }
547
+
548
+ # 处理机身
549
+ if self.body_effect:
550
+ result['body'] = self.body_effect.get_color(current_time)
551
+ else:
552
+ result['body'] = self.body_color
553
+
554
+ # 处理跑马灯效果
555
+ if self.motor_horse_effect:
556
+ motor_colors = self.motor_horse_effect.get_motor_colors(current_time)
557
+ for motor, color in motor_colors.items():
558
+ result[f'motor{motor}'] = color
559
+ else:
560
+ # 处理单独的马达
561
+ for motor in range(1, 5):
562
+ if self.motor_effects[motor]:
563
+ result[f'motor{motor}'] = self.motor_effects[motor].get_color(current_time)
564
+ else:
565
+ result[f'motor{motor}'] = self.motor_colors[motor]
566
+
567
+ return result
568
+
569
+
570
+ class DroneStateInterpolator:
571
+ def __init__(self, input_keyframes: Dict[int, Dict[str, Any]], takeoff_pos: List[float] = [0, 0]):
572
+ """
573
+ 初始化插值器
574
+ :param input_keyframes: 关键帧数据,键为时间戳(毫秒),值为指令字典
575
+ :param takeoff_pos: 起飞点坐标 [x, y] (cm)
576
+ """
577
+ if not isinstance(input_keyframes, dict):
578
+ raise ValueError(f"input_keyframes必须是字典类型,当前是: {type(input_keyframes)}")
579
+
580
+ self.keyframes = dict(input_keyframes)
581
+ self.sorted_keyframes = sorted(self.keyframes.items())
582
+ self.start_time = 0
583
+ self.end_time = self.sorted_keyframes[-1][0]
584
+
585
+ # 运动状态 - 传入起飞点
586
+ self.movement = MovementState(takeoff_pos)
587
+
588
+ # 灯光状态
589
+ self.light = LightState()
590
+
591
+ print(f"初始化完成")
592
+ print(f"起飞点: {takeoff_pos} cm")
593
+ print(f"时间范围: {self.start_time} - {self.end_time}ms")
594
+ print(f"关键帧数量: {len(self.sorted_keyframes)}")
595
+
596
+ def parse_command(self, cmd_key: str, cmd_value: Any, current_time: int):
597
+ """解析单个指令"""
598
+
599
+ # 运动控制指令
600
+ if cmd_key == 'TakeOff':
601
+ self.movement.takeoff(cmd_value, current_time)
602
+
603
+ elif cmd_key == 'Land':
604
+ self.movement.land(current_time)
605
+
606
+ elif cmd_key == 'MoveTo':
607
+ self.movement.move_to(cmd_value, current_time)
608
+
609
+ elif cmd_key == 'Move': # 新增相对移动指令
610
+ self.movement.move(cmd_value, current_time)
611
+
612
+ elif cmd_key == 'XYSpeed':
613
+ self.movement.update_speed(xy_speed=cmd_value)
614
+
615
+ elif cmd_key == 'ZSpeed':
616
+ self.movement.update_speed(z_speed=cmd_value)
617
+
618
+ # 灯光指令
619
+ elif cmd_key in ['AllOn', 'AllOff', 'AllBlink', 'AllBreath',
620
+ 'BodyOn', 'BodyOff', 'BodyBlink', 'BodyBreath',
621
+ 'MotorOn', 'MotorOff', 'MotorBlink', 'MotorBreath', 'MotorHorse']:
622
+ self.light.handle_command(cmd_key, cmd_value, current_time)
623
+
624
+ def generate_states(self) -> Dict[int, Dict[str, Any]]:
625
+ """
626
+ 生成每一毫秒的状态数据
627
+ """
628
+ print(f"\n生成状态数据:{self.start_time} - {self.end_time}ms")
629
+ total_points = self.end_time - self.start_time + 1
630
+ print(f"共 {total_points} 个时间点")
631
+
632
+ result = {}
633
+ last_progress = 0
634
+
635
+ # 遍历每一毫秒
636
+ for t in range(self.start_time, self.end_time + 1):
637
+ # 处理关键帧指令
638
+ for time, commands in self.sorted_keyframes:
639
+ if time == t:
640
+ print(f"\n>>> 时间 {t}ms 执行指令:")
641
+ for cmd_key, cmd_value in commands.items():
642
+ self.parse_command(cmd_key, cmd_value, t)
643
+
644
+ # 计算当前位置
645
+ current_pos = self.movement.calculate_position(t)
646
+
647
+ # 获取当前灯光状态
648
+ light_state = self.light.get_colors(t)
649
+
650
+ # 保存状态
651
+ result[t] = {
652
+ 'pos': [round(coord, 2) for coord in current_pos],
653
+ 'light': light_state.copy()
654
+ }
655
+
656
+ # 显示进度
657
+ progress = int((t - self.start_time) / (self.end_time - self.start_time) * 100)
658
+ if progress >= last_progress + 10:
659
+ print(f"处理进度: {progress}%")
660
+ last_progress = progress
661
+
662
+ # 找到最后一个Land指令的时间
663
+ last_land_time = None
664
+ for time, commands in reversed(self.sorted_keyframes):
665
+ if 'Land' in commands:
666
+ last_land_time = time
667
+ break
668
+
669
+ if last_land_time is not None:
670
+ # 计算降落完成时间
671
+ land_duration = self.movement.estimated_duration
672
+ land_end_time = last_land_time + int(land_duration * 1000)
673
+
674
+ print(f"\n>>> 降落过程: 从{last_land_time}ms开始,需要{land_duration:.2f}s,到{land_end_time}ms结束")
675
+
676
+ # 生成降落过程中的状态
677
+ for t in range(last_land_time + 1, land_end_time + 1):
678
+ current_pos = self.movement.calculate_position(t)
679
+ light_state = self.light.get_colors(t)
680
+ result[t] = {
681
+ 'pos': [round(coord, 2) for coord in current_pos],
682
+ 'light': light_state.copy()
683
+ }
684
+
685
+ # 降落后再延长5000ms
686
+ print(f">>> 降落完成后延长5000ms")
687
+ last_time = land_end_time
688
+ last_state = result[last_time]
689
+
690
+ for t in range(last_time + 1, last_time + 5001):
691
+ result[t] = {
692
+ 'pos': [last_state['pos'][0], last_state['pos'][1], 0.0],
693
+ 'light': last_state['light'].copy()
694
+ }
695
+ else:
696
+ # 如果没有Land指令,只延长5000ms
697
+ print(f"\n>>> 延长5000ms")
698
+ last_time = self.end_time
699
+ last_state = result[last_time]
700
+
701
+ for t in range(last_time + 1, last_time + 5001):
702
+ result[t] = {
703
+ 'pos': [last_state['pos'][0], last_state['pos'][1], last_state['pos'][2]],
704
+ 'light': last_state['light'].copy()
705
+ }
706
+
707
+ print(f"生成完成!总时间点: {len(result)}")
708
+ return result
709
+
710
+
711
+ def test_move_command():
712
+ """测试相对移动功能"""
713
+ print("=" * 70)
714
+ print("测试相对移动功能")
715
+ print("=" * 70)
716
+
717
+ # 测试数据:先绝对移动,然后相对移动
718
+ test_keyframes = {
719
+ 1000: {'TakeOff': 100, 'ZSpeed': [50, 20]},
720
+ 3000: {'MoveTo': [200, 200, 100]}, # 绝对移动到(200,200,100)
721
+ 5000: {'Move': [50, -50, 0]}, # 相对移动:x+50, y-50, z不变 -> (250,150,100)
722
+ 7000: {'Move': [-100, 0, -50]}, # 相对移动:x-100, y不变, z-50 -> (150,150,50)
723
+ }
724
+
725
+ takeoff_point = [100, 100]
726
+ interpolator = DroneStateInterpolator(test_keyframes, takeoff_point)
727
+ states = interpolator.generate_states()
728
+
729
+ print("\n" + "=" * 100)
730
+ print("时间(ms) X(cm) Y(cm) Z(cm) Vx(cm/s) Vy(cm/s) Vz(cm/s) 状态")
731
+ print("=" * 100)
732
+
733
+ key_times = [1000, 2000, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000]
734
+
735
+ for t in key_times:
736
+ if t in states:
737
+ pos = states[t]['pos']
738
+ vx = interpolator.movement.current_vx
739
+ vy = interpolator.movement.current_vy
740
+ vz = interpolator.movement.current_vz
741
+ status = "移动中" if interpolator.movement.is_moving else "静止"
742
+ print(
743
+ f"{t:6d} {pos[0]:6.1f} {pos[1]:6.1f} {pos[2]:6.1f} {vx:6.1f} {vy:6.1f} {vz:6.1f} {status}")
744
+
745
+
746
+ def save_sampled_states(states: Dict[int, Dict[str, Any]], sample_rate: int = 10):
747
+ """
748
+ 保存抽样数据到文件,避免文件过大
749
+ :param states: 完整状态数据
750
+ :param sample_rate: 抽样率,每隔sample_rate毫秒保存一个点
751
+ """
752
+ sampled_states = {}
753
+ for t in sorted(states.keys()):
754
+ if t % sample_rate == 0:
755
+ sampled_states[t] = states[t]
756
+
757
+ # 确保包含最后一个时间点
758
+ last_time = sorted(states.keys())[-1]
759
+ if last_time not in sampled_states:
760
+ sampled_states[last_time] = states[last_time]
761
+
762
+ with open('drone_states_sampled.json', 'w', encoding='utf-8') as f:
763
+ json.dump(sampled_states, f, indent=2)
764
+
765
+ print(f"抽样数据已保存到 drone_states_sampled.json (抽样率: 1/{sample_rate})")
766
+ print(f"原始数据点: {len(states)}, 抽样后: {len(sampled_states)}")
767
+
768
+
769
+ def caculateState(takeoff_pos_list, final_dict_list):
770
+ states_list = []
771
+ try:
772
+ os.mkdir('drone_states')
773
+ except:
774
+ pass
775
+
776
+ for i in range(len(takeoff_pos_list)):
777
+ interpolator = DroneStateInterpolator(final_dict_list[i], takeoff_pos_list[i])
778
+ states = interpolator.generate_states()
779
+ states_list.append(states)
780
+ with open(f'./drone_states/drone_states_drone{i + 1}.json', "w", encoding='utf-8') as f:
781
+ f.write(json.dumps(states, ensure_ascii=False))
782
+ return states_list
783
+
784
+
785
+ if __name__ == "__main__":
786
+ test_move_command()