Skip to content

Commit aaee67e

Browse files
committed
Updates
1 parent 1268c19 commit aaee67e

File tree

1 file changed

+48
-18
lines changed

1 file changed

+48
-18
lines changed

_posts/2024-10-24-dynamic-module-loading-python.md

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,18 @@ Each module has a corresponding YAML configuration file that defines whether the
6666
```yaml
6767
servos:
6868
enabled: true
69-
path: "modules/actuators/servo"
69+
path: "modules.actuators.servo.Servo" # Include class name here
7070
instances:
7171
- name: "leg_l_hip"
7272
id: 0
7373
pin: 9
7474
range: [0, 180]
75-
start_pos: 40
75+
start: 40
7676
- name: "leg_l_knee"
7777
id: 1
7878
pin: 10
7979
range: [0, 180]
80-
start_pos: 10
80+
start: 10
8181
```
8282
8383
In this example, the `servos` module is enabled, and two instances (`leg_l_hip` and `leg_l_knee`) are defined with their respective configurations. The `path` points to where the module is located within the project folder.
@@ -92,9 +92,31 @@ Here’s an implementation of `ModuleLoader`:
9292
import os
9393
import yaml
9494
import importlib.util
95+
from pubsub import pub
9596
9697
class ModuleLoader:
9798
def __init__(self, config_folder='config'):
99+
"""
100+
ModuleLoader class
101+
:param config_folder: folder containing the module configuration files
102+
103+
Example config file:
104+
config/modules.yml
105+
---
106+
buzzer:
107+
enabled: true # Required
108+
path: "modules.audio.buzzer.Buzzer" # Required
109+
config: # Passed as **kwargs to the module's __init__ method
110+
pin: 27
111+
name: 'buzzer'
112+
113+
Example:
114+
loader = ModuleLoader()
115+
modules = loader.load_modules()
116+
117+
Reference module once loaded:
118+
translator_inst = modules['Translator']
119+
"""
98120
self.config_folder = config_folder
99121
self.modules = self.load_yaml_files()
100122
@@ -117,26 +139,31 @@ class ModuleLoader:
117139
"""Dynamically load and instantiate the modules based on the config."""
118140
instances = {} # Use a dictionary to store instances for easy access
119141
for module in self.modules:
120-
module_path = module['path'] # e.g., "modules/actuators/servo"
121-
module_name = module_path.split('/')[-1] # e.g., "servo"
122-
instances_config = module.get('instances', [module.get('config')]) # Get all instances or config
142+
print(f"Enabling {module['path']}")
143+
# get path excluding the last part
144+
module_path = module['path'].rsplit('.', 1)[0].replace('.', '/') # e.g., "modules.servo"
145+
module_name = module['path'].split('.')[-1] # e.g., "Servo"
146+
instances_config = module.get('instances', [module.get('config')]) # Get all instances or just use config
147+
if instances_config[0] is None:
148+
instances_config = [{}]
123149
124150
# Dynamically load the module
125-
spec = importlib.util.spec_from_file_location(module_name, f"{module_path}/{module_name}.py")
151+
spec = importlib.util.spec_from_file_location(module_name, f"{module_path}.py")
126152
mod = importlib.util.module_from_spec(spec)
127153
spec.loader.exec_module(mod)
128154
129155
# Create instances of the module
130156
for instance_config in instances_config:
131157
# Pass the instance config to the module's __init__ method as **kwargs
132-
instance_name = instance_config.get('name') # Use the instance name as the key
133-
instance = getattr(mod, module_name.capitalize())(**instance_config)
158+
instance_name = module_name + '_' + instance_config.get('name') if instance_config.get('name') is not None else module_name # Use the module name and instance name as the key or module_name if single instance
159+
instance = getattr(mod, module_name)(**instance_config)
134160
135161
# Store the instance in the dictionary
136162
instances[instance_name] = instance
163+
pub.sendMessage('log', msg=f"[ModuleLoader] Loaded module: {module_name} instance: {instance_name}")
137164
165+
print("All modules loaded")
138166
return instances # Return the dictionary of instances
139-
140167
```
141168

142169
#### How It Works:
@@ -171,14 +198,14 @@ If you wanted direct access to the instance created by the model loader, you can
171198
from module_loader import ModuleLoader
172199
173200
def main():
174-
# Load all enabled modules
175-
module_loader = ModuleLoader(config_folder='config')
176-
module_instances = module_loader.load_modules()
201+
# Dynamically load and initialize modules
202+
loader = ModuleLoader(config_folder="config")
203+
loader.load_yaml_files()
204+
module_instances = loader.load_modules()
177205
178206
# Access instances by name
179-
my_module = module_instances.get("my_module")
180-
if my_module:
181-
my_module.start() # Assuming there's a start method
207+
vision = module_instances['vision'] # Access the instance by name
208+
leg_servo = module_instances['Servo_leg_l_hip'] # Access one of multiple instances (the module name is prepended to the instance name in this case) )
182209
183210
if __name__ == '__main__':
184211
main()
@@ -192,6 +219,7 @@ from pubsub import pub
192219
pub.sendMessage('mytopic', data='somedata') # Publish to a topic
193220
pub.subscribe(self.handler_method, 'anothertopic') # subscribe to another topic
194221
```
222+
This is the primary communication method between modules in the Modular Biped Project. A timing loop fires events at intervals, triggering functionality in the modules.
195223

196224
## Writing a New Module for This Architecture
197225

@@ -208,8 +236,10 @@ class Buzzer:
208236
def __init__(self, **kwargs):
209237
self.pin = kwargs.get('pin') # Required, no default
210238
print(f"Initializing Buzzer on pin {self.pin}")
239+
pub.subscribe(self.buzz, 'buzz') # Subscribe to the buzz topic
211240
212241
def buzz(self):
242+
# Buzzer functionality goes here
213243
print(f"Buzzer on pin {self.pin} is buzzing!")
214244
```
215245

@@ -220,9 +250,9 @@ Create a corresponding YAML configuration file in the `config` folder that speci
220250
```yaml
221251
buzzer:
222252
enabled: true
223-
path: "modules/output/buzzer"
253+
path: "modules.output.buzzer"
224254
config:
225-
pin: 26
255+
pin: 27
226256
```
227257

228258
### 3. Load the Module Dynamically

0 commit comments

Comments
 (0)