codrone-edu 2.6__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 (37) hide show
  1. {codrone_edu-2.6 → codrone_edu-2.9}/PKG-INFO +1 -1
  2. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/drone.py +16 -8
  3. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/system.py +3 -0
  4. codrone_edu-2.9/codrone_edu/tools/module_loader.py +110 -0
  5. codrone_edu-2.9/codrone_edu/tools/scrubber.py +111 -0
  6. codrone_edu-2.9/codrone_edu/tools/transformer.py +730 -0
  7. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/PKG-INFO +1 -1
  8. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/SOURCES.txt +2 -0
  9. {codrone_edu-2.6 → codrone_edu-2.9}/setup.py +1 -1
  10. codrone_edu-2.6/codrone_edu/tools/transformer.py +0 -254
  11. {codrone_edu-2.6 → codrone_edu-2.9}/MANIFEST.in +0 -0
  12. {codrone_edu-2.6 → codrone_edu-2.9}/README.md +0 -0
  13. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/__init__.py +0 -0
  14. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/__main__.py +0 -0
  15. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/crc.py +0 -0
  16. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/black.txt +0 -0
  17. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/blue.txt +0 -0
  18. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/green.txt +0 -0
  19. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/lightblue.txt +0 -0
  20. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/purple.txt +0 -0
  21. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/red.txt +0 -0
  22. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/white.txt +0 -0
  23. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/yellow.txt +0 -0
  24. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/errors.py +0 -0
  25. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/protocol.py +0 -0
  26. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/receiver.py +0 -0
  27. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/storage.py +0 -0
  28. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/swarm.py +0 -0
  29. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/tools/__init__.py +0 -0
  30. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/tools/interrupter.py +0 -0
  31. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/tools/parser.py +0 -0
  32. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/tools/update.py +0 -0
  33. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/dependency_links.txt +0 -0
  34. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/not-zip-safe +0 -0
  35. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/requires.txt +0 -0
  36. {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/top_level.txt +0 -0
  37. {codrone_edu-2.6 → 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.6
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:
@@ -3022,7 +3030,7 @@ class Drone:
3022
3030
  :param power: int from -100-100
3023
3031
  :return: None
3024
3032
  """
3025
- self._control.roll = int(power)
3033
+ self._control.roll = max(-100, min(100, int(power)))
3026
3034
 
3027
3035
  def set_pitch(self, power):
3028
3036
  """
@@ -3031,7 +3039,7 @@ class Drone:
3031
3039
  :param power: int from 100-100
3032
3040
  :return: None
3033
3041
  """
3034
- self._control.pitch = int(power)
3042
+ self._control.pitch = max(-100, min(100, int(power)))
3035
3043
 
3036
3044
  def set_yaw(self, power):
3037
3045
  """
@@ -3040,7 +3048,7 @@ class Drone:
3040
3048
  :param power: int from -100-100
3041
3049
  :return: None
3042
3050
  """
3043
- self._control.yaw = int(power)
3051
+ self._control.yaw = max(-100, min(100, int(power)))
3044
3052
 
3045
3053
  def set_throttle(self, power):
3046
3054
  """
@@ -3049,7 +3057,7 @@ class Drone:
3049
3057
  :param power: int from -100-100
3050
3058
  :return: None
3051
3059
  """
3052
- self._control.throttle = int(power)
3060
+ self._control.throttle = max(-100, min(100, int(power)))
3053
3061
 
3054
3062
  def move(self, duration=None):
3055
3063
  """
@@ -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):
@@ -0,0 +1,110 @@
1
+ """
2
+ Custom import system for user modules in Pyodide environment.
3
+ Handles import hook installation, path management, and module transformation
4
+ """
5
+
6
+ import importlib
7
+ import importlib.abc
8
+ import importlib.machinery
9
+ import os
10
+ import sys
11
+
12
+
13
+ class TransformingLoader(importlib.abc.Loader):
14
+ """Loader that transforms user modules using transform_imported_module before execution."""
15
+
16
+ def __init__(self, fullname, path):
17
+ self.fullname = fullname
18
+ self.path = path
19
+
20
+ def get_filename(self, fullname):
21
+ return self.path
22
+
23
+ def exec_module(self, module):
24
+ from codrone_edu.tools.transformer import transform_imported_module
25
+
26
+ # Read the source file
27
+ with open(self.path, 'r') as f:
28
+ source = f.read()
29
+
30
+ # Transform the code for imported modules (no wrapper, no drone init)
31
+ try:
32
+ transformed_ast, transformed_code = transform_imported_module(source)
33
+ # Execute the transformed code in the module's namespace
34
+ code_obj = compile(transformed_ast, self.path, 'exec')
35
+ exec(code_obj, module.__dict__)
36
+ except Exception as e:
37
+ # If transformation fails, execute original code
38
+ try:
39
+ import js
40
+ js.console.log(f"Warning: Could not transform module {self.fullname}: {str(e)}")
41
+ import traceback
42
+ js.console.log(traceback.format_exc())
43
+ except:
44
+ # js module not available (not in Pyodide), just print
45
+ print(f"Warning: Could not transform module {self.fullname}: {str(e)}")
46
+
47
+ exec(compile(source, self.path, 'exec'), module.__dict__)
48
+
49
+
50
+ class TransformingFinder(importlib.abc.MetaPathFinder):
51
+ """MetaPathFinder that intercepts imports from user directories."""
52
+
53
+ def find_spec(self, fullname, path, target=None):
54
+ # Only transform modules from user directories
55
+ search_paths = path if path is not None else sys.path
56
+
57
+ # Early exit: if no user paths to search, skip entirely
58
+ has_user_path = any(
59
+ entry.startswith(('/mnt/pfr', '/mnt/blockly'))
60
+ for entry in search_paths
61
+ )
62
+ if not has_user_path:
63
+ return None
64
+
65
+ for entry in search_paths:
66
+ if entry.startswith(('/mnt/pfr', '/mnt/blockly')):
67
+ # Try to find the module file using full dotted path
68
+ parts = fullname.split('.')
69
+
70
+ # For submodule imports (e.g., package.module_1), when path is provided
71
+ # and points to the package directory, only use the last part of the name
72
+ if path is not None and len(parts) > 1:
73
+ # This is a submodule import from within a package
74
+ # path already points to package dir, so only use the submodule name
75
+ module_file = os.path.join(entry, parts[-1]) + '.py'
76
+ else:
77
+ # Top-level import from sys.path
78
+ # Convert dotted name to file path (e.g., utils.helpers -> utils/helpers.py)
79
+ module_file = os.path.join(entry, *parts) + '.py'
80
+
81
+ if os.path.exists(module_file):
82
+ loader = TransformingLoader(fullname, module_file)
83
+ spec = importlib.machinery.ModuleSpec(fullname, loader, origin=module_file)
84
+ spec.has_location = True
85
+ return spec
86
+
87
+ # Check if it's a package (directory with __init__.py)
88
+ # Only check when importing from sys.path (not when path is a package subdir)
89
+ if path is None:
90
+ package_init = os.path.join(entry, *parts, '__init__.py')
91
+ if os.path.exists(package_init):
92
+ loader = TransformingLoader(fullname, package_init)
93
+ spec = importlib.machinery.ModuleSpec(fullname, loader, origin=package_init)
94
+ spec.has_location = True
95
+ spec.submodule_search_locations = [os.path.join(entry, *parts)]
96
+ return spec
97
+
98
+ return None
99
+
100
+
101
+
102
+
103
+ def install_import_hook():
104
+ """Install the import hook at the beginning of sys.meta_path."""
105
+ # Check if already installed
106
+ for finder in sys.meta_path:
107
+ if isinstance(finder, TransformingFinder):
108
+ return # Already installed
109
+
110
+ sys.meta_path.insert(0, TransformingFinder())
@@ -0,0 +1,111 @@
1
+ import os
2
+ import sys
3
+ import importlib
4
+
5
+ def clear_user_module_cache():
6
+ """
7
+ Clear all user modules from sys.modules cache.
8
+
9
+ This removes cached modules from /mnt/pfr and /mnt/blockly to ensure
10
+ fresh imports on each code execution. This is important because module
11
+ cache can cause stale code to be used if a file is modified between runs.
12
+
13
+ Also handles namespace packages (no __init__.py) which have __file__ = None
14
+ but __path__ pointing to user directories.
15
+ """
16
+ user_prefixes = ('/mnt/pfr', '/mnt/blockly')
17
+
18
+ modules_to_remove = []
19
+ for mod_name, mod in sys.modules.items():
20
+ # Check 1: Module has __file__ starting with user prefix
21
+ if hasattr(mod, '__file__') and mod.__file__:
22
+ if mod.__file__.startswith(user_prefixes):
23
+ modules_to_remove.append(mod_name)
24
+ continue
25
+
26
+ # Check 2: Namespace package with __path__ in user directories
27
+ # Namespace packages have __file__ = None but __path__ set
28
+ if hasattr(mod, '__path__'):
29
+ mod_path = getattr(mod, '__path__', None)
30
+ if mod_path:
31
+ # __path__ can be a list or _NamespacePath object
32
+ path_list = list(mod_path) if hasattr(mod_path, '__iter__') else []
33
+ for p in path_list:
34
+ if isinstance(p, str) and p.startswith(user_prefixes):
35
+ modules_to_remove.append(mod_name)
36
+ break
37
+
38
+ for mod in modules_to_remove:
39
+ del sys.modules[mod]
40
+
41
+ # Also invalidate import caches to handle renamed/moved modules
42
+ importlib.invalidate_caches()
43
+
44
+ def rebuild_python_path():
45
+ """
46
+ Clear user script paths from sys.path.
47
+
48
+ This removes /mnt/pfr and /mnt/blockly from sys.path to prevent
49
+ stale module imports from user code directories.
50
+ """
51
+ sys.path = [
52
+ p for p in sys.path
53
+ if not p.startswith(('/mnt/pfr', '/mnt/blockly'))
54
+ ]
55
+
56
+ def cleanup_wildcard_imports_and_path(script_dir, exec_globals):
57
+ """
58
+ Clean up wildcard imports from the global namespace.
59
+
60
+ This function is called after user code execution to remove all names that were
61
+ imported via wildcard imports (e.g., from module import *), while preserving
62
+ the original initialization imports.
63
+
64
+ Args:
65
+ script_dir: The script's directory to remove from sys.path
66
+ exec_globals: The globals dictionary from the main execution context
67
+
68
+ Requires the following to be in exec_globals:
69
+ - _wildcard_cleanup_modules: List of module names to clean up (added by transformer)
70
+ - _globals_before_user_code: Set of global names before user code execution
71
+
72
+ This prevents namespace pollution between different code executions while maintaining
73
+ the necessary imports from PyodideManager initialization.
74
+ """
75
+
76
+ if script_dir and script_dir in sys.path and script_dir != '/mnt/pfr' and script_dir != '/mnt/blockly':
77
+ sys.path.remove(script_dir)
78
+
79
+ if '_wildcard_cleanup_modules' not in exec_globals:
80
+ return
81
+
82
+ # Check if we have the snapshot of globals before user code
83
+ if '_globals_before_user_code' in exec_globals:
84
+ globals_before = exec_globals['_globals_before_user_code']
85
+
86
+ for module_name in exec_globals['_wildcard_cleanup_modules']:
87
+ try:
88
+ # Import the module so we can access it for cleanup
89
+ module = importlib.import_module(module_name)
90
+
91
+ # Get all public names from the module
92
+ imported_names = [name for name in dir(module) if not name.startswith('_')]
93
+
94
+ # Delete each imported name from globals, but preserve initialization imports
95
+ for name in imported_names:
96
+ # Only delete if it was added by user code (not present before)
97
+ if name in exec_globals and name not in globals_before:
98
+ del exec_globals[name]
99
+ except Exception:
100
+ print("Error during cleanup of module:", module_name)
101
+ pass
102
+
103
+ # Clean up the tracking variable
104
+ del exec_globals['_wildcard_cleanup_modules']
105
+ else:
106
+ # If we don't have the snapshot, just delete the tracking variable
107
+ del exec_globals['_wildcard_cleanup_modules']
108
+
109
+ # Clean up our snapshot variable
110
+ if '_globals_before_user_code' in exec_globals:
111
+ del exec_globals['_globals_before_user_code']