From cc66fa09cd3e66e3b8a0acf29193e9137503b645 Mon Sep 17 00:00:00 2001 From: MathiasMahn <53939819+MathiasMahn@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:41:39 +0100 Subject: [PATCH 1/8] adding event type decoding to FSMThread --- .gitignore | 3 + bpod_core/bpod.py | 7 +- examples/hello_world.ipynb | 222 ++++++++++++++++++++++++++++++++++++ examples/minimal_example.py | 67 +++++++++++ 4 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 examples/hello_world.ipynb create mode 100644 examples/minimal_example.py diff --git a/.gitignore b/.gitignore index 65de09cc..24aa61a4 100644 --- a/.gitignore +++ b/.gitignore @@ -193,6 +193,9 @@ target/ profile_default/ ipython_config.py +# LLM files +.repotomdrc + # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: diff --git a/bpod_core/bpod.py b/bpod_core/bpod.py index fc6e539b..4fc09462 100644 --- a/bpod_core/bpod.py +++ b/bpod_core/bpod.py @@ -169,6 +169,7 @@ def __init__( # noqa: PLR0913 softcode_handler: Callable, state_transitions: NDArray[np.uint8], use_back_op: bool, + event_names: list[str], ) -> None: """ Initialize the FSMThread. @@ -199,6 +200,7 @@ def __init__( # noqa: PLR0913 self._softcode_handler = softcode_handler self._state_transitions = state_transitions self._use_back_op = use_back_op + self._event_names = event_names def stop(self) -> None: self._stop_event.set() @@ -258,7 +260,9 @@ def run(self) -> None: events = event_data_view[:param] for event in events: if debug: - logger.debug('%d µs: Event %d', micros, event) + if not(event == 255): # exit event + event_name = self._event_names[event] if event < len(self._event_names) else "Unknown" + logger.debug('%d µs: Event: %s (%d)', micros, event_name, event) # TODO: handle event # handle state transitions / exit event @@ -1301,6 +1305,7 @@ def _run_state_machine(self, *, blocking: bool) -> None: self._softcode_handler, self._state_transitions, self._use_back_op, + self.event_names, ) self._fsm_thread.start() self._waiting_for_confirmation = False diff --git a/examples/hello_world.ipynb b/examples/hello_world.ipynb new file mode 100644 index 00000000..e8a80b05 --- /dev/null +++ b/examples/hello_world.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "e8a973cd", + "metadata": {}, + "outputs": [], + "source": [ + "from bpod_core.fsm import StateMachine" + ] + }, + { + "cell_type": "markdown", + "id": "09331e75", + "metadata": {}, + "source": [ + "### Define a state machine" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "1dfc3950", + "metadata": {}, + "outputs": [], + "source": [ + "fsm = StateMachine()\n", + "\n", + "fsm.set_global_timer(\n", + " index=1,\n", + " duration=5\n", + ")\n", + "\n", + "\n", + "fsm.add_state(\n", + " name='StartGlobalTimer',\n", + " timer=0.25,\n", + " transitions={'Tup': 'Port1Light'},\n", + " actions={'GlobalTimerTrig': 1},\n", + ")\n", + "fsm.add_state(\n", + " name='Port1Light',\n", + " timer=0.25,\n", + " transitions={\n", + " 'Tup': 'Port3Light',\n", + " 'GlobalTimer1_End': '>exit',\n", + " },\n", + " actions={'PWM1': 255},\n", + ")\n", + "fsm.add_state(\n", + " name='Port3Light',\n", + " timer=0.25,\n", + " transitions={\n", + " 'Tup': 'Port1Light',\n", + " 'GlobalTimer1_End': '>exit',\n", + " },\n", + " actions={'PWM3': 255},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a32cfda8", + "metadata": {}, + "source": [ + "### Display the statemechine for easy verification" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2c77e81e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "State Machine\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "StartGlobalTimer\n", + "\n", + "\n", + "StartGlobalTimer  \n", + "\n", + "0.25 s\n", + "GlobalTimerTrig\n", + "1\n", + "\n", + "\n", + "\n", + "\n", + "->StartGlobalTimer\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Port1Light\n", + "\n", + "\n", + "Port1Light  \n", + "\n", + "0.25 s\n", + "PWM1\n", + "255\n", + "\n", + "\n", + "\n", + "\n", + "StartGlobalTimer->Port1Light\n", + "\n", + "\n", + "Tup\n", + "\n", + "\n", + "\n", + "exit\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Port1Light->exit\n", + "\n", + "\n", + "GlobalTimer1_End\n", + "\n", + "\n", + "\n", + "Port3Light\n", + "\n", + "\n", + "Port3Light  \n", + "\n", + "0.25 s\n", + "PWM3\n", + "255\n", + "\n", + "\n", + "\n", + "\n", + "Port1Light->Port3Light\n", + "\n", + "\n", + "Tup\n", + "\n", + "\n", + "\n", + "Port3Light->exit\n", + "\n", + "\n", + "GlobalTimer1_End\n", + "\n", + "\n", + "\n", + "Port3Light->Port1Light\n", + "\n", + "\n", + "Tup\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fsm.to_digraph() " + ] + }, + { + "cell_type": "markdown", + "id": "ecf44170", + "metadata": {}, + "source": [ + "### Running a state machine / bpod communication does not seem to work from a jupyter notebook..." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "bpod-core", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/minimal_example.py b/examples/minimal_example.py new file mode 100644 index 00000000..6e1559f1 --- /dev/null +++ b/examples/minimal_example.py @@ -0,0 +1,67 @@ +from bpod_core.fsm import StateMachine +from bpod_core.bpod import Bpod +import logging +import sys + + +LOG_FILE = "bpod_debug.log" + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8'), + logging.StreamHandler(sys.stdout) # This also keeps logs appearing in the terminal + ] +) + +# 3. Specifically ensure the bpod_core library is set to DEBUG +logging.getLogger('bpod_core').setLevel(logging.DEBUG) + +print(f"Logging initialized. All Bpod traffic will be saved to: {LOG_FILE}") + +fsm = StateMachine() + +fsm.set_global_timer( + index=1, + duration=5 +) + + +fsm.add_state( + name='StartGlobalTimer', + timer=0.25, + transitions={'Tup': 'Port1Light'}, + actions={'GlobalTimerTrig': 1}, +) +fsm.add_state( + name='Port1Light', + timer=0.25, + transitions={ + 'Tup': 'Port3Light', + 'GlobalTimer1_End': '>exit', + }, + actions={'PWM1': 255}, +) +fsm.add_state( + name='Port3Light', + timer=0.25, + transitions={ + 'Tup': '>exit', + 'GlobalTimer1_End': '>exit', + }, + actions={'PWM3': 255}, +) + + + +with Bpod() as bpod: + print(f"Connected to Bpod!") + print(f"Found Bpod on port {bpod.serial0.port}") + print(f"Firmware Version: {bpod.version.firmware}") + print(f"Hardware Version: {bpod.version.machine}") + print("Send State machine.") + bpod.send_state_machine(fsm) + print("Run State machine.") + bpod.run_state_machine() + print("State machine finished.") \ No newline at end of file From 3471d866f066662b45d52efcbdd50cd9909818fc Mon Sep 17 00:00:00 2001 From: MathiasMahn <53939819+MathiasMahn@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:23:02 +0100 Subject: [PATCH 2/8] additional debugging and working global counter SM and run added --- bpod_core/bpod.py | 10 +++++ examples/global_timers.py | 39 +++++++++++++++++++ ...=> minimal_example_run_global_timer_SM.py} | 10 ++--- 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 examples/global_timers.py rename examples/{minimal_example.py => minimal_example_run_global_timer_SM.py} (85%) diff --git a/bpod_core/bpod.py b/bpod_core/bpod.py index 4fc09462..175b2835 100644 --- a/bpod_core/bpod.py +++ b/bpod_core/bpod.py @@ -827,6 +827,8 @@ def _compile_output_actions(self) -> None: if self.version.machine == 4: self.actions.extend(['AnalogThreshEnable', 'AnalogThreshDisable']) + + @property def port(self) -> str | None: """The port of the Bpod's primary serial device.""" @@ -895,6 +897,12 @@ def update_modules(self) -> None: self._compile_event_names() self._compile_output_actions() + def load_serial_message(self, module_index, message_id, message_bytes): + # Format: ord('L'), module_index(0-based), n_messages(1), msg_id, msg_len, msg_payload + header = struct.pack(' None: """ Validate the provided state machine for compatibility with the hardware. @@ -1265,6 +1273,8 @@ def append_events(event0: str, event1: str) -> None: f'exit', # this is 1 indexed + }, + actions={'PWM1': 255}, +) +fsm.add_state( + name='Port3Light', + timer=0.25, + transitions={ + 'Tup': 'Port1Light', + 'GlobalTimer1_End': '>exit', # this is 1 indexed + }, + actions={'PWM3': 255}, +) \ No newline at end of file diff --git a/examples/minimal_example.py b/examples/minimal_example_run_global_timer_SM.py similarity index 85% rename from examples/minimal_example.py rename to examples/minimal_example_run_global_timer_SM.py index 6e1559f1..36e2dfbe 100644 --- a/examples/minimal_example.py +++ b/examples/minimal_example_run_global_timer_SM.py @@ -23,7 +23,7 @@ fsm = StateMachine() fsm.set_global_timer( - index=1, + index=0, # this is 0 indexed duration=5 ) @@ -32,14 +32,14 @@ name='StartGlobalTimer', timer=0.25, transitions={'Tup': 'Port1Light'}, - actions={'GlobalTimerTrig': 1}, + actions={'GlobalTimerTrig': 1}, # this is 1 indexed ) fsm.add_state( name='Port1Light', timer=0.25, transitions={ 'Tup': 'Port3Light', - 'GlobalTimer1_End': '>exit', + 'GlobalTimer1_End': '>exit', # this is 1 indexed }, actions={'PWM1': 255}, ) @@ -47,8 +47,8 @@ name='Port3Light', timer=0.25, transitions={ - 'Tup': '>exit', - 'GlobalTimer1_End': '>exit', + 'Tup': 'Port1Light', + 'GlobalTimer1_End': '>exit', # this is 1 indexed }, actions={'PWM3': 255}, ) From df7a55151c78204bcb62387e940ded9029adb9b4 Mon Sep 17 00:00:00 2001 From: MathiasMahn <53939819+MathiasMahn@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:04:20 +0100 Subject: [PATCH 3/8] extended debug logging to include event_names. Fixed global counter index in example --- bpod_core/bpod.py | 1 + bpod_core/fsm.py | 2 +- ...ld.ipynb => Graph_global_counter_SM.ipynb} | 0 examples/global_counters.py | 8 +-- examples/global_timers.py | 8 +-- .../minimal_example_run_global_counter_SM.py | 72 +++++++++++++++++++ 6 files changed, 82 insertions(+), 9 deletions(-) rename examples/{hello_world.ipynb => Graph_global_counter_SM.ipynb} (100%) create mode 100644 examples/minimal_example_run_global_counter_SM.py diff --git a/bpod_core/bpod.py b/bpod_core/bpod.py index 175b2835..a30cbe09 100644 --- a/bpod_core/bpod.py +++ b/bpod_core/bpod.py @@ -801,6 +801,7 @@ def _compile_event_names(self) -> None: ]: self.event_names.extend(event_name.format(i) for i in range(n)) self.event_names.append('Tup') + logger.debug('Compiled event names: %s', self.event_names) def _compile_output_actions(self) -> None: """Compile the list of output actions supported by the Bpod hardware.""" diff --git a/bpod_core/fsm.py b/bpod_core/fsm.py index 9a01ac7c..a5db2124 100644 --- a/bpod_core/fsm.py +++ b/bpod_core/fsm.py @@ -462,7 +462,7 @@ def set_global_counter( threshold: GlobalCounterThreshold, ) -> None: """ - Configure a global timer with the specified parameters. + Configure a global counter with the specified parameters. Parameters ---------- diff --git a/examples/hello_world.ipynb b/examples/Graph_global_counter_SM.ipynb similarity index 100% rename from examples/hello_world.ipynb rename to examples/Graph_global_counter_SM.ipynb diff --git a/examples/global_counters.py b/examples/global_counters.py index 3ce22293..38d0a36b 100644 --- a/examples/global_counters.py +++ b/examples/global_counters.py @@ -9,7 +9,7 @@ fsm = StateMachine() fsm.set_global_counter( - index=0, + index=0, # this is zero based, so this is Global Counter 1 event='Port1High', threshold=5, ) @@ -23,14 +23,14 @@ fsm.add_state( name='ResetGlobalCounter', transitions={'Tup': 'Port1Light'}, - actions={'GlobalCounterReset': 0}, + actions={'GlobalCounterReset': 1}, ) fsm.add_state( name='Port1Light', timer=0.25, transitions={ 'Tup': 'Port3Light', - 'GlobalCounter0_End': '>exit', + 'GlobalCounter1_End': '>exit', }, actions={'PWM1': 255}, ) @@ -39,7 +39,7 @@ timer=0.25, transitions={ 'Tup': 'Port1Light', - 'GlobalCounter0_End': '>exit', + 'GlobalCounter1_End': '>exit', }, actions={'PWM3': 255}, ) diff --git a/examples/global_timers.py b/examples/global_timers.py index 0df414dc..69890c87 100644 --- a/examples/global_timers.py +++ b/examples/global_timers.py @@ -8,7 +8,7 @@ fsm = StateMachine() fsm.set_global_timer( - index=0, # this is 0 indexed + index=0, # this is zero based, so this is Global Timer 1 duration=5 ) @@ -17,14 +17,14 @@ name='StartGlobalTimer', timer=0.25, transitions={'Tup': 'Port1Light'}, - actions={'GlobalTimerTrig': 1}, # this is 1 indexed + actions={'GlobalTimerTrig': 1}, # this is 1 based ) fsm.add_state( name='Port1Light', timer=0.25, transitions={ 'Tup': 'Port3Light', - 'GlobalTimer1_End': '>exit', # this is 1 indexed + 'GlobalTimer1_End': '>exit', # this is 1 based }, actions={'PWM1': 255}, ) @@ -33,7 +33,7 @@ timer=0.25, transitions={ 'Tup': 'Port1Light', - 'GlobalTimer1_End': '>exit', # this is 1 indexed + 'GlobalTimer1_End': '>exit', # this is 1 based }, actions={'PWM3': 255}, ) \ No newline at end of file diff --git a/examples/minimal_example_run_global_counter_SM.py b/examples/minimal_example_run_global_counter_SM.py new file mode 100644 index 00000000..f275fc53 --- /dev/null +++ b/examples/minimal_example_run_global_counter_SM.py @@ -0,0 +1,72 @@ +from bpod_core.fsm import StateMachine +from bpod_core.bpod import Bpod +import logging +import sys + + +LOG_FILE = "bpod_debug.log" + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8'), + logging.StreamHandler(sys.stdout) # This also keeps logs appearing in the terminal + ] +) + +# 3. Specifically ensure the bpod_core library is set to DEBUG +logging.getLogger('bpod_core').setLevel(logging.DEBUG) + +print(f"Logging initialized. All Bpod traffic will be saved to: {LOG_FILE}") + +fsm = StateMachine() + +fsm.set_global_counter( + index=0, # this is zero based, so this is Global Counter 1 + event='BNC1_High', + threshold=5, +) + +fsm.add_state( + name='InitialDelay', + timer=2, + transitions={'Tup': 'ResetGlobalCounter'}, + actions={'PWM2': 255}, +) +fsm.add_state( + name='ResetGlobalCounter', + transitions={'Tup': 'Port1Light'}, + actions={'GlobalCounterReset': 1}, +) +fsm.add_state( + name='Port1Light', + timer=0.25, + transitions={ + 'Tup': 'Port3Light', + 'GlobalCounter1_End': '>exit', + }, + actions={'PWM1': 255}, +) +fsm.add_state( + name='Port3Light', + timer=0.25, + transitions={ + 'Tup': 'Port1Light', + 'GlobalCounter1_End': '>exit', + }, + actions={'PWM3': 255}, +) + + + +with Bpod() as bpod: + print(f"Connected to Bpod!") + print(f"Found Bpod on port {bpod.serial0.port}") + print(f"Firmware Version: {bpod.version.firmware}") + print(f"Hardware Version: {bpod.version.machine}") + print("Send State machine.") + bpod.send_state_machine(fsm) + print("Run State machine.") + bpod.run_state_machine() + print("State machine finished.") \ No newline at end of file From 0acc9d7ab5fb7b445ff2c8c93c32912ee84eba9a Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Tue, 27 Jan 2026 18:00:00 +0000 Subject: [PATCH 4/8] refactor `load_serial_message` and move to `bpod.Module` class --- bpod_core/bpod.py | 55 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/bpod_core/bpod.py b/bpod_core/bpod.py index a30cbe09..ab728ae2 100644 --- a/bpod_core/bpod.py +++ b/bpod_core/bpod.py @@ -898,12 +898,6 @@ def update_modules(self) -> None: self._compile_event_names() self._compile_output_actions() - def load_serial_message(self, module_index, message_id, message_bytes): - # Format: ord('L'), module_index(0-based), n_messages(1), msg_id, msg_len, msg_payload - header = struct.pack(' None: """ Validate the provided state machine for compatibility with the hardware. @@ -1570,6 +1564,55 @@ def relay(self, state: bool) -> None: """The current state of the serial relay.""" self.set_relay(state) + @validate_call + def load_serial_message( + self, + message_id: int, + message_bytes: bytes, + ) -> bool: + """ + Load a serial message targeting the module. + + Serial messages are byte sequences targeting a specific module that can be + triggered as output actions during a state machine run. Each message is + identified by a ``message_id``. + + Parameters + ---------- + message_id : int + Identifier for the message, in the range ``[0, 254]``. + message_bytes : bytes + The message payload (1 to 3 bytes). + + Returns + ------- + bool + :obj:`True` if the Bpod acknowledged the message, :obj:`False` otherwise. + + Raises + ------ + ValidationError + If the provided parameters cannot be validated or coerced to the expected + type. + ValueError + If ```message_id``, or ``message_bytes`` length is out of range. + """ + if not (0 <= message_id <= 254): + raise ValueError('Message ID must be between 0 and 254') + if not (1 <= (message_length := len(message_bytes)) <= 3): + raise ValueError('Message must be between 1 and 3 bytes long') + + self._bpod.serial0.write_struct( + f' Date: Tue, 27 Jan 2026 18:03:43 +0000 Subject: [PATCH 5/8] please ruff --- bpod_core/bpod.py | 19 +++++---- examples/global_counters.py | 2 +- examples/global_timers.py | 10 ++--- .../minimal_example_run_global_counter_SM.py | 31 ++++++++------- .../minimal_example_run_global_timer_SM.py | 39 ++++++++++--------- 5 files changed, 53 insertions(+), 48 deletions(-) diff --git a/bpod_core/bpod.py b/bpod_core/bpod.py index ab728ae2..81147b8c 100644 --- a/bpod_core/bpod.py +++ b/bpod_core/bpod.py @@ -259,10 +259,13 @@ def run(self) -> None: # handle each event events = event_data_view[:param] for event in events: - if debug: - if not(event == 255): # exit event - event_name = self._event_names[event] if event < len(self._event_names) else "Unknown" - logger.debug('%d µs: Event: %s (%d)', micros, event_name, event) + if debug and event != 255: # exit event + event_name = ( + self._event_names[event] + if event < len(self._event_names) + else 'Unknown' + ) + logger.debug('%d µs: Event: %s (%d)', micros, event_name, event) # TODO: handle event # handle state transitions / exit event @@ -828,8 +831,6 @@ def _compile_output_actions(self) -> None: if self.version.machine == 4: self.actions.extend(['AnalogThreshEnable', 'AnalogThreshDisable']) - - @property def port(self) -> str | None: """The port of the Bpod's primary serial device.""" @@ -1268,8 +1269,10 @@ def append_events(event0: str, event1: str) -> None: f'exit', # this is 1 based + 'GlobalTimer1_End': '>exit', # this is 1 based }, actions={'PWM1': 255}, ) @@ -33,7 +33,7 @@ timer=0.25, transitions={ 'Tup': 'Port1Light', - 'GlobalTimer1_End': '>exit', # this is 1 based + 'GlobalTimer1_End': '>exit', # this is 1 based }, actions={'PWM3': 255}, -) \ No newline at end of file +) diff --git a/examples/minimal_example_run_global_counter_SM.py b/examples/minimal_example_run_global_counter_SM.py index f275fc53..03ee238c 100644 --- a/examples/minimal_example_run_global_counter_SM.py +++ b/examples/minimal_example_run_global_counter_SM.py @@ -1,29 +1,31 @@ -from bpod_core.fsm import StateMachine -from bpod_core.bpod import Bpod import logging import sys +from bpod_core.bpod import Bpod +from bpod_core.fsm import StateMachine -LOG_FILE = "bpod_debug.log" +LOG_FILE = 'bpod_debug.log' logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8'), - logging.StreamHandler(sys.stdout) # This also keeps logs appearing in the terminal - ] + logging.StreamHandler( + sys.stdout + ), # This also keeps logs appearing in the terminal + ], ) # 3. Specifically ensure the bpod_core library is set to DEBUG logging.getLogger('bpod_core').setLevel(logging.DEBUG) -print(f"Logging initialized. All Bpod traffic will be saved to: {LOG_FILE}") +print(f'Logging initialized. All Bpod traffic will be saved to: {LOG_FILE}') fsm = StateMachine() fsm.set_global_counter( - index=0, # this is zero based, so this is Global Counter 1 + index=0, # this is zero based, so this is Global Counter 1 event='BNC1_High', threshold=5, ) @@ -59,14 +61,13 @@ ) - with Bpod() as bpod: - print(f"Connected to Bpod!") - print(f"Found Bpod on port {bpod.serial0.port}") - print(f"Firmware Version: {bpod.version.firmware}") - print(f"Hardware Version: {bpod.version.machine}") - print("Send State machine.") + print('Connected to Bpod!') + print(f'Found Bpod on port {bpod.serial0.port}') + print(f'Firmware Version: {bpod.version.firmware}') + print(f'Hardware Version: {bpod.version.machine}') + print('Send State machine.') bpod.send_state_machine(fsm) - print("Run State machine.") + print('Run State machine.') bpod.run_state_machine() - print("State machine finished.") \ No newline at end of file + print('State machine finished.') diff --git a/examples/minimal_example_run_global_timer_SM.py b/examples/minimal_example_run_global_timer_SM.py index 36e2dfbe..f56dedbc 100644 --- a/examples/minimal_example_run_global_timer_SM.py +++ b/examples/minimal_example_run_global_timer_SM.py @@ -1,30 +1,32 @@ -from bpod_core.fsm import StateMachine -from bpod_core.bpod import Bpod import logging import sys +from bpod_core.bpod import Bpod +from bpod_core.fsm import StateMachine -LOG_FILE = "bpod_debug.log" +LOG_FILE = 'bpod_debug.log' logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8'), - logging.StreamHandler(sys.stdout) # This also keeps logs appearing in the terminal - ] + logging.StreamHandler( + sys.stdout + ), # This also keeps logs appearing in the terminal + ], ) # 3. Specifically ensure the bpod_core library is set to DEBUG logging.getLogger('bpod_core').setLevel(logging.DEBUG) -print(f"Logging initialized. All Bpod traffic will be saved to: {LOG_FILE}") +print(f'Logging initialized. All Bpod traffic will be saved to: {LOG_FILE}') fsm = StateMachine() fsm.set_global_timer( - index=0, # this is 0 indexed - duration=5 + index=0, # this is 0 indexed + duration=5, ) @@ -32,14 +34,14 @@ name='StartGlobalTimer', timer=0.25, transitions={'Tup': 'Port1Light'}, - actions={'GlobalTimerTrig': 1}, # this is 1 indexed + actions={'GlobalTimerTrig': 1}, # this is 1 indexed ) fsm.add_state( name='Port1Light', timer=0.25, transitions={ 'Tup': 'Port3Light', - 'GlobalTimer1_End': '>exit', # this is 1 indexed + 'GlobalTimer1_End': '>exit', # this is 1 indexed }, actions={'PWM1': 255}, ) @@ -48,20 +50,19 @@ timer=0.25, transitions={ 'Tup': 'Port1Light', - 'GlobalTimer1_End': '>exit', # this is 1 indexed + 'GlobalTimer1_End': '>exit', # this is 1 indexed }, actions={'PWM3': 255}, ) - with Bpod() as bpod: - print(f"Connected to Bpod!") - print(f"Found Bpod on port {bpod.serial0.port}") - print(f"Firmware Version: {bpod.version.firmware}") - print(f"Hardware Version: {bpod.version.machine}") - print("Send State machine.") + print('Connected to Bpod!') + print(f'Found Bpod on port {bpod.serial0.port}') + print(f'Firmware Version: {bpod.version.firmware}') + print(f'Hardware Version: {bpod.version.machine}') + print('Send State machine.') bpod.send_state_machine(fsm) - print("Run State machine.") + print('Run State machine.') bpod.run_state_machine() - print("State machine finished.") \ No newline at end of file + print('State machine finished.') From c3d0d097757b38bb04fbfe0e14dfad53099d2f5f Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Tue, 27 Jan 2026 19:19:25 +0000 Subject: [PATCH 6/8] reorganize examples --- docs/source/conf.py | 4 +- examples/Graph_global_counter_SM.ipynb | 222 ------------------ .../graph_state_machine.ipynb | 99 ++++++++ .../minimal_example_run_global_counter_SM.py | 73 ------ .../minimal_example_run_global_timer_SM.py | 68 ------ examples/minimal_examples/run_global_timer.py | 49 ++++ .../{ => state_machines}/back_operator.py | 0 .../bnc_triggered_state_change.py | 0 examples/{ => state_machines}/conditions.py | 0 .../{ => state_machines}/global_counters.py | 8 +- .../{ => state_machines}/global_timers.py | 9 +- .../{ => state_machines}/light_chasing.py | 0 examples/{ => state_machines}/two_choice.py | 0 pyproject.toml | 2 +- 14 files changed, 159 insertions(+), 375 deletions(-) delete mode 100644 examples/Graph_global_counter_SM.ipynb create mode 100644 examples/ipython_notebooks/graph_state_machine.ipynb delete mode 100644 examples/minimal_example_run_global_counter_SM.py delete mode 100644 examples/minimal_example_run_global_timer_SM.py create mode 100644 examples/minimal_examples/run_global_timer.py rename examples/{ => state_machines}/back_operator.py (100%) rename examples/{ => state_machines}/bnc_triggered_state_change.py (100%) rename examples/{ => state_machines}/conditions.py (100%) rename examples/{ => state_machines}/global_counters.py (81%) rename examples/{ => state_machines}/global_timers.py (71%) rename examples/{ => state_machines}/light_chasing.py (100%) rename examples/{ => state_machines}/two_choice.py (100%) diff --git a/docs/source/conf.py b/docs/source/conf.py index 4be9786f..7684a527 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -19,7 +19,7 @@ def generate_fsm_examples(app): return # Create docs/source/state_machines/examples/ with one page per example - examples_source_path = project_root / 'examples' + examples_source_path = project_root / 'examples' / 'state_machines' examples_target_path = docs_source_path / 'state_machines' / 'examples' examples_target_path.mkdir(parents=True, exist_ok=True) example_files = sorted(examples_source_path.glob('*.py'), key=lambda f: f.name) @@ -63,7 +63,7 @@ def generate_fsm_examples(app): '', ' .. tab-item:: Python', '', - f' .. literalinclude:: ../../../../examples/{fn.name}', + f' .. literalinclude:: ../../../../examples/state_machines/{fn.name}', ' :language: python', ' :start-at: from bpod_core.', '', diff --git a/examples/Graph_global_counter_SM.ipynb b/examples/Graph_global_counter_SM.ipynb deleted file mode 100644 index e8a80b05..00000000 --- a/examples/Graph_global_counter_SM.ipynb +++ /dev/null @@ -1,222 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e8a973cd", - "metadata": {}, - "outputs": [], - "source": [ - "from bpod_core.fsm import StateMachine" - ] - }, - { - "cell_type": "markdown", - "id": "09331e75", - "metadata": {}, - "source": [ - "### Define a state machine" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "1dfc3950", - "metadata": {}, - "outputs": [], - "source": [ - "fsm = StateMachine()\n", - "\n", - "fsm.set_global_timer(\n", - " index=1,\n", - " duration=5\n", - ")\n", - "\n", - "\n", - "fsm.add_state(\n", - " name='StartGlobalTimer',\n", - " timer=0.25,\n", - " transitions={'Tup': 'Port1Light'},\n", - " actions={'GlobalTimerTrig': 1},\n", - ")\n", - "fsm.add_state(\n", - " name='Port1Light',\n", - " timer=0.25,\n", - " transitions={\n", - " 'Tup': 'Port3Light',\n", - " 'GlobalTimer1_End': '>exit',\n", - " },\n", - " actions={'PWM1': 255},\n", - ")\n", - "fsm.add_state(\n", - " name='Port3Light',\n", - " timer=0.25,\n", - " transitions={\n", - " 'Tup': 'Port1Light',\n", - " 'GlobalTimer1_End': '>exit',\n", - " },\n", - " actions={'PWM3': 255},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "a32cfda8", - "metadata": {}, - "source": [ - "### Display the statemechine for easy verification" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2c77e81e", - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "State Machine\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "StartGlobalTimer\n", - "\n", - "\n", - "StartGlobalTimer  \n", - "\n", - "0.25 s\n", - "GlobalTimerTrig\n", - "1\n", - "\n", - "\n", - "\n", - "\n", - "->StartGlobalTimer\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Port1Light\n", - "\n", - "\n", - "Port1Light  \n", - "\n", - "0.25 s\n", - "PWM1\n", - "255\n", - "\n", - "\n", - "\n", - "\n", - "StartGlobalTimer->Port1Light\n", - "\n", - "\n", - "Tup\n", - "\n", - "\n", - "\n", - "exit\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Port1Light->exit\n", - "\n", - "\n", - "GlobalTimer1_End\n", - "\n", - "\n", - "\n", - "Port3Light\n", - "\n", - "\n", - "Port3Light  \n", - "\n", - "0.25 s\n", - "PWM3\n", - "255\n", - "\n", - "\n", - "\n", - "\n", - "Port1Light->Port3Light\n", - "\n", - "\n", - "Tup\n", - "\n", - "\n", - "\n", - "Port3Light->exit\n", - "\n", - "\n", - "GlobalTimer1_End\n", - "\n", - "\n", - "\n", - "Port3Light->Port1Light\n", - "\n", - "\n", - "Tup\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "fsm.to_digraph() " - ] - }, - { - "cell_type": "markdown", - "id": "ecf44170", - "metadata": {}, - "source": [ - "### Running a state machine / bpod communication does not seem to work from a jupyter notebook..." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "bpod-core", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/ipython_notebooks/graph_state_machine.ipynb b/examples/ipython_notebooks/graph_state_machine.ipynb new file mode 100644 index 00000000..180d71fb --- /dev/null +++ b/examples/ipython_notebooks/graph_state_machine.ipynb @@ -0,0 +1,99 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "e8a973cd", + "metadata": {}, + "source": [ + "from bpod_core.fsm import StateMachine" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "09331e75", + "metadata": {}, + "source": [ + "### Define a state machine" + ] + }, + { + "cell_type": "code", + "id": "1dfc3950", + "metadata": {}, + "source": [ + "fsm = StateMachine()\n", + "\n", + "fsm.set_global_timer(\n", + " index=0,\n", + " duration=5\n", + ")\n", + "\n", + "fsm.add_state(\n", + " name='StartGlobalTimer',\n", + " timer=0.25,\n", + " transitions={'Tup': 'Port1Light'},\n", + " actions={'GlobalTimerTrig': 0},\n", + ")\n", + "fsm.add_state(\n", + " name='Port1Light',\n", + " timer=0.25,\n", + " transitions={\n", + " 'Tup': 'Port3Light',\n", + " 'GlobalTimer0_End': '>exit',\n", + " },\n", + " actions={'PWM1': 255},\n", + ")\n", + "fsm.add_state(\n", + " name='Port3Light',\n", + " timer=0.25,\n", + " transitions={\n", + " 'Tup': 'Port1Light',\n", + " 'GlobalTimer0_End': '>exit',\n", + " },\n", + " actions={'PWM3': 255},\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "a32cfda8", + "metadata": {}, + "source": [ + "### Display the statemechine for easy verification" + ] + }, + { + "cell_type": "code", + "id": "2c77e81e", + "metadata": {}, + "source": "fsm.to_digraph()", + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "kernelspec": { + "display_name": "bpod-core", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/minimal_example_run_global_counter_SM.py b/examples/minimal_example_run_global_counter_SM.py deleted file mode 100644 index 03ee238c..00000000 --- a/examples/minimal_example_run_global_counter_SM.py +++ /dev/null @@ -1,73 +0,0 @@ -import logging -import sys - -from bpod_core.bpod import Bpod -from bpod_core.fsm import StateMachine - -LOG_FILE = 'bpod_debug.log' - -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8'), - logging.StreamHandler( - sys.stdout - ), # This also keeps logs appearing in the terminal - ], -) - -# 3. Specifically ensure the bpod_core library is set to DEBUG -logging.getLogger('bpod_core').setLevel(logging.DEBUG) - -print(f'Logging initialized. All Bpod traffic will be saved to: {LOG_FILE}') - -fsm = StateMachine() - -fsm.set_global_counter( - index=0, # this is zero based, so this is Global Counter 1 - event='BNC1_High', - threshold=5, -) - -fsm.add_state( - name='InitialDelay', - timer=2, - transitions={'Tup': 'ResetGlobalCounter'}, - actions={'PWM2': 255}, -) -fsm.add_state( - name='ResetGlobalCounter', - transitions={'Tup': 'Port1Light'}, - actions={'GlobalCounterReset': 1}, -) -fsm.add_state( - name='Port1Light', - timer=0.25, - transitions={ - 'Tup': 'Port3Light', - 'GlobalCounter1_End': '>exit', - }, - actions={'PWM1': 255}, -) -fsm.add_state( - name='Port3Light', - timer=0.25, - transitions={ - 'Tup': 'Port1Light', - 'GlobalCounter1_End': '>exit', - }, - actions={'PWM3': 255}, -) - - -with Bpod() as bpod: - print('Connected to Bpod!') - print(f'Found Bpod on port {bpod.serial0.port}') - print(f'Firmware Version: {bpod.version.firmware}') - print(f'Hardware Version: {bpod.version.machine}') - print('Send State machine.') - bpod.send_state_machine(fsm) - print('Run State machine.') - bpod.run_state_machine() - print('State machine finished.') diff --git a/examples/minimal_example_run_global_timer_SM.py b/examples/minimal_example_run_global_timer_SM.py deleted file mode 100644 index f56dedbc..00000000 --- a/examples/minimal_example_run_global_timer_SM.py +++ /dev/null @@ -1,68 +0,0 @@ -import logging -import sys - -from bpod_core.bpod import Bpod -from bpod_core.fsm import StateMachine - -LOG_FILE = 'bpod_debug.log' - -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8'), - logging.StreamHandler( - sys.stdout - ), # This also keeps logs appearing in the terminal - ], -) - -# 3. Specifically ensure the bpod_core library is set to DEBUG -logging.getLogger('bpod_core').setLevel(logging.DEBUG) - -print(f'Logging initialized. All Bpod traffic will be saved to: {LOG_FILE}') - -fsm = StateMachine() - -fsm.set_global_timer( - index=0, # this is 0 indexed - duration=5, -) - - -fsm.add_state( - name='StartGlobalTimer', - timer=0.25, - transitions={'Tup': 'Port1Light'}, - actions={'GlobalTimerTrig': 1}, # this is 1 indexed -) -fsm.add_state( - name='Port1Light', - timer=0.25, - transitions={ - 'Tup': 'Port3Light', - 'GlobalTimer1_End': '>exit', # this is 1 indexed - }, - actions={'PWM1': 255}, -) -fsm.add_state( - name='Port3Light', - timer=0.25, - transitions={ - 'Tup': 'Port1Light', - 'GlobalTimer1_End': '>exit', # this is 1 indexed - }, - actions={'PWM3': 255}, -) - - -with Bpod() as bpod: - print('Connected to Bpod!') - print(f'Found Bpod on port {bpod.serial0.port}') - print(f'Firmware Version: {bpod.version.firmware}') - print(f'Hardware Version: {bpod.version.machine}') - print('Send State machine.') - bpod.send_state_machine(fsm) - print('Run State machine.') - bpod.run_state_machine() - print('State machine finished.') diff --git a/examples/minimal_examples/run_global_timer.py b/examples/minimal_examples/run_global_timer.py new file mode 100644 index 00000000..b3e6fc1b --- /dev/null +++ b/examples/minimal_examples/run_global_timer.py @@ -0,0 +1,49 @@ +"""Global Timer Example. + +This example demonstrates how to define a state machine with a global timer and run it +on a Bpod with debug logging enabled. The state machine alternates LEDs of Port 1 and +Port 3 every 250 ms until a 5-second global timer expires, then exits the state machine. +""" + +import logging + +from bpod_core.bpod import Bpod +from bpod_core.fsm import StateMachine + +# configure debug logging +logging.basicConfig(level=logging.DEBUG) + +# create a new StateMachine instance and configure a 5-second global timer +fsm = StateMachine() +fsm.set_global_timer(index=0, duration=5) + +# define the state machine's states +fsm.add_state( + name='StartGlobalTimer', + timer=0.25, + transitions={'Tup': 'Port1Light'}, + actions={'GlobalTimerTrig': 0}, +) +fsm.add_state( + name='Port1Light', + timer=0.25, + transitions={ + 'Tup': 'Port3Light', + 'GlobalTimer0_End': '>exit', + }, + actions={'PWM1': 255}, +) +fsm.add_state( + name='Port3Light', + timer=0.25, + transitions={ + 'Tup': 'Port1Light', + 'GlobalTimer0_End': '>exit', + }, + actions={'PWM3': 255}, +) + +# connect to the Bpod, send the state machine, and run it +with Bpod() as bpod: + bpod.send_state_machine(fsm) + bpod.run_state_machine() diff --git a/examples/back_operator.py b/examples/state_machines/back_operator.py similarity index 100% rename from examples/back_operator.py rename to examples/state_machines/back_operator.py diff --git a/examples/bnc_triggered_state_change.py b/examples/state_machines/bnc_triggered_state_change.py similarity index 100% rename from examples/bnc_triggered_state_change.py rename to examples/state_machines/bnc_triggered_state_change.py diff --git a/examples/conditions.py b/examples/state_machines/conditions.py similarity index 100% rename from examples/conditions.py rename to examples/state_machines/conditions.py diff --git a/examples/global_counters.py b/examples/state_machines/global_counters.py similarity index 81% rename from examples/global_counters.py rename to examples/state_machines/global_counters.py index 6e0bed4b..3ce22293 100644 --- a/examples/global_counters.py +++ b/examples/state_machines/global_counters.py @@ -9,7 +9,7 @@ fsm = StateMachine() fsm.set_global_counter( - index=0, # this is zero based, so this is Global Counter 1 + index=0, event='Port1High', threshold=5, ) @@ -23,14 +23,14 @@ fsm.add_state( name='ResetGlobalCounter', transitions={'Tup': 'Port1Light'}, - actions={'GlobalCounterReset': 1}, + actions={'GlobalCounterReset': 0}, ) fsm.add_state( name='Port1Light', timer=0.25, transitions={ 'Tup': 'Port3Light', - 'GlobalCounter1_End': '>exit', + 'GlobalCounter0_End': '>exit', }, actions={'PWM1': 255}, ) @@ -39,7 +39,7 @@ timer=0.25, transitions={ 'Tup': 'Port1Light', - 'GlobalCounter1_End': '>exit', + 'GlobalCounter0_End': '>exit', }, actions={'PWM3': 255}, ) diff --git a/examples/global_timers.py b/examples/state_machines/global_timers.py similarity index 71% rename from examples/global_timers.py rename to examples/state_machines/global_timers.py index 7a555e35..21ab25c7 100644 --- a/examples/global_timers.py +++ b/examples/state_machines/global_timers.py @@ -8,23 +8,22 @@ fsm = StateMachine() fsm.set_global_timer( - index=0, # this is zero based, so this is Global Timer 1 + index=0, duration=5, ) - fsm.add_state( name='StartGlobalTimer', timer=0.25, transitions={'Tup': 'Port1Light'}, - actions={'GlobalTimerTrig': 1}, # this is 1 based + actions={'GlobalTimerTrig': 0}, ) fsm.add_state( name='Port1Light', timer=0.25, transitions={ 'Tup': 'Port3Light', - 'GlobalTimer1_End': '>exit', # this is 1 based + 'GlobalTimer0_End': '>exit', }, actions={'PWM1': 255}, ) @@ -33,7 +32,7 @@ timer=0.25, transitions={ 'Tup': 'Port1Light', - 'GlobalTimer1_End': '>exit', # this is 1 based + 'GlobalTimer0_End': '>exit', }, actions={'PWM3': 255}, ) diff --git a/examples/light_chasing.py b/examples/state_machines/light_chasing.py similarity index 100% rename from examples/light_chasing.py rename to examples/state_machines/light_chasing.py diff --git a/examples/two_choice.py b/examples/state_machines/two_choice.py similarity index 100% rename from examples/two_choice.py rename to examples/state_machines/two_choice.py diff --git a/pyproject.toml b/pyproject.toml index f674a762..a2944e37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ source-include = [ "tests/*.py", "schema/*.json", "docs/source/**/*", - "examples/*" + "examples/**/*" ] source-exclude = [ "docs/source/api", From 20f871416a02cac3006794538858a8c9fcfd228c Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Tue, 27 Jan 2026 19:33:00 +0000 Subject: [PATCH 7/8] refactor: remove redundant debug checks and logs --- bpod_core/bpod.py | 39 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/bpod_core/bpod.py b/bpod_core/bpod.py index 81147b8c..7bf5251b 100644 --- a/bpod_core/bpod.py +++ b/bpod_core/bpod.py @@ -218,26 +218,22 @@ def run(self) -> None: target_exit = np.uint8(state_transitions.shape[0]) target_back = np.uint8(255) use_back_op = self._use_back_op + event_names = self._event_names # create buffers for repeated serial reads opcode_buf = bytearray(2) # buffer for opcodes event_data_buf = bytearray(259) # max 255 events + 4 bytes for n_cycles - # should we use debug logging? - debug = logger.isEnabledFor(logging.DEBUG) - # confirm the state machine if self._confirm_fsm: if serial.read(1) != b'\x01': raise RuntimeError(f'State machine #{index} was not confirmed by Bpod') - if debug: - logger.debug('State machine #%d confirmed by Bpod', index) + logger.debug('State machine #%d confirmed by Bpod', index) # read the start time of the state machine t0 = serial.read_uint64() - if debug: - logger.debug('%d µs: Starting state machine #%d', t0, index) - logger.debug('%d µs: State %d', t0, current_state) + logger.debug('%d µs: Starting state machine #%d', t0, index) + logger.debug('%d µs: State %d', t0, current_state) # TODO: handle start of state machine # TODO: handle start of state @@ -259,13 +255,10 @@ def run(self) -> None: # handle each event events = event_data_view[:param] for event in events: - if debug and event != 255: # exit event - event_name = ( - self._event_names[event] - if event < len(self._event_names) - else 'Unknown' + if event != 255: + logger.debug( + '%d µs: Event %d - %s)', micros, event, event_names[event] ) - logger.debug('%d µs: Event: %s (%d)', micros, event_name, event) # TODO: handle event # handle state transitions / exit event @@ -285,14 +278,12 @@ def run(self) -> None: previous_state = current_state current_state = target_state # TODO: handle start of state - if debug: - logger.debug('%d µs: State %d', micros, current_state) + logger.debug('%d µs: State %d', micros, current_state) break # only handle the first state transition elif opcode == 2: # handle softcodes param -= 1 - if debug: - logger.debug('Softcode %d', param) + logger.debug('Softcode %d', param) softcode_handler(param) else: @@ -301,10 +292,9 @@ def run(self) -> None: # exit state machine # read 12 bytes: cycles (uInt32) and micros (uInt64) cycles, micros = self._struct_exit.unpack(serial.read(12)) - if debug: - logger.debug( - '%d µs: Ending state machine #%d (%d cycles)', micros, index, cycles - ) + logger.debug( + '%d µs: Ending state machine #%d (%d cycles)', micros, index, cycles + ) # TODO: handle end of state machine @@ -804,7 +794,6 @@ def _compile_event_names(self) -> None: ]: self.event_names.extend(event_name.format(i) for i in range(n)) self.event_names.append('Tup') - logger.debug('Compiled event names: %s', self.event_names) def _compile_output_actions(self) -> None: """Compile the list of output actions supported by the Bpod hardware.""" @@ -1269,10 +1258,6 @@ def append_events(event0: str, event1: str) -> None: f' Date: Tue, 27 Jan 2026 19:37:30 +0000 Subject: [PATCH 8/8] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 409d747b..0769a664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - convenience functions for reading and writing integers in `com.ExtendedSerial`. - `misc.extend_packed` for extending a bytearray by multiple values of the same format. - `misc.DocstringInheritanceMixin` for inheriting docstrings from base classes. +- more examples. ### Changed +- more verbose debug logging during state machine runs. - replace unmaintained `appdirs` dependency with `platformdirs`. - switch to zero-based indexing for global counters, timers, conditions and soft-codes. - added file locking to `misc.SettingsDict`.