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.
- {codrone_edu-2.6 → codrone_edu-2.9}/PKG-INFO +1 -1
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/drone.py +16 -8
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/system.py +3 -0
- codrone_edu-2.9/codrone_edu/tools/module_loader.py +110 -0
- codrone_edu-2.9/codrone_edu/tools/scrubber.py +111 -0
- codrone_edu-2.9/codrone_edu/tools/transformer.py +730 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/PKG-INFO +1 -1
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/SOURCES.txt +2 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/setup.py +1 -1
- codrone_edu-2.6/codrone_edu/tools/transformer.py +0 -254
- {codrone_edu-2.6 → codrone_edu-2.9}/MANIFEST.in +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/README.md +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/__init__.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/__main__.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/crc.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/black.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/blue.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/green.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/lightblue.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/purple.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/red.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/white.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/data/yellow.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/errors.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/protocol.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/receiver.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/storage.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/swarm.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/tools/__init__.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/tools/interrupter.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/tools/parser.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu/tools/update.py +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/dependency_links.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/not-zip-safe +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/requires.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/codrone_edu.egg-info/top_level.txt +0 -0
- {codrone_edu-2.6 → codrone_edu-2.9}/setup.cfg +0 -0
|
@@ -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.
|
|
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
|
-
|
|
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']
|