Skip to content

Commit bff51db

Browse files
committed
Detangle command processing
The way commands were submitted and processed was to convoluted. Commands for HW objects would go through the following path: frontend -> queue -> controller -> core -> object Completions would come back like this: object -> core -> controller -> frontend This is the intended path. Once the command is queue by the frontend, it will be picked up and sent to the controller, which would encode the options and send it to the core. The core would then dispatch to the HW object. Completions would follow a similar path (except the queue). A command completion would be sent by the core to the controller, any data would be decoded, and then the completion is sent to the frontend. Commands for virtual objectes followed seemingly similar path: frontend -> queue -> controller -> virtual object The completions then would: virtual object -> controller -> frontend However, there were a couple of big issues with how this was done: 1. Virtual object command completions don't need to go through the controller. 2. Virtual object command handling required a lot of code to separate their handling from the handling of HW object commands. For example, handling command completions in the controller required a separate completions method. This commit fixes this by making the virtual object commands follow the same path as HW objects when submitted. Since virtual objects have to be registered with the core, it is possible for the core to send the commands to them. On the completion path, virtual object completions now use the same controller methods as HW objects allowing for the removal of all the "duplicated" code.
1 parent 025bbc7 commit bff51db

7 files changed

Lines changed: 117 additions & 230 deletions

File tree

controllers/__init__.py

Lines changed: 55 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class Controller(core.VortexCore):
180180
_libc = ctypes.CDLL("libc.so.6")
181181
_Command = namedtuple("Command", ['id', 'name', 'opts', 'data', 'defaults'])
182182

183-
def __init__(self, config):
183+
def __init__(self, config, command_completion_callback):
184184
# Unfortunately, Python does not deal with merging
185185
# class members from multiple classes. So, we can't
186186
# set chip architecture and frequency to a default
@@ -203,7 +203,7 @@ def __init__(self, config):
203203
self.object_defs = {x: None for x in core.ObjectKlass}
204204
self.object_factory = {x: None for x in core.ObjectKlass}
205205
self._virtual_objects = {}
206-
self._completion_callback = None
206+
self._completion_callback = command_completion_callback
207207
self._event_handlers = {}
208208
self._timer_factory = timers.Factory(self)
209209
self._exec_cmd_map = {}
@@ -274,14 +274,13 @@ def _load_objects(self, config):
274274
try:
275275
obj = self.object_factory[klass](options, self.objects,
276276
self.query_objects,
277-
self.virtual_command_complete,
277+
self._completion_callback,
278278
self.event_submit)
279279
except VirtualObjectError as error:
280280
self.log.error(f"Failed to create virtual object {klass}:{name}: {error}")
281281
continue
282282
self.object_defs[klass] = obj.__class__
283-
object_id = self.register_virtual_object(klass, name, self.vobj_cmd_exec,
284-
self.vobj_get_state)
283+
object_id = self.register_virtual_object(klass, name, obj)
285284
if object_id == vortex.core.INVALID_OBJECT_ID:
286285
self.log.error(f"Failed to register virtual object {klass}:{name}")
287286
continue
@@ -320,9 +319,7 @@ def _verify_pins(self, config):
320319
raise core.VortexCoreError(e)
321320
return True, None
322321

