0

I'm building a microcontroller project which is programmed in MicroPython, and I'm struggling to find a sensible way to extend my code to support multiple operating modes.

Currently it looks like this: I have a base hardware class which instantiates all the different hardware interfaces, and loads information from a configuration file, and an application class which does the useful work - they're separated primarily to stop them getting too unweildy.

class HardwareClass():
    def __init__(self):
        self.display = self.initialise_display()
        self.sensor = self.initialise_sensor()
        self.config = self.load_config_from_sd()

    def ...

class ApplicationClass(HardwareClass):
    def run(self):
        while (True)
            # do useful work
            pass

app = ApplicationClass()
app.run()

What I'd like to do is have modes for testing and normal operation which are selected by a value in the configuration file.

My first thought was to simply write a different ApplicationClass for each mode - that means they can re-use all the hardware interfaces that were initialised at boot, and each will have a different run() method.

However, as I can't just do this:

import HardwareClass

class TestModeClass(HardwareClass):
    def run(self):
        # do testing

class RunModeClass(HardwareClass):
    def run(self):
        while (True)
            # do useful work

because I don't know which class I'll need until the config loading is done.

I could define run() for each mode separately and import them at runtime, but I've seen that has issues, as well as making it difficult for the IDE to know whether I'm referencing valid class members (ie. the different bits of hardware) in each of the different run methods (or functions).

Any suggestions for how you'd arrange an application like this?

3
  • 1
    I don't think there's enough information here for us to answer the question. What are the practical differences between the "testing" and "production" classes? Could you manage that with a relatively small number of conditionals? Can you inherit from ApplicationClass and override a small number of methods but keep the rest of the functionality? Commented Jun 5, 2023 at 17:34
  • The modes a very different (a test mode that works with an interactive shell, a mode to help tune a PID loop, and the main running program which operates automatically) and complex enough that I'd want to keep them in separate files for maintainability. The issue with simple inheritance from the base class is the decision about which to use comes after the object has been instantiated (which is when the configuration is loaded). Is it possible to "upgrade" and object from it's base class to a derived class? (or something similar). Commented Jun 6, 2023 at 14:08
  • 1
    Both TestModeClass and RunModeClass are derived classes from HardwareClass in your code, so they are instantiated first, then they have to initialize your base class by themselves. You have to create a class that reads the config and then instantiates one of the other classes and returns the object it created. That is a pattern called "abstract factory". See link Commented Jun 6, 2023 at 14:41

1 Answer 1

0

This is what I've settled on - the hints from @larsks & @peter-i were helpful, and gave me a few more things to read around.

Although I understand that adding methods to classes like this isn't recommended, but it makes the most sense to me, and there will only be one instance so should be safe in this situation.

from abc import abstractmethod

# Base hardware class to initialise all the hardware
class Hardware:
    def __init__(self):
        self.display = self.initialise_display()
        self.sensor = self.initialise_sensor()
        self.actuator = self.initialise_actuator()
        self.config = self.load_config_from_sd()
    
    # Ensure derived classes implement a run() method
    @abstractmethod
    def run(self):
        pass

    def ...

# Concrete mode classes, never actually instatiated,
# but functions drawn from then can refer to members of the "grandparent" Hardware class
class TestMode(Mode):
    def run(self):
        # do testing
        print(self.sensor)

class RunMode(Mode):
    def run(self):
        while (True)
            # do useful work
            self.move_actuator(self.actuator)
    
    def ...
            
            
app = Hardware()

if app.config['mode'] == test:
    Hardware.run = TestMode.run
else
    Hardware.run = RunMode.run
    
app.run()

Annoyingly abc isn't a MicroPython built-in library, but you can at least use a placeholder class to avoid runtime errors, while still allowing an IDE to highlight missing methods.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.