codrone-edu 2.8__tar.gz → 2.9__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.
Files changed (36) hide show
  1. {codrone_edu-2.8 → codrone_edu-2.9}/PKG-INFO +1 -1
  2. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/drone.py +12 -4
  3. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/system.py +3 -0
  4. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/tools/transformer.py +135 -130
  5. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu.egg-info/PKG-INFO +1 -1
  6. {codrone_edu-2.8 → codrone_edu-2.9}/setup.py +1 -1
  7. {codrone_edu-2.8 → codrone_edu-2.9}/MANIFEST.in +0 -0
  8. {codrone_edu-2.8 → codrone_edu-2.9}/README.md +0 -0
  9. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/__init__.py +0 -0
  10. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/__main__.py +0 -0
  11. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/crc.py +0 -0
  12. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/data/black.txt +0 -0
  13. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/data/blue.txt +0 -0
  14. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/data/green.txt +0 -0
  15. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/data/lightblue.txt +0 -0
  16. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/data/purple.txt +0 -0
  17. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/data/red.txt +0 -0
  18. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/data/white.txt +0 -0
  19. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/data/yellow.txt +0 -0
  20. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/errors.py +0 -0
  21. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/protocol.py +0 -0
  22. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/receiver.py +0 -0
  23. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/storage.py +0 -0
  24. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/swarm.py +0 -0
  25. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/tools/__init__.py +0 -0
  26. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/tools/interrupter.py +0 -0
  27. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/tools/module_loader.py +0 -0
  28. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/tools/parser.py +0 -0
  29. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/tools/scrubber.py +0 -0
  30. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu/tools/update.py +0 -0
  31. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu.egg-info/SOURCES.txt +0 -0
  32. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu.egg-info/dependency_links.txt +0 -0
  33. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu.egg-info/not-zip-safe +0 -0
  34. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu.egg-info/requires.txt +0 -0
  35. {codrone_edu-2.8 → codrone_edu-2.9}/codrone_edu.egg-info/top_level.txt +0 -0
  36. {codrone_edu-2.8 → codrone_edu-2.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codrone_edu
3
- Version: 2.8
3
+ Version: 2.9
4
4
  Summary: Python CoDrone EDU library
5
5
  Home-page:
6
6
  Author:
@@ -182,6 +182,8 @@ class Drone:
182
182
  self._cpuId = []
183
183
  self._battery = 0
184
184
  self._system = ModeSystem.Error
185
+ self._state_update_count = 0
186
+ self._last_state_update_time = 0.0
185
187
 
186
188
  self.timeStartProgram = time.time() # Program Start Time Recording
187
189
 
@@ -1142,6 +1144,10 @@ class Drone:
1142
1144
  self.state_data[6] = drone_type.battery
1143
1145
  self.state_data[7] = drone_type.modeMovement
1144
1146
  self.state_data[8] = drone_type.controlSpeed
1147
+ if sys.platform == 'emscripten':
1148
+ self._battery = drone_type.battery
1149
+ self._state_update_count += 1
1150
+ self._last_state_update_time = time.time()
1145
1151
 
1146
1152
  def get_state_data(self, delay=0.01):
1147
1153
  if sys.platform != 'emscripten' and not self._swarm:
@@ -1162,10 +1168,14 @@ class Drone:
1162
1168
  return self.state_data
1163
1169
 
1164
1170
  async def _get_state_data_emscripten(self, delay=0.01):
1171
+ prev_count = self._state_update_count
1165
1172
  await self.sendRequest(DeviceType.Drone, DataType.State)
1166
1173
  await asyncio.sleep(delay)
1167
1174
  self._system = self.state_data[1]
1168
- self._battery = self.state_data[6]
1175
+ if self._state_update_count == prev_count:
1176
+ # No response this call — only zero battery if data is genuinely stale
1177
+ if time.time() - self._last_state_update_time > 2.0:
1178
+ self._battery = 0
1169
1179
  self.state_data[1] = ModeSystem.Error
1170
1180
  self.state_data[6] = 0
1171
1181
  return self.state_data
@@ -1192,9 +1202,7 @@ class Drone:
1192
1202
 
1193
1203
  async def _get_battery_emscripten(self):
1194
1204
  await self.get_state_data(delay=0.02)
1195
- battery = self._battery
1196
- self._battery = 0
1197
- return battery
1205
+ return self._battery
1198
1206
 
1199
1207
  def get_system_state(self):
1200
1208
  if sys.platform != 'emscripten' and not self._swarm:
@@ -77,7 +77,10 @@ class ModelNumber(Enum):
77
77
 
78
78
  Drone_12_Controller_P1 = 0x000C2001 # Drone_12_Controller_P1 (CDE JROTC ed.)
79
79
  Drone_12_Drone_P1 = 0x000C1002 # Drone_12_Drone_P1 (CDE JROTC ed.)
80
+ Drone_8_Drone_P3 = 0x00081003 # Drone_8_Drone_P3 CoDrone EDU (IMU)
80
81
 
82
+ Drone_12_Controller_P10 = 0x000C200A # CDEP Controller
83
+ Drone_12_Drone_P10 = 0x000C100A # CDEP Drone
81
84
 
82
85
 
83
86
  class DeviceType(Enum):
@@ -369,6 +369,141 @@ class AsyncTransformer(ast.NodeTransformer):
369
369
  return self.copy_location(new_node, node)
370
370
  return node
371
371
 
372
+ class ModuleAsyncTransformer(ast.NodeTransformer):
373
+ """
374
+ Transforms imported user modules to async without wrapping in a _wrapper function.
375
+ Unlike AsyncTransformer, this does not restructure the module body — it only converts
376
+ functions to async and adds await to drone/custom calls in-place.
377
+ Used by transform_imported_module() for user modules loaded via the custom import hook.
378
+ """
379
+ def __init__(self, custom_functions, drone_instance_name, function_aliases=None):
380
+ self.custom_functions = custom_functions
381
+ self.drone_instance_name = drone_instance_name
382
+ self.function_aliases = function_aliases if function_aliases else {}
383
+ self.time_alias = None # Track what 'time' is imported as
384
+ self.custom_modules = set() # Track modules from custom imports
385
+
386
+ def copy_location(self, new_node, old_node):
387
+ ast.copy_location(new_node, old_node)
388
+ if hasattr(old_node, 'end_lineno'):
389
+ new_node.end_lineno = old_node.end_lineno
390
+ if hasattr(old_node, 'end_col_offset'):
391
+ new_node.end_col_offset = old_node.end_col_offset
392
+ return new_node
393
+
394
+ def visit_Import(self, node):
395
+ """Track time module imports and custom module imports"""
396
+ for alias in node.names:
397
+ if alias.name == 'time':
398
+ self.time_alias = alias.asname if alias.asname else 'time'
399
+ else:
400
+ # Track custom module imports (not library modules)
401
+ module_name = alias.name.split('.')[0] # Get root module name
402
+ if module_name not in LIBRARY_MODULES:
403
+ local_name = alias.asname if alias.asname else alias.name
404
+ self.custom_modules.add(local_name)
405
+ return self.generic_visit(node)
406
+
407
+ def visit_ImportFrom(self, node):
408
+ """Track ImportFrom statements for both time and custom modules"""
409
+ if node.module == 'time':
410
+ self.time_alias = 'time'
411
+ elif node.module:
412
+ # Track custom module imports
413
+ module_name = node.module.split('.')[0] # Get root module name
414
+ if module_name not in LIBRARY_MODULES:
415
+ # For "from module import x", track imported names
416
+ for alias in node.names:
417
+ local_name = alias.asname if alias.asname else alias.name
418
+ if local_name != '*': # Skip import *
419
+ self.custom_modules.add(local_name)
420
+ return self.generic_visit(node)
421
+
422
+ def visit_FunctionDef(self, node):
423
+ new_node = ast.AsyncFunctionDef(
424
+ name=node.name,
425
+ args=node.args,
426
+ body=node.body,
427
+ decorator_list=node.decorator_list,
428
+ returns=node.returns
429
+ )
430
+ new_node = self.copy_location(new_node, node)
431
+ self.generic_visit(new_node)
432
+ return new_node
433
+
434
+ def visit_Call(self, node):
435
+ # First, visit all arguments and keywords to ensure nested calls are transformed
436
+ node = self.generic_visit(node)
437
+
438
+ if isinstance(node.func, ast.Attribute):
439
+ # Handle attribute calls (e.g., drone.takeoff(), helper.do_something(), np.array())
440
+ if isinstance(node.func.value, ast.Name):
441
+ module_name = node.func.value.id
442
+ method_name = node.func.attr
443
+
444
+ # Check if this is a drone method call (by method name, not variable name)
445
+ # This allows imported modules to use drone instances with any parameter name
446
+ if method_name in awaitable_drone_methods:
447
+ if method_name in ['pair', 'open', 'connect', 'disconnect', 'close']:
448
+ # Replace with dummy_function()
449
+ new_node = ast.Call(
450
+ func=ast.Attribute(value=node.func.value, attr='dummy_function', ctx=ast.Load()),
451
+ args=[], keywords=[]
452
+ )
453
+ return self.copy_location(new_node, node)
454
+ elif method_name not in non_await_drone_functions:
455
+ new_node = ast.Await(value=node)
456
+ return self.copy_location(new_node, node)
457
+ elif module_name == self.drone_instance_name:
458
+ # Fallback: check if variable is named 'drone' (for any unlisted methods)
459
+ if node.func.attr in ['pair', 'open', 'connect', 'disconnect', 'close']:
460
+ # Replace with dummy_function()
461
+ new_node = ast.Call(
462
+ func=ast.Attribute(value=ast.Name(id=self.drone_instance_name, ctx=ast.Load()), attr='dummy_function', ctx=ast.Load()),
463
+ args=[], keywords=[]
464
+ )
465
+ return self.copy_location(new_node, node)
466
+ elif node.func.attr not in non_await_drone_functions:
467
+ new_node = ast.Await(value=node)
468
+ return self.copy_location(new_node, node)
469
+ elif module_name in self.custom_modules:
470
+ # Calls on custom imported modules (e.g., helper.do_something())
471
+ new_node = ast.Await(value=node)
472
+ return self.copy_location(new_node, node)
473
+ # Skip library module calls (e.g., np.array(), asyncio.sleep())
474
+ elif isinstance(node.func, ast.Name):
475
+ # Direct function calls (e.g., my_function(), some_utility_function())
476
+ func_name = node.func.id
477
+
478
+ # Check if it's a Python built-in function (don't await built-ins)
479
+ # EXCEPT for input() which is overridden with an async version in Pyodide
480
+ import builtins
481
+ if hasattr(builtins, func_name) and func_name != 'input':
482
+ builtin_obj = getattr(builtins, func_name)
483
+ # Check if it's callable (a function/class) and not async
484
+ # Exclude async built-ins like aiter, anext
485
+ if callable(builtin_obj) and func_name not in {'aiter', 'anext'}:
486
+ return node
487
+
488
+ # Look up the original function name if this is an alias
489
+ original_name = self.function_aliases.get(func_name, func_name)
490
+
491
+ # Await if it's NOT a known library function
492
+ # This handles:
493
+ # - Custom functions defined in the file
494
+ # - Functions imported from custom modules
495
+ # - Functions from 'import *' (which we can't track)
496
+ # - input() calls (overridden with async version in Pyodide)
497
+ if (func_name in self.custom_functions or
498
+ original_name in self.custom_functions or
499
+ func_name in self.custom_modules or
500
+ func_name == 'input' or
501
+ func_name not in LIBRARY_MODULES): # Await unknown functions (could be from import *)
502
+ new_node = ast.Await(value=node)
503
+ return self.copy_location(new_node, node)
504
+ return node
505
+
506
+
372
507
  class SleepTransformer(ast.NodeTransformer):
373
508
  def __init__(self, time_alias=None):
374
509
  self.time_alias = time_alias if time_alias else 'time'
@@ -531,136 +666,6 @@ def transform_imported_module(code):
531
666
  analyzer = CodeAnalyzer()
532
667
  analyzer.visit(tree)
533
668
 
534
- # Create a modified AsyncTransformer that doesn't wrap in _wrapper
535
- class ModuleAsyncTransformer(ast.NodeTransformer):
536
- def __init__(self, custom_functions, drone_instance_name, function_aliases=None):
537
- self.custom_functions = custom_functions
538
- self.drone_instance_name = drone_instance_name
539
- self.function_aliases = function_aliases if function_aliases else {}
540
- self.time_alias = None # Track what 'time' is imported as
541
- self.custom_modules = set() # Track modules from custom imports
542
-
543
- def copy_location(self, new_node, old_node):
544
- ast.copy_location(new_node, old_node)
545
- if hasattr(old_node, 'end_lineno'):
546
- new_node.end_lineno = old_node.end_lineno
547
- if hasattr(old_node, 'end_col_offset'):
548
- new_node.end_col_offset = old_node.end_col_offset
549
- return new_node
550
-
551
- def visit_Import(self, node):
552
- """Track time module imports and custom module imports"""
553
- for alias in node.names:
554
- if alias.name == 'time':
555
- self.time_alias = alias.asname if alias.asname else 'time'
556
- else:
557
- # Track custom module imports (not library modules)
558
- module_name = alias.name.split('.')[0] # Get root module name
559
- if module_name not in LIBRARY_MODULES:
560
- local_name = alias.asname if alias.asname else alias.name
561
- self.custom_modules.add(local_name)
562
- return self.generic_visit(node)
563
-
564
- def visit_ImportFrom(self, node):
565
- """Track ImportFrom statements for both time and custom modules"""
566
- if node.module == 'time':
567
- self.time_alias = 'time'
568
- elif node.module:
569
- # Track custom module imports
570
- module_name = node.module.split('.')[0] # Get root module name
571
- if module_name not in LIBRARY_MODULES:
572
- # For "from module import x", track imported names
573
- for alias in node.names:
574
- local_name = alias.asname if alias.asname else alias.name
575
- if local_name != '*': # Skip import *
576
- self.custom_modules.add(local_name)
577
- return self.generic_visit(node)
578
-
579
- # Don't wrap module - just transform functions
580
- def visit_FunctionDef(self, node):
581
- new_node = ast.AsyncFunctionDef(
582
- name=node.name,
583
- args=node.args,
584
- body=node.body,
585
- decorator_list=node.decorator_list,
586
- returns=node.returns
587
- )
588
- new_node = self.copy_location(new_node, node)
589
- self.generic_visit(new_node)
590
- return new_node
591
-
592
- def visit_Call(self, node):
593
- # First, visit all arguments and keywords to ensure nested calls are transformed
594
- node = self.generic_visit(node)
595
-
596
- if isinstance(node.func, ast.Attribute):
597
- # Handle attribute calls (e.g., drone.takeoff(), helper.do_something(), np.array())
598
- if isinstance(node.func.value, ast.Name):
599
- module_name = node.func.value.id
600
- method_name = node.func.attr
601
-
602
- # Check if this is a drone method call (by method name, not variable name)
603
- # This allows imported modules to use drone instances with any parameter name
604
- if method_name in awaitable_drone_methods:
605
- if method_name in ['pair', 'open', 'connect', 'disconnect', 'close']:
606
- # Replace with dummy_function()
607
- new_node = ast.Call(
608
- func=ast.Attribute(value=node.func.value, attr='dummy_function', ctx=ast.Load()),
609
- args=[], keywords=[]
610
- )
611
- return self.copy_location(new_node, node)
612
- elif method_name not in non_await_drone_functions:
613
- new_node = ast.Await(value=node)
614
- return self.copy_location(new_node, node)
615
- elif module_name == self.drone_instance_name:
616
- # Fallback: check if variable is named 'drone' (for any unlisted methods)
617
- if node.func.attr in ['pair', 'open', 'connect', 'disconnect', 'close']:
618
- # Replace with dummy_function()
619
- new_node = ast.Call(
620
- func=ast.Attribute(value=ast.Name(id=self.drone_instance_name, ctx=ast.Load()), attr='dummy_function', ctx=ast.Load()),
621
- args=[], keywords=[]
622
- )
623
- return self.copy_location(new_node, node)
624
- elif node.func.attr not in non_await_drone_functions:
625
- new_node = ast.Await(value=node)
626
- return self.copy_location(new_node, node)
627
- elif module_name in self.custom_modules:
628
- # Calls on custom imported modules (e.g., helper.do_something())
629
- new_node = ast.Await(value=node)
630
- return self.copy_location(new_node, node)
631
- # Skip library module calls (e.g., np.array(), asyncio.sleep())
632
- elif isinstance(node.func, ast.Name):
633
- # Direct function calls (e.g., my_function(), some_utility_function())
634
- func_name = node.func.id
635
-
636
- # Check if it's a Python built-in function (don't await built-ins)
637
- # EXCEPT for input() which is overridden with an async version in Pyodide
638
- import builtins
639
- if hasattr(builtins, func_name) and func_name != 'input':
640
- builtin_obj = getattr(builtins, func_name)
641
- # Check if it's callable (a function/class) and not async
642
- # Exclude async built-ins like aiter, anext
643
- if callable(builtin_obj) and func_name not in {'aiter', 'anext'}:
644
- return node
645
-
646
- # Look up the original function name if this is an alias
647
- original_name = self.function_aliases.get(func_name, func_name)
648
-
649
- # Await if it's NOT a known library function
650
- # This handles:
651
- # - Custom functions defined in the file
652
- # - Functions imported from custom modules
653
- # - Functions from 'import *' (which we can't track)
654
- # - input() calls (overridden with async version in Pyodide)
655
- if (func_name in self.custom_functions or
656
- original_name in self.custom_functions or
657
- func_name in self.custom_modules or
658
- func_name == 'input' or
659
- func_name not in LIBRARY_MODULES): # Await unknown functions (could be from import *)
660
- new_node = ast.Await(value=node)
661
- return self.copy_location(new_node, node)
662
- return node
663
-
664
669
  # Transform the AST (without module wrapper)
665
670
  module_transformer = ModuleAsyncTransformer(
666
671
  analyzer.custom_functions,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codrone_edu
3
- Version: 2.8
3
+ Version: 2.9
4
4
  Summary: Python CoDrone EDU library
5
5
  Home-page:
6
6
  Author:
@@ -22,7 +22,7 @@ Python package for controlling CoDrone EDU.
22
22
 
23
23
  setup(
24
24
  name='codrone_edu',
25
- version='2.8',
25
+ version='2.9',
26
26
  description='Python CoDrone EDU library',
27
27
  url='',
28
28
  author='',
File without changes
File without changes
File without changes
File without changes