MX340 CircuitPython: Internals and Context Manager

I’ve got CircuitPython code to interface with control panel of a Canon MX340 multi-function inkjet, and gradually refining my crude experimental code into an actual reusable CircuitPython library. Using events for synchronization and exposing property setters was just the start.

A major change is to separate communication infrastructure from code that utilizes such infrastructure. For example, I’ve had code in there blinking the “In Use/Memory” LED to let me know everything is still running. I consider code to transmit the LED control bytes to the NEC K13988 as infrastructure, and the timing loop to toggle LED on and off to be application code. I have a separate asynchronous task that continually updates the LCD content for the same reason, and that should be separated out as well. During the experimental phase I could just mix code in there in a big jumble. A global space where any bit of code can call any other bit of code. But now I have to think about which methods should be part of the API and which are internal implementation.

To reflect this division, a minor change is to rename several methods with a leading underscore. This is a “weak internal” indicator by Python convention. They are understood to be for internal use though there’s no language level mechanism preventing callers from accessing them. I guess this means they can be changed without becoming a public API breaking change? Reading the linked section of PEP 8, I saw there’s also a way to declare public interface with __all__. As per Python tutorial on module imports, this works in conjunction with “from [module] import *“. I don’t understand enough about Python modules yet to know if it’s applicable to my project.

After I performed this separation, I can clearly see a set of startup and cleanup tasks that have to run before and after application code. This sounds like a job for context managers! I can set up all the infrastructure by using an async with statement, and all application code can run within its scope. I first tried to write my context manager with the generator syntax that has a single yield for application code, but that failed: I need to decorate such a generator with @contextmanager to apply supporting infrastructure from contextlib, which has not been ported to CircuitPython. Oops! Fortunately I can still implement a context manager by falling back to using special method names __aenter__() and __aexit__(). Once implemented, I can apply even more lessons from my recent study session, implementing a clean separation between my test code from my NEC K13988 infrastructure.

I was pretty pleased with myself with how well this is going when I realized I completely forgot about key press events.

Leave a comment