codrone-edu 2.9__tar.gz → 2.10__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.
- {codrone_edu-2.9 → codrone_edu-2.10}/PKG-INFO +1 -1
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/drone.py +271 -14
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/protocol.py +254 -13
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/storage.py +6 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/system.py +93 -9
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/tools/transformer.py +144 -2
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu.egg-info/PKG-INFO +1 -1
- {codrone_edu-2.9 → codrone_edu-2.10}/setup.py +1 -1
- {codrone_edu-2.9 → codrone_edu-2.10}/MANIFEST.in +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/README.md +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/__init__.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/__main__.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/crc.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/data/black.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/data/blue.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/data/green.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/data/lightblue.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/data/purple.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/data/red.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/data/white.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/data/yellow.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/errors.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/receiver.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/swarm.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/tools/__init__.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/tools/interrupter.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/tools/module_loader.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/tools/parser.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/tools/scrubber.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu/tools/update.py +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu.egg-info/SOURCES.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu.egg-info/dependency_links.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu.egg-info/not-zip-safe +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu.egg-info/requires.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/codrone_edu.egg-info/top_level.txt +0 -0
- {codrone_edu-2.9 → codrone_edu-2.10}/setup.cfg +0 -0
|
@@ -137,7 +137,7 @@ class Drone:
|
|
|
137
137
|
# BaseFunctions Start
|
|
138
138
|
|
|
139
139
|
def __init__(self, flagCheckBackground=True, flagShowErrorMessage=False, flagShowLogMessage=False,
|
|
140
|
-
flagShowTransferData=False, flagShowReceiveData=False, swarm=False, color_data_dir=None):
|
|
140
|
+
flagShowTransferData=False, flagShowReceiveData=False, swarm=False, color_data_dir=None, flagShowDepLibraryVersion=True):
|
|
141
141
|
|
|
142
142
|
self._serialport = None
|
|
143
143
|
if sys.platform == 'emscripten':
|
|
@@ -270,10 +270,12 @@ class Drone:
|
|
|
270
270
|
library_name = "codrone-edu"
|
|
271
271
|
library = importlib.metadata.distribution(library_name)
|
|
272
272
|
if sys.platform != 'emscripten' and not self._swarm:
|
|
273
|
-
|
|
274
|
-
|
|
273
|
+
if flagShowDepLibraryVersion:
|
|
274
|
+
colorama.init()
|
|
275
|
+
print(Fore.GREEN + f"Running {library_name} library version {library.version}" + Style.RESET_ALL)
|
|
275
276
|
elif sys.platform == 'emscripten':
|
|
276
|
-
|
|
277
|
+
if flagShowDepLibraryVersion:
|
|
278
|
+
print("Running {0} library version {1}".format(library_name, library.version), color="green")
|
|
277
279
|
# this only works when using the release version of the library
|
|
278
280
|
|
|
279
281
|
def __del__(self):
|
|
@@ -1308,6 +1310,70 @@ class Drone:
|
|
|
1308
1310
|
await asyncio.sleep(0.01)
|
|
1309
1311
|
await self._trigger_speed_callback()
|
|
1310
1312
|
|
|
1313
|
+
def control_change(self, mode=ModeControlFlight.Position):
|
|
1314
|
+
"""
|
|
1315
|
+
Changes the flight control mode of the drone.
|
|
1316
|
+
default is set to ModeControlFlight.Position
|
|
1317
|
+
:param mode: ModeControlFlight enum value (e.g. Attitude, Position)
|
|
1318
|
+
:return: N/A
|
|
1319
|
+
"""
|
|
1320
|
+
if not isinstance(mode, ModeControlFlight):
|
|
1321
|
+
return None
|
|
1322
|
+
|
|
1323
|
+
if sys.platform != 'emscripten' and not self._swarm:
|
|
1324
|
+
return self._control_change_desktop(mode)
|
|
1325
|
+
if sys.platform != 'emscripten' and self._swarm:
|
|
1326
|
+
return asyncio.create_task(self._control_change_swarm(mode))
|
|
1327
|
+
else:
|
|
1328
|
+
return asyncio.create_task(self._control_change_emscripten(mode))
|
|
1329
|
+
|
|
1330
|
+
def _control_change_desktop(self, mode=ModeControlFlight.Position):
|
|
1331
|
+
for _ in range(2):
|
|
1332
|
+
self.sendCommand(CommandType.ModeControlFlight, mode.value)
|
|
1333
|
+
time.sleep(0.03)
|
|
1334
|
+
|
|
1335
|
+
async def _control_change_swarm(self, mode=ModeControlFlight.Position):
|
|
1336
|
+
for _ in range(2):
|
|
1337
|
+
await self.sendCommand(CommandType.ModeControlFlight, mode.value)
|
|
1338
|
+
await asyncio.sleep(0.03)
|
|
1339
|
+
|
|
1340
|
+
async def _control_change_emscripten(self, mode=ModeControlFlight.Position):
|
|
1341
|
+
for _ in range(2):
|
|
1342
|
+
await self.sendCommand(CommandType.ModeControlFlight, mode.value)
|
|
1343
|
+
await asyncio.sleep(0.03)
|
|
1344
|
+
|
|
1345
|
+
def headless_change(self, mode=Headless.Normal):
|
|
1346
|
+
"""
|
|
1347
|
+
Changes the headless mode of the drone.
|
|
1348
|
+
default is set to Headless.Normal
|
|
1349
|
+
:param mode: Headless enum value (Headless, Normal)
|
|
1350
|
+
:return: N/A
|
|
1351
|
+
"""
|
|
1352
|
+
if not isinstance(mode, Headless):
|
|
1353
|
+
return None
|
|
1354
|
+
|
|
1355
|
+
if sys.platform != 'emscripten' and not self._swarm:
|
|
1356
|
+
return self._headless_change_desktop(mode)
|
|
1357
|
+
if sys.platform != 'emscripten' and self._swarm:
|
|
1358
|
+
return asyncio.create_task(self._headless_change_swarm(mode))
|
|
1359
|
+
else:
|
|
1360
|
+
return asyncio.create_task(self._headless_change_emscripten(mode))
|
|
1361
|
+
|
|
1362
|
+
def _headless_change_desktop(self, mode=Headless.Normal):
|
|
1363
|
+
for _ in range(3):
|
|
1364
|
+
self.sendCommand(CommandType.Headless, mode.value)
|
|
1365
|
+
time.sleep(0.03)
|
|
1366
|
+
|
|
1367
|
+
async def _headless_change_swarm(self, mode=Headless.Normal):
|
|
1368
|
+
for _ in range(3):
|
|
1369
|
+
await self.sendCommand(CommandType.Headless, mode.value)
|
|
1370
|
+
await asyncio.sleep(0.03)
|
|
1371
|
+
|
|
1372
|
+
async def _headless_change_emscripten(self, mode=Headless.Normal):
|
|
1373
|
+
for _ in range(3):
|
|
1374
|
+
await self.sendCommand(CommandType.Headless, mode.value)
|
|
1375
|
+
await asyncio.sleep(0.03)
|
|
1376
|
+
|
|
1311
1377
|
def update_motion_data(self, drone_type):
|
|
1312
1378
|
|
|
1313
1379
|
"""
|
|
@@ -2221,7 +2287,11 @@ class Drone:
|
|
|
2221
2287
|
# set the speed to medium level
|
|
2222
2288
|
self.speed_change(speed_level=2)
|
|
2223
2289
|
time.sleep(0.2)
|
|
2224
|
-
|
|
2290
|
+
self.control_change(mode=ModeControlFlight.Position)
|
|
2291
|
+
time.sleep(0.2)
|
|
2292
|
+
self.headless_change(mode=Headless.Normal)
|
|
2293
|
+
time.sleep(0.2)
|
|
2294
|
+
|
|
2225
2295
|
# disable the previous YPRT commands
|
|
2226
2296
|
self.sendControl(0, 0, 0, 0)
|
|
2227
2297
|
time.sleep(0.2)
|
|
@@ -2303,6 +2373,10 @@ class Drone:
|
|
|
2303
2373
|
# set the speed to medium level
|
|
2304
2374
|
await self.speed_change(speed_level=2)
|
|
2305
2375
|
await asyncio.sleep(0.2)
|
|
2376
|
+
await self.control_change(mode=ModeControlFlight.Position)
|
|
2377
|
+
await asyncio.sleep(0.2)
|
|
2378
|
+
await self.headless_change(mode=Headless.Normal)
|
|
2379
|
+
await asyncio.sleep(0.2)
|
|
2306
2380
|
|
|
2307
2381
|
# disable the previous YPRT commands
|
|
2308
2382
|
await self.sendControl(0, 0, 0, 0)
|
|
@@ -2350,6 +2424,12 @@ class Drone:
|
|
|
2350
2424
|
await self.speed_change(speed_level=2)
|
|
2351
2425
|
await asyncio.sleep(0.2)
|
|
2352
2426
|
|
|
2427
|
+
# put all users in the same control/headless modes
|
|
2428
|
+
await self.control_change(mode=ModeControlFlight.Position)
|
|
2429
|
+
await asyncio.sleep(0.2)
|
|
2430
|
+
await self.headless_change(mode=Headless.Normal)
|
|
2431
|
+
await asyncio.sleep(0.2)
|
|
2432
|
+
|
|
2353
2433
|
# disable the previous YPRT commands
|
|
2354
2434
|
await self.sendControl(0, 0, 0, 0)
|
|
2355
2435
|
await asyncio.sleep(0.2)
|
|
@@ -2543,7 +2623,7 @@ class Drone:
|
|
|
2543
2623
|
|
|
2544
2624
|
if monitor0.monitorDataType == MonitorDataType.F32:
|
|
2545
2625
|
|
|
2546
|
-
dataCount = (
|
|
2626
|
+
dataCount = (len(dataArray) - 1 - Monitor0.getSize()) // 4
|
|
2547
2627
|
|
|
2548
2628
|
for i in range(0, dataCount):
|
|
2549
2629
|
|
|
@@ -2559,7 +2639,7 @@ class Drone:
|
|
|
2559
2639
|
|
|
2560
2640
|
self.systemTimeMonitorData = monitor4.systemTime
|
|
2561
2641
|
|
|
2562
|
-
dataCount = (
|
|
2642
|
+
dataCount = (len(dataArray) - 1 - Monitor4.getSize()) // 4
|
|
2563
2643
|
|
|
2564
2644
|
for i in range(0, dataCount):
|
|
2565
2645
|
|
|
@@ -2575,7 +2655,7 @@ class Drone:
|
|
|
2575
2655
|
|
|
2576
2656
|
self.systemTimeMonitorData = monitor8.systemTime
|
|
2577
2657
|
|
|
2578
|
-
dataCount = (
|
|
2658
|
+
dataCount = (len(dataArray) - 1 - Monitor8.getSize()) // 4
|
|
2579
2659
|
|
|
2580
2660
|
for i in range(0, dataCount):
|
|
2581
2661
|
|
|
@@ -3408,7 +3488,7 @@ class Drone:
|
|
|
3408
3488
|
"""
|
|
3409
3489
|
:param distance: the numerical value of the value to move
|
|
3410
3490
|
:param units: can either be in inches, centimeters, meters, feet.
|
|
3411
|
-
:param speed: default
|
|
3491
|
+
:param speed: default 0.5 meter per second. Max is 2 meters/second.
|
|
3412
3492
|
:return: N/A
|
|
3413
3493
|
"""
|
|
3414
3494
|
if sys.platform != 'emscripten' and not self._swarm:
|
|
@@ -3497,7 +3577,7 @@ class Drone:
|
|
|
3497
3577
|
"""
|
|
3498
3578
|
:param distance: the numerical value of the value to move
|
|
3499
3579
|
:param units: can either be in inches, centimeters, meters, feet.
|
|
3500
|
-
:param speed: default
|
|
3580
|
+
:param speed: default 0.5 meter per second. Max is 2 meters/second.
|
|
3501
3581
|
:return: N/A
|
|
3502
3582
|
"""
|
|
3503
3583
|
if sys.platform != 'emscripten' and not self._swarm:
|
|
@@ -3586,7 +3666,7 @@ class Drone:
|
|
|
3586
3666
|
"""
|
|
3587
3667
|
:param distance: the numerical value of the value to move
|
|
3588
3668
|
:param units: can either be in inches, centimeters, meters, feet.
|
|
3589
|
-
:param speed: default
|
|
3669
|
+
:param speed: default 0.5 meter per second. Max is 2 meters/second.
|
|
3590
3670
|
:return: N/A
|
|
3591
3671
|
"""
|
|
3592
3672
|
if sys.platform != 'emscripten' and not self._swarm:
|
|
@@ -3674,7 +3754,7 @@ class Drone:
|
|
|
3674
3754
|
"""
|
|
3675
3755
|
:param distance: the numerical value of the value to move
|
|
3676
3756
|
:param units: can either be in inches, centimeters, meters, feet.
|
|
3677
|
-
:param speed: default
|
|
3757
|
+
:param speed: default 0.5 meter per second. Max is 2 meters/second.
|
|
3678
3758
|
:return: N/A
|
|
3679
3759
|
"""
|
|
3680
3760
|
if sys.platform != 'emscripten' and not self._swarm:
|
|
@@ -3758,6 +3838,183 @@ class Drone:
|
|
|
3758
3838
|
|
|
3759
3839
|
await asyncio.sleep(delay + 1.0)
|
|
3760
3840
|
|
|
3841
|
+
|
|
3842
|
+
def move_upward(self, distance, units="cm", speed=0.5):
|
|
3843
|
+
"""
|
|
3844
|
+
:param distance: the numerical value of the value to move
|
|
3845
|
+
:param units: can either be in inches, centimeters, meters, feet.
|
|
3846
|
+
:param speed: default 0.5 meter per second. Max is 2 meters/second.
|
|
3847
|
+
:return: N/A
|
|
3848
|
+
"""
|
|
3849
|
+
if sys.platform != 'emscripten' and not self._swarm:
|
|
3850
|
+
return self._move_upward_desktop(distance, units, speed)
|
|
3851
|
+
else:
|
|
3852
|
+
return asyncio.create_task(self._move_upward_emscripten(distance, units, speed))
|
|
3853
|
+
|
|
3854
|
+
def _move_upward_desktop(self, distance, units="cm", speed=0.5):
|
|
3855
|
+
if units == "cm":
|
|
3856
|
+
distance_meters = distance / 100
|
|
3857
|
+
elif units == "ft":
|
|
3858
|
+
distance_meters = distance / 3.28084
|
|
3859
|
+
elif units == "in":
|
|
3860
|
+
distance_meters = distance / 39.37
|
|
3861
|
+
elif units == "m":
|
|
3862
|
+
distance_meters = distance * 1
|
|
3863
|
+
else:
|
|
3864
|
+
print(Fore.RED + "Error: Not a valid unit." + Style.RESET_ALL)
|
|
3865
|
+
return
|
|
3866
|
+
|
|
3867
|
+
# cap the speed
|
|
3868
|
+
if speed > 2:
|
|
3869
|
+
speed = 2
|
|
3870
|
+
elif speed < 0:
|
|
3871
|
+
speed = 0
|
|
3872
|
+
|
|
3873
|
+
self.sendControlPosition(positionX=0,
|
|
3874
|
+
positionY=0,
|
|
3875
|
+
positionZ=distance_meters,
|
|
3876
|
+
velocity=speed,
|
|
3877
|
+
heading=0,
|
|
3878
|
+
rotationalVelocity=0)
|
|
3879
|
+
|
|
3880
|
+
delay = distance_meters / speed
|
|
3881
|
+
|
|
3882
|
+
time.sleep(0.1)
|
|
3883
|
+
|
|
3884
|
+
while self.get_movement_state() != ModeMovement.Moving:
|
|
3885
|
+
self.sendControlPosition(positionX=0, positionY=0, positionZ=distance_meters, velocity=speed,
|
|
3886
|
+
heading=0,
|
|
3887
|
+
rotationalVelocity=0)
|
|
3888
|
+
time.sleep(0.1)
|
|
3889
|
+
|
|
3890
|
+
time.sleep(delay + 1.0)
|
|
3891
|
+
|
|
3892
|
+
async def _move_upward_emscripten(self, distance, units="cm", speed=0.5):
|
|
3893
|
+
if units == "cm":
|
|
3894
|
+
distance_meters = distance / 100
|
|
3895
|
+
elif units == "ft":
|
|
3896
|
+
distance_meters = distance / 3.28084
|
|
3897
|
+
elif units == "in":
|
|
3898
|
+
distance_meters = distance / 39.37
|
|
3899
|
+
elif units == "m":
|
|
3900
|
+
distance_meters = distance * 1
|
|
3901
|
+
else:
|
|
3902
|
+
print("Error: Not a valid unit.", color="error")
|
|
3903
|
+
return
|
|
3904
|
+
|
|
3905
|
+
# cap the speed
|
|
3906
|
+
if speed > 2:
|
|
3907
|
+
speed = 2
|
|
3908
|
+
elif speed < 0:
|
|
3909
|
+
speed = 0
|
|
3910
|
+
|
|
3911
|
+
await self.sendControlPosition(positionX=0,
|
|
3912
|
+
positionY=0,
|
|
3913
|
+
positionZ=distance_meters,
|
|
3914
|
+
velocity=speed,
|
|
3915
|
+
heading=0,
|
|
3916
|
+
rotationalVelocity=0)
|
|
3917
|
+
|
|
3918
|
+
delay = distance_meters / speed
|
|
3919
|
+
|
|
3920
|
+
await asyncio.sleep(0.1)
|
|
3921
|
+
|
|
3922
|
+
while (await self.get_movement_state()) != ModeMovement.Moving:
|
|
3923
|
+
await self.sendControlPosition(positionX=0, positionY=0, positionZ=distance_meters, velocity=speed,
|
|
3924
|
+
heading=0,
|
|
3925
|
+
rotationalVelocity=0)
|
|
3926
|
+
await asyncio.sleep(0.10)
|
|
3927
|
+
|
|
3928
|
+
await asyncio.sleep(delay + 1.0)
|
|
3929
|
+
|
|
3930
|
+
def move_downward(self, distance, units="cm", speed=0.5):
|
|
3931
|
+
"""
|
|
3932
|
+
:param distance: the numerical value of the value to move
|
|
3933
|
+
:param units: can either be in inches, centimeters, meters, feet.
|
|
3934
|
+
:param speed: default 0.5 meter per second. Max is 2 meters/second.
|
|
3935
|
+
:return: N/A
|
|
3936
|
+
"""
|
|
3937
|
+
if sys.platform != 'emscripten' and not self._swarm:
|
|
3938
|
+
return self._move_downward_desktop(distance, units, speed)
|
|
3939
|
+
else:
|
|
3940
|
+
return asyncio.create_task(self._move_downward_emscripten(distance, units, speed))
|
|
3941
|
+
|
|
3942
|
+
def _move_downward_desktop(self, distance, units="cm", speed=0.5):
|
|
3943
|
+
if units == "cm":
|
|
3944
|
+
distance_meters = distance / 100
|
|
3945
|
+
elif units == "ft":
|
|
3946
|
+
distance_meters = distance / 3.28084
|
|
3947
|
+
elif units == "in":
|
|
3948
|
+
distance_meters = distance / 39.37
|
|
3949
|
+
elif units == "m":
|
|
3950
|
+
distance_meters = distance * 1
|
|
3951
|
+
else:
|
|
3952
|
+
print(Fore.RED + "Error: Not a valid unit." + Style.RESET_ALL)
|
|
3953
|
+
return
|
|
3954
|
+
|
|
3955
|
+
# cap the speed
|
|
3956
|
+
if speed > 2:
|
|
3957
|
+
speed = 2
|
|
3958
|
+
elif speed < 0:
|
|
3959
|
+
speed = 0
|
|
3960
|
+
|
|
3961
|
+
self.sendControlPosition(positionX=0,
|
|
3962
|
+
positionY=0,
|
|
3963
|
+
positionZ=-distance_meters,
|
|
3964
|
+
velocity=speed,
|
|
3965
|
+
heading=0,
|
|
3966
|
+
rotationalVelocity=0)
|
|
3967
|
+
|
|
3968
|
+
delay = distance_meters / speed
|
|
3969
|
+
|
|
3970
|
+
time.sleep(0.1)
|
|
3971
|
+
|
|
3972
|
+
while self.get_movement_state() != ModeMovement.Moving:
|
|
3973
|
+
self.sendControlPosition(positionX=0, positionY=0, positionZ=-distance_meters, velocity=speed,
|
|
3974
|
+
heading=0,
|
|
3975
|
+
rotationalVelocity=0)
|
|
3976
|
+
time.sleep(0.1)
|
|
3977
|
+
|
|
3978
|
+
time.sleep(delay + 1.0)
|
|
3979
|
+
|
|
3980
|
+
async def _move_downward_emscripten(self, distance, units="cm", speed=0.5):
|
|
3981
|
+
if units == "cm":
|
|
3982
|
+
distance_meters = distance / 100
|
|
3983
|
+
elif units == "ft":
|
|
3984
|
+
distance_meters = distance / 3.28084
|
|
3985
|
+
elif units == "in":
|
|
3986
|
+
distance_meters = distance / 39.37
|
|
3987
|
+
elif units == "m":
|
|
3988
|
+
distance_meters = distance * 1
|
|
3989
|
+
else:
|
|
3990
|
+
print("Error: Not a valid unit.", color="error")
|
|
3991
|
+
return
|
|
3992
|
+
|
|
3993
|
+
# cap the speed
|
|
3994
|
+
if speed > 2:
|
|
3995
|
+
speed = 2
|
|
3996
|
+
elif speed < 0:
|
|
3997
|
+
speed = 0
|
|
3998
|
+
|
|
3999
|
+
await self.sendControlPosition(positionX=0,
|
|
4000
|
+
positionY=0,
|
|
4001
|
+
positionZ=-distance_meters,
|
|
4002
|
+
velocity=speed,
|
|
4003
|
+
heading=0,
|
|
4004
|
+
rotationalVelocity=0)
|
|
4005
|
+
|
|
4006
|
+
delay = distance_meters / speed
|
|
4007
|
+
|
|
4008
|
+
await asyncio.sleep(0.1)
|
|
4009
|
+
|
|
4010
|
+
while (await self.get_movement_state()) != ModeMovement.Moving:
|
|
4011
|
+
await self.sendControlPosition(positionX=0, positionY=0, positionZ=-distance_meters, velocity=speed,
|
|
4012
|
+
heading=0,
|
|
4013
|
+
rotationalVelocity=0)
|
|
4014
|
+
await asyncio.sleep(0.10)
|
|
4015
|
+
|
|
4016
|
+
await asyncio.sleep(delay + 1.0)
|
|
4017
|
+
|
|
3761
4018
|
def send_absolute_position(self, positionX, positionY, positionZ, velocity, heading, rotationalVelocity):
|
|
3762
4019
|
"""
|
|
3763
4020
|
drone movement command
|
|
@@ -4528,7 +4785,7 @@ class Drone:
|
|
|
4528
4785
|
"""
|
|
4529
4786
|
A looped method that makes the drone fly forward until it reaches
|
|
4530
4787
|
a desired distance. The drone will keep that distance.
|
|
4531
|
-
The range of front sensor is from 0cm-
|
|
4788
|
+
The range of front sensor is from 0cm-150cm
|
|
4532
4789
|
|
|
4533
4790
|
:param timeout: duration in seconds that function will run
|
|
4534
4791
|
:param distance: distance in cm the drone will maintain in front of object
|
|
@@ -4580,7 +4837,7 @@ class Drone:
|
|
|
4580
4837
|
def detect_wall(self, distance=50):
|
|
4581
4838
|
"""
|
|
4582
4839
|
Returns True when a distance below the threshold is reached.
|
|
4583
|
-
The range of front sensor is from 0cm-
|
|
4840
|
+
The range of front sensor is from 0cm-150cm
|
|
4584
4841
|
|
|
4585
4842
|
:param distance: threshold in millimeters that returns True
|
|
4586
4843
|
:return: Boolean
|
|
@@ -53,6 +53,7 @@ class DataType(Enum):
|
|
|
53
53
|
Command = 0x11 # 명령
|
|
54
54
|
Pairing = 0x12 # 페어링
|
|
55
55
|
Rssi = 0x13 # RSSI
|
|
56
|
+
#ResponseRate = 0x13 # CDEP - response rate (success rate) metric
|
|
56
57
|
TimeSync = 0x14 # 시간 동기화
|
|
57
58
|
TransmissionPower = 0x15 # 전송 출력
|
|
58
59
|
Configuration = 0x16 # 설정
|
|
@@ -66,6 +67,12 @@ class DataType(Enum):
|
|
|
66
67
|
LightEvent = 0x22 # LED 이벤트
|
|
67
68
|
LightDefault = 0x23 # LED 초기 모드
|
|
68
69
|
|
|
70
|
+
# CDEP - packet-based LED commands (sent as top-level DataType, bypass Command/LightMode/LightEvent)
|
|
71
|
+
LED_rainbow = 0x24 # CDEP LED rainbow effect
|
|
72
|
+
LED_flicker = 0x25 # CDEP LED flicker effect
|
|
73
|
+
LED_random = 0x26 # CDEP LED random color effect
|
|
74
|
+
LED_individual = 0x27 # CDEP per-LED individual color control (5 LEDs)
|
|
75
|
+
|
|
69
76
|
# 센서 RAW 데이터
|
|
70
77
|
RawMotion = 0x30 # Motion 센서 데이터 RAW 값
|
|
71
78
|
RawFlow = 0x31 # Flow 센서 데이터 RAW 값
|
|
@@ -91,6 +98,7 @@ class DataType(Enum):
|
|
|
91
98
|
MotorSingle = 0x61 # 한 개의 모터 제어
|
|
92
99
|
Buzzer = 0x62 # 부저 제어
|
|
93
100
|
Vibrator = 0x63 # 진동 제어
|
|
101
|
+
Battery = 0x64 # CDEP - dedicated battery information
|
|
94
102
|
|
|
95
103
|
# Input
|
|
96
104
|
Button = 0x70 # 버튼 입력
|
|
@@ -165,6 +173,8 @@ class CommandType(Enum):
|
|
|
165
173
|
Backlight = 0x09 # 조종기 백라이트 설정
|
|
166
174
|
ModeController = 0x0A # 조종기 동작 모드(0x10:조종, 0x80:링크)
|
|
167
175
|
Link = 0x0B # 링크 제어(0:Client Mode, 1:Server Mode, 2:Pairing Start)
|
|
176
|
+
LoadDefaultColor = 0x0C # CDEP - apply default LED color
|
|
177
|
+
Trim = 0x0D # CDEP - absolute trim set (roll/pitch/yaw/throttle)
|
|
168
178
|
|
|
169
179
|
# 관리자
|
|
170
180
|
ClearCounter = 0xA0 # 카운터 클리어(관리자 권한을 획득했을 경우에만 동작)
|
|
@@ -177,11 +187,17 @@ class CommandType(Enum):
|
|
|
177
187
|
NavigationStop = 0xE4 # 네비게이션 중단
|
|
178
188
|
NavigationNext = 0xE5 # 네비게이션 목표점을 다음으로 변경
|
|
179
189
|
NavigationReturnToHome = 0xE6 # 시작 위치로 귀환
|
|
180
|
-
|
|
190
|
+
|
|
181
191
|
GpsRtkBase = 0xEA
|
|
182
192
|
GpsRtkRover = 0xEB
|
|
183
193
|
|
|
184
|
-
|
|
194
|
+
# CDEP
|
|
195
|
+
ModeTest = 0xF0 # test lock (unavailable until test completed / 27:enable, 11:disable)
|
|
196
|
+
ResetTimeCount = 0xFC # reset test time
|
|
197
|
+
RfChannel = 0xFD # RF channel (2400, 2440, 2483)
|
|
198
|
+
RfModulate = 0xFE # RF modulate (Modulate, Rx, Unmodulate)
|
|
199
|
+
|
|
200
|
+
EndOfType = 0xFF
|
|
185
201
|
|
|
186
202
|
|
|
187
203
|
# CommandType End
|
|
@@ -704,6 +720,81 @@ class Rssi(ISerializable):
|
|
|
704
720
|
|
|
705
721
|
|
|
706
722
|
|
|
723
|
+
class PairingCDEP(ISerializable):
|
|
724
|
+
# CDEP - Protocol::Pairing. Same DataType slot as Pairing (0x12), but a completely different
|
|
725
|
+
# 14-byte layout (BLE-style device-type + 5-byte address + 8-channel list) rather than the
|
|
726
|
+
# 11-byte address0/1/2+scramble+channel0-3 shape classic CoDrone EDU sends on this slot.
|
|
727
|
+
# Kept as its own class — the existing Pairing/sendPairing() path must stay intact for
|
|
728
|
+
# non-Plus hardware.
|
|
729
|
+
|
|
730
|
+
def __init__(self):
|
|
731
|
+
self.connectorDeviceType = DeviceType.None_
|
|
732
|
+
self.address = bytearray(5)
|
|
733
|
+
self.channelArray = bytearray(8)
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
@classmethod
|
|
737
|
+
def getSize(cls):
|
|
738
|
+
return 14
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def toArray(self):
|
|
742
|
+
dataArray = bytearray()
|
|
743
|
+
dataArray.extend(pack('<B', self.connectorDeviceType.value))
|
|
744
|
+
dataArray.extend(self.address[0:5])
|
|
745
|
+
dataArray.extend(self.channelArray[0:8])
|
|
746
|
+
return dataArray
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
@classmethod
|
|
750
|
+
def parse(cls, dataArray):
|
|
751
|
+
data = PairingCDEP()
|
|
752
|
+
|
|
753
|
+
if len(dataArray) != cls.getSize():
|
|
754
|
+
return None
|
|
755
|
+
|
|
756
|
+
indexStart = 0; indexEnd = 1; data.connectorDeviceType, = unpack('<B', dataArray[indexStart:indexEnd])
|
|
757
|
+
indexStart = indexEnd; indexEnd += 5; data.address = dataArray[indexStart:indexEnd]
|
|
758
|
+
indexStart = indexEnd; indexEnd += 8; data.channelArray = dataArray[indexStart:indexEnd]
|
|
759
|
+
|
|
760
|
+
data.connectorDeviceType = DeviceType(data.connectorDeviceType)
|
|
761
|
+
|
|
762
|
+
return data
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
class ResponseRate(ISerializable):
|
|
767
|
+
# CDEP - Protocol::ResponseRate. Same DataType slot and byte size as Rssi (0x13, 1 byte),
|
|
768
|
+
# but the Plus docs type the field as an unsigned success-rate metric rather than a signed
|
|
769
|
+
# dBm value. Kept as its own class rather than changing Rssi in place, since Rssi is still
|
|
770
|
+
# the shape classic CoDrone EDU hardware sends on this slot.
|
|
771
|
+
|
|
772
|
+
def __init__(self):
|
|
773
|
+
self.responseRate = 0
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
@classmethod
|
|
777
|
+
def getSize(cls):
|
|
778
|
+
return 1
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def toArray(self):
|
|
782
|
+
return pack('<B', self.responseRate)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
@classmethod
|
|
786
|
+
def parse(cls, dataArray):
|
|
787
|
+
data = ResponseRate()
|
|
788
|
+
|
|
789
|
+
if len(dataArray) != cls.getSize():
|
|
790
|
+
return None
|
|
791
|
+
|
|
792
|
+
data.responseRate, = unpack('<B', dataArray)
|
|
793
|
+
|
|
794
|
+
return data
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
|
|
707
798
|
class Command(ISerializable):
|
|
708
799
|
|
|
709
800
|
def __init__(self):
|
|
@@ -858,17 +949,17 @@ class MonitorHeaderType(Enum):
|
|
|
858
949
|
|
|
859
950
|
|
|
860
951
|
class MonitorDataType(Enum):
|
|
861
|
-
|
|
862
|
-
U8 = 0x00
|
|
863
|
-
S8 = 0x01
|
|
864
|
-
U16 = 0x02
|
|
865
|
-
S16 = 0x03
|
|
866
|
-
U32 = 0x04
|
|
867
|
-
S32 = 0x05
|
|
868
|
-
U64 = 0x06
|
|
869
|
-
S64 = 0x07
|
|
870
|
-
F32 = 0x08
|
|
871
|
-
F64 = 0x09
|
|
952
|
+
|
|
953
|
+
U8 = 0x00
|
|
954
|
+
S8 = 0x01
|
|
955
|
+
U16 = 0x02
|
|
956
|
+
S16 = 0x03
|
|
957
|
+
U32 = 0x04
|
|
958
|
+
S32 = 0x05
|
|
959
|
+
U64 = 0x06
|
|
960
|
+
S64 = 0x07
|
|
961
|
+
F32 = 0x08
|
|
962
|
+
F64 = 0x09
|
|
872
963
|
|
|
873
964
|
EndOfType = 0x0A
|
|
874
965
|
|
|
@@ -1646,6 +1737,156 @@ class LightEventColors(ISerializable):
|
|
|
1646
1737
|
return data
|
|
1647
1738
|
|
|
1648
1739
|
|
|
1740
|
+
|
|
1741
|
+
# CDEP LED packet commands Start
|
|
1742
|
+
# Sent as top-level DataType (0x24-0x27), independent of Command/LightMode/LightEvent.
|
|
1743
|
+
|
|
1744
|
+
|
|
1745
|
+
class LightRainbow(ISerializable):
|
|
1746
|
+
|
|
1747
|
+
def __init__(self):
|
|
1748
|
+
self.color = Color()
|
|
1749
|
+
self.interval = 0
|
|
1750
|
+
|
|
1751
|
+
|
|
1752
|
+
@classmethod
|
|
1753
|
+
def getSize(cls):
|
|
1754
|
+
return Color.getSize() + 2
|
|
1755
|
+
|
|
1756
|
+
|
|
1757
|
+
def toArray(self):
|
|
1758
|
+
dataArray = bytearray()
|
|
1759
|
+
dataArray.extend(self.color.toArray())
|
|
1760
|
+
dataArray.extend(pack('<H', self.interval))
|
|
1761
|
+
return dataArray
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
@classmethod
|
|
1765
|
+
def parse(cls, dataArray):
|
|
1766
|
+
data = LightRainbow()
|
|
1767
|
+
|
|
1768
|
+
if len(dataArray) != cls.getSize():
|
|
1769
|
+
return None
|
|
1770
|
+
|
|
1771
|
+
indexStart = 0; indexEnd = Color.getSize(); data.color = Color.parse(dataArray[indexStart:indexEnd])
|
|
1772
|
+
indexStart = indexEnd; indexEnd += 2; data.interval, = unpack('<H', dataArray[indexStart:indexEnd])
|
|
1773
|
+
|
|
1774
|
+
return data
|
|
1775
|
+
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
class LightFlicker(ISerializable):
|
|
1779
|
+
|
|
1780
|
+
def __init__(self):
|
|
1781
|
+
self.color = Color()
|
|
1782
|
+
self.interval = 0
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
@classmethod
|
|
1786
|
+
def getSize(cls):
|
|
1787
|
+
return Color.getSize() + 2
|
|
1788
|
+
|
|
1789
|
+
|
|
1790
|
+
def toArray(self):
|
|
1791
|
+
dataArray = bytearray()
|
|
1792
|
+
dataArray.extend(self.color.toArray())
|
|
1793
|
+
dataArray.extend(pack('<H', self.interval))
|
|
1794
|
+
return dataArray
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
@classmethod
|
|
1798
|
+
def parse(cls, dataArray):
|
|
1799
|
+
data = LightFlicker()
|
|
1800
|
+
|
|
1801
|
+
if len(dataArray) != cls.getSize():
|
|
1802
|
+
return None
|
|
1803
|
+
|
|
1804
|
+
indexStart = 0; indexEnd = Color.getSize(); data.color = Color.parse(dataArray[indexStart:indexEnd])
|
|
1805
|
+
indexStart = indexEnd; indexEnd += 2; data.interval, = unpack('<H', dataArray[indexStart:indexEnd])
|
|
1806
|
+
|
|
1807
|
+
return data
|
|
1808
|
+
|
|
1809
|
+
|
|
1810
|
+
|
|
1811
|
+
class LightRandom(ISerializable):
|
|
1812
|
+
# Doc's template shares the same 5-byte shape as LightRainbow/LightFlicker; the drone may
|
|
1813
|
+
# ignore the color/interval fields for this mode, but that is not confirmed by the docs.
|
|
1814
|
+
|
|
1815
|
+
def __init__(self):
|
|
1816
|
+
self.color = Color()
|
|
1817
|
+
self.interval = 0
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
@classmethod
|
|
1821
|
+
def getSize(cls):
|
|
1822
|
+
return Color.getSize() + 2
|
|
1823
|
+
|
|
1824
|
+
|
|
1825
|
+
def toArray(self):
|
|
1826
|
+
dataArray = bytearray()
|
|
1827
|
+
dataArray.extend(self.color.toArray())
|
|
1828
|
+
dataArray.extend(pack('<H', self.interval))
|
|
1829
|
+
return dataArray
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
@classmethod
|
|
1833
|
+
def parse(cls, dataArray):
|
|
1834
|
+
data = LightRandom()
|
|
1835
|
+
|
|
1836
|
+
if len(dataArray) != cls.getSize():
|
|
1837
|
+
return None
|
|
1838
|
+
|
|
1839
|
+
indexStart = 0; indexEnd = Color.getSize(); data.color = Color.parse(dataArray[indexStart:indexEnd])
|
|
1840
|
+
indexStart = indexEnd; indexEnd += 2; data.interval, = unpack('<H', dataArray[indexStart:indexEnd])
|
|
1841
|
+
|
|
1842
|
+
return data
|
|
1843
|
+
|
|
1844
|
+
|
|
1845
|
+
|
|
1846
|
+
class LightIndividual(ISerializable):
|
|
1847
|
+
# 5 independently addressable LEDs + a shared interval. Doc example: 17-byte payload
|
|
1848
|
+
# (5 * 3-byte Color + 2-byte interval), matching the LED_individual header length of 0x11.
|
|
1849
|
+
|
|
1850
|
+
def __init__(self):
|
|
1851
|
+
self.colors = [Color(), Color(), Color(), Color(), Color()]
|
|
1852
|
+
self.interval = 0
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
@classmethod
|
|
1856
|
+
def getSize(cls):
|
|
1857
|
+
return (Color.getSize() * 5) + 2
|
|
1858
|
+
|
|
1859
|
+
|
|
1860
|
+
def toArray(self):
|
|
1861
|
+
dataArray = bytearray()
|
|
1862
|
+
for color in self.colors:
|
|
1863
|
+
dataArray.extend(color.toArray())
|
|
1864
|
+
dataArray.extend(pack('<H', self.interval))
|
|
1865
|
+
return dataArray
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
@classmethod
|
|
1869
|
+
def parse(cls, dataArray):
|
|
1870
|
+
data = LightIndividual()
|
|
1871
|
+
|
|
1872
|
+
if len(dataArray) != cls.getSize():
|
|
1873
|
+
return None
|
|
1874
|
+
|
|
1875
|
+
indexStart = 0
|
|
1876
|
+
for i in range(5):
|
|
1877
|
+
indexEnd = indexStart + Color.getSize()
|
|
1878
|
+
data.colors[i] = Color.parse(dataArray[indexStart:indexEnd])
|
|
1879
|
+
indexStart = indexEnd
|
|
1880
|
+
|
|
1881
|
+
data.interval, = unpack('<H', dataArray[indexStart:indexStart + 2])
|
|
1882
|
+
|
|
1883
|
+
return data
|
|
1884
|
+
|
|
1885
|
+
|
|
1886
|
+
# CDEP LED packet commands End
|
|
1887
|
+
|
|
1888
|
+
|
|
1889
|
+
|
|
1649
1890
|
# Light End
|
|
1650
1891
|
|
|
1651
1892
|
|
|
@@ -87,4 +87,10 @@ class Parser:
|
|
|
87
87
|
self.d[DataType.InformationAssembledForController] = InformationAssembledForController.parse
|
|
88
88
|
self.d[DataType.InformationAssembledForEntry] = InformationAssembledForEntry.parse
|
|
89
89
|
|
|
90
|
+
# CDEP LED packet commands
|
|
91
|
+
self.d[DataType.LED_rainbow] = None
|
|
92
|
+
self.d[DataType.LED_flicker] = None
|
|
93
|
+
self.d[DataType.LED_random] = None
|
|
94
|
+
self.d[DataType.LED_individual] = None
|
|
95
|
+
|
|
90
96
|
|
|
@@ -79,7 +79,8 @@ class ModelNumber(Enum):
|
|
|
79
79
|
Drone_12_Drone_P1 = 0x000C1002 # Drone_12_Drone_P1 (CDE JROTC ed.)
|
|
80
80
|
Drone_8_Drone_P3 = 0x00081003 # Drone_8_Drone_P3 CoDrone EDU (IMU)
|
|
81
81
|
|
|
82
|
-
Drone_12_Controller_P10 = 0x000C200A # CDEP Controller
|
|
82
|
+
Drone_12_Controller_P10 = 0x000C200A # CDEP Controller (v1.0)
|
|
83
|
+
Drone_12_Controller_P11 = 0x000C2010 # CDEP Controller (v1.1)
|
|
83
84
|
Drone_12_Drone_P10 = 0x000C100A # CDEP Drone
|
|
84
85
|
|
|
85
86
|
|
|
@@ -130,7 +131,7 @@ class ModeSystem(Enum):
|
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
class ModeControlFlight(Enum):
|
|
133
|
-
|
|
134
|
+
|
|
134
135
|
None_ = 0x00
|
|
135
136
|
|
|
136
137
|
Attitude = 0x10 # 자세 - X,Y는 각도(deg)로 입력받음, Z,Yaw는 속도(m/s)로 입력 받음
|
|
@@ -138,8 +139,67 @@ class ModeControlFlight(Enum):
|
|
|
138
139
|
Manual = 0x12 # 고도를 수동으로 조종함
|
|
139
140
|
Rate = 0x13 # Rate - X,Y는 각속도(deg/s)로 입력받음, Z,Yaw는 속도(m/s)로 입력 받음
|
|
140
141
|
Function = 0x14 # 기능
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
|
|
143
|
+
BirdWing = 0x15 # CDEP - bird wing style aircraft
|
|
144
|
+
FixedWing = 0x16 # CDEP - fixed wing aircraft
|
|
145
|
+
|
|
146
|
+
EndOfType = 0x17
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ModeDrone(Enum):
|
|
151
|
+
# CDEP - drone-side operating mode (Flight / Card-coding / Motion-coding / Piano / Link / Calibration)
|
|
152
|
+
|
|
153
|
+
None_ = 0x00
|
|
154
|
+
|
|
155
|
+
Flight = 0x10
|
|
156
|
+
Card = 0x11
|
|
157
|
+
Motion = 0x12
|
|
158
|
+
Piano = 0x13
|
|
159
|
+
|
|
160
|
+
Link = 0x80
|
|
161
|
+
Calibration = 0x81
|
|
162
|
+
|
|
163
|
+
Error = 0xA0
|
|
164
|
+
|
|
165
|
+
EndOfType = 0xA1
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ModeController(Enum):
|
|
170
|
+
# CDEP - controller-side operating mode
|
|
171
|
+
|
|
172
|
+
None_ = 0x00
|
|
173
|
+
|
|
174
|
+
Control = 0x10
|
|
175
|
+
Setup = 0x11
|
|
176
|
+
|
|
177
|
+
Link = 0x80
|
|
178
|
+
|
|
179
|
+
Error = 0xA0
|
|
180
|
+
|
|
181
|
+
EndOfType = 0xA1
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ModeConnection(Enum):
|
|
186
|
+
# CDEP - RF link connection state
|
|
187
|
+
|
|
188
|
+
None_ = 0x00
|
|
189
|
+
|
|
190
|
+
PairingStart = 0x01
|
|
191
|
+
PairingExchange = 0x02
|
|
192
|
+
PairingComplete = 0x03
|
|
193
|
+
|
|
194
|
+
Ready = 0x04
|
|
195
|
+
|
|
196
|
+
ConnectingStart = 0x05
|
|
197
|
+
Connecting = 0x06
|
|
198
|
+
Connected = 0x07
|
|
199
|
+
LostConnection = 0x08
|
|
200
|
+
Disconnected = 0x09
|
|
201
|
+
|
|
202
|
+
EndOfPairing = 0x0A
|
|
143
203
|
|
|
144
204
|
|
|
145
205
|
|
|
@@ -199,26 +259,34 @@ class ErrorFlagsForSensor(Enum):
|
|
|
199
259
|
|
|
200
260
|
RangeGround_NoAnswer = 0x00000100 # No response from Ground distance sensor
|
|
201
261
|
RangeGround_WrongValue = 0x00000200 # Incorrect value from Ground distance sensor
|
|
202
|
-
|
|
262
|
+
|
|
263
|
+
RangeFront_NoAnswer = 0x00000400 # CDEP - No response from front distance sensor
|
|
264
|
+
RangeFront_WrongValue = 0x00000800 # CDEP - Incorrect value from front distance sensor
|
|
265
|
+
|
|
203
266
|
Flow_NoAnswer = 0x00001000 # No response from Flow sensor
|
|
204
267
|
Flow_WrongValue = 0x00002000 # Incorrect value from Flow sensor
|
|
205
268
|
Flow_CannotRecognizeGroundImage = 0x00004000 # Cannot recognize ground image
|
|
206
269
|
|
|
270
|
+
RF_NoAnswer = 0x10000000 # CDEP - No response from RF module
|
|
271
|
+
RF_Paired = 0x20000000 # CDEP - RF paired
|
|
272
|
+
RF_Connected = 0x40000000 # CDEP - RF connected
|
|
273
|
+
|
|
207
274
|
|
|
208
275
|
|
|
209
276
|
class ErrorFlagsForState(Enum):
|
|
210
277
|
|
|
211
278
|
None_ = 0x00000000
|
|
212
|
-
|
|
279
|
+
|
|
213
280
|
NotRegistered = 0x00000001 # Device not registered
|
|
214
281
|
FlashReadLock_UnLocked = 0x00000002 # Flash memory read lock not engaged
|
|
215
282
|
BootloaderWriteLock_UnLocked = 0x00000004 # Bootloader write lock not engaged
|
|
216
283
|
LowBattery = 0x00000008 # Low battery
|
|
217
|
-
|
|
284
|
+
|
|
218
285
|
TakeoffFailure_CheckPropellerAndMotor = 0x00000010 # Takeoff failure
|
|
219
286
|
CheckPropellerVibration = 0x00000020 # Propeller vibration detected
|
|
220
287
|
Attitude_NotStable = 0x00000040 # Attitude is too tilted or inverted
|
|
221
|
-
|
|
288
|
+
Motors_NotGood = 0x00000080 # CDEP - one or more motors not functioning correctly
|
|
289
|
+
|
|
222
290
|
CanNotFlip_LowBattery = 0x00000100 # Battery below 50%
|
|
223
291
|
CanNotFlip_TooHeavy = 0x00000200 # Device is too heavy
|
|
224
292
|
|
|
@@ -269,7 +337,7 @@ class Direction(Enum):
|
|
|
269
337
|
|
|
270
338
|
|
|
271
339
|
class Rotation(Enum):
|
|
272
|
-
|
|
340
|
+
|
|
273
341
|
None_ = 0x00
|
|
274
342
|
|
|
275
343
|
Clockwise = 0x01
|
|
@@ -279,6 +347,22 @@ class Rotation(Enum):
|
|
|
279
347
|
|
|
280
348
|
|
|
281
349
|
|
|
350
|
+
class MotorPart(Enum):
|
|
351
|
+
# CDEP - Motor::Part::Type. Note: BYROBOT's own docs are internally inconsistent about whether
|
|
352
|
+
# index 0 is the front-left or front-right motor (prose says front-left, enum lists MFR first).
|
|
353
|
+
# This transcribes the literal enum order; verify against real hardware before relying on it.
|
|
354
|
+
|
|
355
|
+
MFR = 0 # Front Right
|
|
356
|
+
MRR = 1 # Rear Right
|
|
357
|
+
MRL = 2 # Rear Left
|
|
358
|
+
MFL = 3 # Front Left
|
|
359
|
+
|
|
360
|
+
EndOfPart = 4
|
|
361
|
+
|
|
362
|
+
All = 5
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
|
|
282
366
|
class SensorOrientation(Enum):
|
|
283
367
|
|
|
284
368
|
None_ = 0x00
|
|
@@ -29,7 +29,7 @@ awaitable_drone_methods = frozenset({
|
|
|
29
29
|
"takeoff", "land", "emergency_stop", "hover", "reset_move",
|
|
30
30
|
# Movement - basic - 14
|
|
31
31
|
"go", "move", "turn", "flip", "set_waypoint", "sendLanding", "sendControlWhile",
|
|
32
|
-
"move_distance", "move_forward", "move_backward", "move_left", "move_right",
|
|
32
|
+
"move_distance", "move_forward", "move_backward", "move_left", "move_right", "move_upward", "move_downward",
|
|
33
33
|
"send_absolute_position", "goto_waypoint",
|
|
34
34
|
# Movement - turns - 4
|
|
35
35
|
"turn_degree", "turn_direction", "turn_left", "turn_right",
|
|
@@ -49,7 +49,7 @@ awaitable_drone_methods = frozenset({
|
|
|
49
49
|
"get_color_data", "get_position_data", "get_pos_x", "get_pos_y", "get_pos_z",
|
|
50
50
|
"get_height", "get_flow_data", "get_flow_velocity_x", "get_flow_velocity_y",
|
|
51
51
|
"get_state_data", "get_system_state", "get_flight_state", "get_movement_state",
|
|
52
|
-
"get_control_speed", "speed_change", "get_motion_data", "get_raw_motion_data",
|
|
52
|
+
"get_control_speed", "speed_change", "control_change", "headless_change", "get_motion_data", "get_raw_motion_data",
|
|
53
53
|
"get_accel_x", "get_accel_y", "get_accel_z",
|
|
54
54
|
"get_x_gyro", "get_y_gyro", "get_z_gyro",
|
|
55
55
|
"get_angle_x", "get_angle_y", "get_angle_z",
|
|
@@ -110,6 +110,87 @@ class CodeAnalyzer(ast.NodeVisitor):
|
|
|
110
110
|
self.function_aliases[local_name] = alias.name
|
|
111
111
|
self.generic_visit(node)
|
|
112
112
|
|
|
113
|
+
|
|
114
|
+
class DroneUsageAnalyzer(ast.NodeVisitor):
|
|
115
|
+
"""
|
|
116
|
+
Standalone AST visitor backing check_drone_usage(). Kept separate from
|
|
117
|
+
CodeAnalyzer (rather than added to it) so transform_code() and
|
|
118
|
+
transform_imported_module() - which run CodeAnalyzer on every execution -
|
|
119
|
+
don't pay for traversal they don't need.
|
|
120
|
+
"""
|
|
121
|
+
def __init__(self):
|
|
122
|
+
self.uses_drone = False
|
|
123
|
+
self.instantiates_drone = False
|
|
124
|
+
self.has_pair_or_connect = False
|
|
125
|
+
self.has_close_or_disconnect = False
|
|
126
|
+
self.has_drone_import = False
|
|
127
|
+
|
|
128
|
+
def visit_Assign(self, node):
|
|
129
|
+
if isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Name):
|
|
130
|
+
if node.value.func.id == 'Drone':
|
|
131
|
+
if node.targets[0].id != 'drone':
|
|
132
|
+
raise ValueError(f"Drone instance must be named 'drone', found '{node.targets[0].id}' instead.")
|
|
133
|
+
self.instantiates_drone = True
|
|
134
|
+
self.generic_visit(node)
|
|
135
|
+
|
|
136
|
+
def visit_ImportFrom(self, node):
|
|
137
|
+
if node.module == 'codrone_edu.drone':
|
|
138
|
+
self.has_drone_import = True
|
|
139
|
+
self.generic_visit(node)
|
|
140
|
+
|
|
141
|
+
def visit_Import(self, node):
|
|
142
|
+
for alias in node.names:
|
|
143
|
+
if alias.name == 'codrone_edu.drone':
|
|
144
|
+
self.has_drone_import = True
|
|
145
|
+
self.generic_visit(node)
|
|
146
|
+
|
|
147
|
+
def visit_Attribute(self, node):
|
|
148
|
+
if isinstance(node.value, ast.Name) and node.value.id == 'drone':
|
|
149
|
+
self.uses_drone = True
|
|
150
|
+
self.generic_visit(node)
|
|
151
|
+
|
|
152
|
+
def visit_Call(self, node):
|
|
153
|
+
if isinstance(node.func, ast.Name) and node.func.id == 'Drone':
|
|
154
|
+
self.instantiates_drone = True
|
|
155
|
+
elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == 'drone':
|
|
156
|
+
if node.func.attr in ('pair', 'connect'):
|
|
157
|
+
self.has_pair_or_connect = True
|
|
158
|
+
elif node.func.attr in ('close', 'disconnect'):
|
|
159
|
+
self.has_close_or_disconnect = True
|
|
160
|
+
self.generic_visit(node)
|
|
161
|
+
|
|
162
|
+
def get_warnings(self):
|
|
163
|
+
"""
|
|
164
|
+
Returns a list of warning strings for the most common CoDrone EDU
|
|
165
|
+
setup mistakes: using 'drone.' without creating a Drone() instance,
|
|
166
|
+
without pairing/connecting, without closing/disconnecting, or using
|
|
167
|
+
Drone() without importing it. Returns an empty list when no issues
|
|
168
|
+
are found.
|
|
169
|
+
"""
|
|
170
|
+
warnings = []
|
|
171
|
+
|
|
172
|
+
if self.uses_drone and not self.instantiates_drone:
|
|
173
|
+
warnings.append(
|
|
174
|
+
"Your code uses 'drone.' but never creates a Drone() instance. This will not run outside of Python for Robolink. " \
|
|
175
|
+
"Add 'drone = Drone()' before using 'drone'."
|
|
176
|
+
)
|
|
177
|
+
if self.uses_drone and not self.has_pair_or_connect:
|
|
178
|
+
warnings.append(
|
|
179
|
+
"Your code uses 'drone.' but never calls drone.pair() or drone.connect(). This will not run outside of Python for Robolink."
|
|
180
|
+
)
|
|
181
|
+
if self.uses_drone and not self.has_close_or_disconnect:
|
|
182
|
+
warnings.append(
|
|
183
|
+
"Your code uses 'drone.' but never calls drone.close() or drone.disconnect(). This will not run outside of Python for Robolink. " \
|
|
184
|
+
"Add drone.close() or drone.disconnect() at the end of your program to release the connection."
|
|
185
|
+
)
|
|
186
|
+
if self.instantiates_drone and not self.has_drone_import:
|
|
187
|
+
warnings.append(
|
|
188
|
+
"Your code uses 'Drone()' but never imports it. Add 'from codrone_edu.drone import *' at the top of your program."
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return warnings
|
|
192
|
+
|
|
193
|
+
|
|
113
194
|
class AsyncTransformer(ast.NodeTransformer):
|
|
114
195
|
def __init__(self, custom_functions, drone_instance_name, function_aliases=None):
|
|
115
196
|
self.custom_functions = custom_functions
|
|
@@ -590,6 +671,9 @@ class SleepTransformer(ast.NodeTransformer):
|
|
|
590
671
|
class CheckInterruptAndGlobalTransformer(ast.NodeTransformer):
|
|
591
672
|
def __init__(self, convert_global_to_nonlocal=True):
|
|
592
673
|
self.convert_global_to_nonlocal = convert_global_to_nonlocal
|
|
674
|
+
self._func_depth = 0
|
|
675
|
+
# Only `while True:` loops get the injected sleep
|
|
676
|
+
self.injected_sleep = False
|
|
593
677
|
|
|
594
678
|
def copy_location(self, new_node, old_node):
|
|
595
679
|
ast.copy_location(new_node, old_node)
|
|
@@ -609,6 +693,33 @@ class CheckInterruptAndGlobalTransformer(ast.NodeTransformer):
|
|
|
609
693
|
return [node, check_interrupt_call]
|
|
610
694
|
return super().generic_visit(node)
|
|
611
695
|
|
|
696
|
+
def visit_FunctionDef(self, node):
|
|
697
|
+
self._func_depth += 1
|
|
698
|
+
try:
|
|
699
|
+
return self.generic_visit(node)
|
|
700
|
+
finally:
|
|
701
|
+
self._func_depth -= 1
|
|
702
|
+
|
|
703
|
+
def visit_AsyncFunctionDef(self, node):
|
|
704
|
+
self._func_depth += 1
|
|
705
|
+
try:
|
|
706
|
+
return self.generic_visit(node)
|
|
707
|
+
finally:
|
|
708
|
+
self._func_depth -= 1
|
|
709
|
+
|
|
710
|
+
def visit_While(self, node):
|
|
711
|
+
is_while_true = isinstance(node.test, ast.Constant) and node.test.value is True
|
|
712
|
+
node = self.generic_visit(node)
|
|
713
|
+
if self._func_depth > 0 and is_while_true:
|
|
714
|
+
sleep_call = ast.Expr(value=ast.Await(value=ast.Call(
|
|
715
|
+
func=ast.Attribute(value=ast.Name(id='asyncio', ctx=ast.Load()), attr='sleep', ctx=ast.Load()),
|
|
716
|
+
args=[ast.Constant(value=0.0000000001)], keywords=[]
|
|
717
|
+
)))
|
|
718
|
+
sleep_call = self.copy_location(sleep_call, node)
|
|
719
|
+
node.body.append(sleep_call)
|
|
720
|
+
self.injected_sleep = True
|
|
721
|
+
return node
|
|
722
|
+
|
|
612
723
|
def visit_Global(self, node):
|
|
613
724
|
# Only convert global to nonlocal if we're in a wrapper function
|
|
614
725
|
if self.convert_global_to_nonlocal:
|
|
@@ -616,6 +727,31 @@ class CheckInterruptAndGlobalTransformer(ast.NodeTransformer):
|
|
|
616
727
|
return ast.copy_location(nonlocal_node, node)
|
|
617
728
|
return node
|
|
618
729
|
|
|
730
|
+
def check_drone_usage(code):
|
|
731
|
+
"""
|
|
732
|
+
Static, AST-based check for the most common CoDrone EDU setup mistakes:
|
|
733
|
+
using 'drone.' without creating a Drone() instance, without pairing or
|
|
734
|
+
connecting, without closing or disconnecting, or using Drone() without
|
|
735
|
+
importing it. Returns a list of warning strings (empty if no issues are
|
|
736
|
+
found). Like transform_code(), raises ValueError if a Drone() instance is
|
|
737
|
+
assigned to a variable not named 'drone'.
|
|
738
|
+
"""
|
|
739
|
+
tree = ast.parse(code)
|
|
740
|
+
analyzer = DroneUsageAnalyzer()
|
|
741
|
+
analyzer.visit(tree)
|
|
742
|
+
return analyzer.get_warnings()
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def ensure_asyncio_import(tree):
|
|
746
|
+
"""Insert `import asyncio` at module top level if not already present."""
|
|
747
|
+
has_asyncio = any(
|
|
748
|
+
(isinstance(n, ast.Import) and any(alias.name == 'asyncio' for alias in n.names))
|
|
749
|
+
or (isinstance(n, ast.ImportFrom) and n.module == 'asyncio')
|
|
750
|
+
for n in tree.body
|
|
751
|
+
)
|
|
752
|
+
if not has_asyncio:
|
|
753
|
+
tree.body.insert(0, ast.Import(names=[ast.alias(name='asyncio', asname=None)]))
|
|
754
|
+
|
|
619
755
|
def transform_code(code):
|
|
620
756
|
# Parse the code into an AST
|
|
621
757
|
tree = ast.parse(code)
|
|
@@ -645,6 +781,9 @@ def transform_code(code):
|
|
|
645
781
|
combined_transformer.visit(node)
|
|
646
782
|
break
|
|
647
783
|
|
|
784
|
+
if combined_transformer.injected_sleep:
|
|
785
|
+
ensure_asyncio_import(tree)
|
|
786
|
+
|
|
648
787
|
# Ensure the tree is correctly fixed
|
|
649
788
|
ast.fix_missing_locations(tree)
|
|
650
789
|
|
|
@@ -682,6 +821,9 @@ def transform_imported_module(code):
|
|
|
682
821
|
tree = sleep_transformer.visit(tree)
|
|
683
822
|
tree = combined_transformer.visit(tree)
|
|
684
823
|
|
|
824
|
+
if combined_transformer.injected_sleep:
|
|
825
|
+
ensure_asyncio_import(tree)
|
|
826
|
+
|
|
685
827
|
# Inject checkInterrupt import at the beginning of the module
|
|
686
828
|
# This is necessary because CheckInterruptTransformer adds checkInterrupt() calls
|
|
687
829
|
check_interrupt_import = ast.ImportFrom(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|