323-
def start(self, timer_frequency, update_frequency, set_priority, vobj_exec_cb, completion_cb):
324-
self._virtual_object_exec_command = vobj_exec_cb
325-
self._completion_callback = completion_cb
322+
def start(self, timer_frequency, update_frequency, set_priority):
326323
cur_freq = get_host_cpu_frequency()
327324
max_freq = get_host_cpu_frequency("max")
328325
tick_ns = hz_to_nsec(self.FREQUENCY)
@@ -336,47 +333,6 @@ def start(self, timer_frequency, update_frequency, set_priority, vobj_exec_cb, c
336333
def get_frequency(self):
337334
return self.FREQUENCY
338335

339-
def command_complete(self, cmd_id, status, data_addr):
340-
self.log.debug(f"controller complete {cmd_id}={status}, data_addr={data_addr}")
341-
obj_id, obj_cmd = self._exec_cmd_map.pop(cmd_id, (None, None))
342-
obj = self.objects.object_by_id(obj_id)
343-
data = None
344-
if data_addr:
345-
cmd = [x for x in self.object_factory[obj.klass].commands if x[0] == obj_cmd]
346-
if not cmd or len(cmd) != 1:
347-
self._completion_callback(cmd_id, -255)
348-
cmd_data = cmd[0][3]
349-
if cmd_data is not None and issubclass(cmd_data, ctypes.Structure):
350-
data = ctypes.cast(data_addr, ctypes.POINTER(cmd_data)).contents
351-
data = vortex.lib.ctypes_helpers.parse_ctypes_struct(data)
352-
self._completion_callback(cmd_id, status, data)
353-
354-
def virtual_command_complete(self, cmd_id, status, data=None):
355-
self.log.debug(f"controller virtual complete: {cmd_id}={status}, data={data}")
356-
self._completion_callback(cmd_id, status, data)
357-
358-
def virtual_object_opts_convert(self, obj_id, obj_cmd_id, opts):
359-
if obj_id not in self._virtual_objects:
360-
return -1
361-
vobj = self._virtual_objects[obj_id]
362-
args_struct = [cmd[2] for cmd in vobj.commands if cmd[0] == obj_cmd_id]
363-
if not args_struct:
364-
raise -1
365-
args_ptr = ctypes.cast(opts, ctypes.POINTER(args_struct[0])).contents
366-
args = vortex.lib.ctypes_helpers.parse_ctypes_struct(args_ptr)
367-
return args
368-
369-
def vobj_cmd_exec(self, klass, obj_id, cmd_id, cmd, opts):
370-
return self._virtual_object_exec_command(klass, obj_id, cmd_id, cmd, opts)
371-
372-
def vobj_get_state(self, klass, obj_id):
373-
if obj_id not in self._virtual_objects:
374-
return 0
375-
state = self._virtual_objects[obj_id].get_status()
376-
struct = self._virtual_objects[obj_id].state()
377-
vortex.lib.ctypes_helpers.fill_ctypes_struct(struct, state)
378-
return struct
379-
380336
def get_param(self, param):
381337
if param == "commands":
382338
cmds = {x: [] for x in core.ObjectKlass}
@@ -420,12 +376,6 @@ def lookup_objects(self, klass):
420376
return self.objects.object_by_klass(klass)
421377

422378
def query_objects(self, objects):
423-
virtual_objects = []
424-
for id in objects:
425-
object = self.objects.object_by_id(id)
426-
if object.klass and self.object_defs[object.klass].virtual:
427-
virtual_objects.append(id)
428-
objects = [x for x in objects if x not in virtual_objects]
429379
_status = self.get_status(objects)
430380
object_status = dict.fromkeys(objects, None)
431381
for i, id in enumerate(objects):
@@ -434,14 +384,15 @@ def query_objects(self, objects):
434384
self.log.error(f"Could not find klass for object id {id}")
435385
continue
436386
if _status[i]:
437-
status_struct = self.object_defs[object.klass].state
438-
status = ctypes.cast(_status[i], ctypes.POINTER(status_struct)).contents
439-
object_status[id] = vortex.lib.ctypes_helpers.parse_ctypes_struct(status)
440-
self._libc.free(ctypes.c_void_p(_status[i]))
387+
if self.object_defs[object.klass].virtual:
388+
object_status[id] = _status[i]
389+
else:
390+
status_struct = self.object_defs[object.klass].state
391+
status = ctypes.cast(_status[i], ctypes.POINTER(status_struct)).contents
392+
object_status[id] = vortex.lib.ctypes_helpers.parse_ctypes_struct(status)
393+
self._libc.free(ctypes.c_void_p(_status[i]))
441394
else:
442395
object_status[id] = None
443-
for id in virtual_objects:
444-
object_status[id] = self._virtual_objects[id].get_status()
445396
return object_status
446397

447398
def _convert_opts(self, klass, cmd_id, opts):
@@ -471,35 +422,49 @@ def _convert_opts(self, klass, cmd_id, opts):
471422
def exec_command(self, command_id, object_id, subcommand_id, opts=None):
472423
object = self.objects.object_by_id(object_id)
473424
self.log.debug(f"controller executing command {command_id}: {object.id} {object.klass} {object.name}")
474-
if opts is not None:
425+
if not self.object_defs[object.klass].virtual and opts is not None:
475426
opts = self._convert_opts(object.klass, subcommand_id, opts)
476-
if not self.object_defs[object.klass].virtual:
477-
args = 0
478-
if opts is not None:
427+
args = 0
428+
if opts is not None:
429+
if self.object_defs[object.klass].virtual:
430+
args = id(opts)
431+
else:
479432
args = ctypes.addressof(opts)
480-
# Store the command into the command map so if the object
481-
# completes the command quickly (before it is stored if it were
482-
# stored after), the complete callback can successfully find it.
483-
#
484-
# this thread core completion thread
485-
# -------------------------------------------------------
486-
# self.exec_command()
487-
# object->exec_command()
488-
# CORE_CMD_COMPLETE()
489-
# core_process_completions()
490-
# complete_cm()
491-
# self.command_complete()
492-
# return
493-
# return
494-
self._exec_cmd_map[command_id] = (object_id, subcommand_id)
495-
ret = super().exec_command(command_id, object_id, subcommand_id, args)
496-
self.log.debug(f"result: {command_id}={ret}")
497-
if ret:
498-
self._exec_cmd_map.pop(command_id)
499-
return ret
500-
else:
501-
return self._virtual_objects[object_id].exec_command(command_id,
502-
subcommand_id, opts)
433+
# Store the command into the command map so if the object
434+
# completes the command quickly (before it is stored if it were
435+
# stored after), the complete callback can successfully find it.
436+
#
437+
# this thread core completion thread
438+
# -------------------------------------------------------
439+
# self.exec_command()
440+
# object->exec_command()
441+
# CORE_CMD_COMPLETE()
442+
# core_process_completions()
443+
# complete_cm()
444+
# self.command_complete()
445+
# return
446+
# return
447+
self._exec_cmd_map[command_id] = (object_id, subcommand_id)
448+
ret = super().exec_command(command_id, object_id, subcommand_id, args)
449+
self.log.debug(f"result: {command_id}={ret}")
450+
if ret:
451+
self._exec_cmd_map.pop(command_id)
452+
return ret
453+
454+
def command_complete(self, cmd_id, status, data_addr):
455+
self.log.debug(f"controller complete {cmd_id}={status}, data_addr={data_addr}")
456+
obj_id, obj_cmd = self._exec_cmd_map.pop(cmd_id, (None, None))
457+
obj = self.objects.object_by_id(obj_id)
458+
data = None
459+
if data_addr:
460+
cmd = [x for x in self.object_factory[obj.klass].commands if x[0] == obj_cmd]
461+
if not cmd or len(cmd) != 1:
462+
self._completion_callback(cmd_id, -255)
463+
cmd_data = cmd[0][3]
464+
if cmd_data is not None and issubclass(cmd_data, ctypes.Structure):
465+
data = ctypes.cast(data_addr, ctypes.POINTER(cmd_data)).contents
466+
data = vortex.lib.ctypes_helpers.parse_ctypes_struct(data)
467+
self._completion_callback(cmd_id, status, data)
503468

504469
def event_register(self, klass, event_type, object_name, handler):
505470
subscription_id = super().event_register(klass, event_type, object_name,
@@ -549,7 +514,7 @@ def cleanup(self):
549514
for obj in self.objects.object_by_klass(klass):
550515
self.destory_object(obj.id)
551516

552-
def load_mcu(name, config):
517+
def load_mcu(name):
553518
# Get base controller class
554519
base_module = importlib.import_module("vortex.controllers")
555520
base_class = getattr(base_module, "Controller")
@@ -571,4 +536,4 @@ def load_mcu(name, config):
571536
mro = controllers[0].__mro__
572537
if mro[-3] is not Controller or mro[-2] is not core.VortexCore:
573538
logging.warning("Controller class inheritance is incorrect.")
574-
return controllers[0](config)
539+
return controllers[0]

controllers/boards/btt_octopus.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,3 @@ class BTTOctopus(STM32F4, Controller):
3939
DIGITAL_PIN_COUNT = 22
4040
NEOPIXEL_COUNT = 1
4141
ADC_MAX = 4095
42-
43-
def __init__(self, config):
44-
super().__init__(config)

emulator/__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ def __init__(self, config, frontend, sequential=False):
4343
machine = config.get_machine_config()
4444
kin = config.get_kinematics_config()
4545
self._kinematics = kinematics.Kinematics(kin)
46-
self._controller = load_mcu(machine.controller, config)
47-
if self._controller is None:
48-
raise EmulatorError(f"Controller creation failure")
4946
self._frontend = frontends.create_frontend(frontend)
5047
if self._frontend is None:
5148
raise EmulatorError(f"Failed to create frontend '{frontend}'")
49+
mcu = load_mcu(machine.controller)
50+
self._controller = mcu(config, self._frontend.complete_command)
51+
if self._controller is None:
52+
raise EmulatorError(f"Controller creation failure")
5253
self._frontend.set_sequential_mode(sequential)
5354
self._frontend.set_kinematics_model(self._kinematics)
5455
self._frontend.set_controller(self._controller)
@@ -92,9 +93,7 @@ def start(self):
9293
self._profiler.enable()
9394
try:
9495
self._controller.start(self._timer_frequency, self._update_frequency,
95-
self._controller_thread_priority,
96-
self._frontend.queue_virtual_object_command,
97-
self._frontend.complete_command)
96+
self._controller_thread_priority)
9897
except VortexCoreError as e:
9998
logging.error(str(e))
10099
self._controller.stop()

frontends/__init__.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -214,22 +214,6 @@ def queue_command(self, klass, object, cmd, opts, callback=None):
214214
self.wait_for_command(cmd_id)
215215
return cmd_id
216216

217-
def queue_virtual_object_command(self, klass, object, cmd_id, cmd, opts, callback=None):
218-
check = self._command_check(klass, object, cmd)
219-
if check is False:
220-
return False
221-
obj_id, obj_cmd_id = check
222-
opts = self._controller.virtual_object_opts_convert(obj_id, obj_cmd_id, opts)
223-
self.log.debug(f"Submitting virtual object command: {self.get_object_name(klass, obj_id)} {obj_cmd_id} {opts}")
224-
cmd = Command(obj_id, obj_cmd_id, opts)
225-
cmd.id = cmd_id
226-
with self._command_completion_lock:
227-
self._command_completion[cmd_id] = (cmd, callback)
228-
self._queue.put(cmd)
229-
if self._run_sequential:
230-
self.wait_for_command(cmd_id)
231-
return True
232-
233217
def complete_command(self, id, result, data=None):
234218
self.log.debug(f"Completing command: {id} {result}")
235219
with self._command_completion_lock:

frontends/klipper/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ def update(self, position, data):
793793
def send(self):
794794
for i in range(0, len(self.data), self.unit_len):
795795
cmd_id = self.frontend.queue_command(self.klass, self.name, "set",
796-
{"index": i / self.unit_len,
796+
{"index": int(i / self.unit_len),
797797
"color": self.data[i:i+self.unit_len]})
798798
completion = self.frontend.wait_for_command(cmd_id)
799799
return completion[0][0]

src/core/common_defs.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ typedef struct {
4646
event_unregister_t event_unregister;
4747
event_submit_t event_submit;
4848
cmd_submit_cb_t cmd_submit;
49-
void *v_cmd_exec;
50-
void *v_get_state;
5149
vortex_logger_t *logger;
5250
void *cb_data;
5351
} core_call_data_t;
@@ -72,6 +70,11 @@ struct core_object {
7270
*/
7371
const char *name;
7472

73+
/*
74+
* Is this a virtual object?
75+
*/
76+
bool virtual;
77+
7578
/*
7679
* Object update frequency in HZ. This is how
7780
* frequently the `update` callback will be called.

0 commit comments

Comments
 (0)