Pioreactor / pioreactor

Hardware and software for accessible, extensible, and scalable bioreactors. Built on Raspberry Pi.
https://pioreactor.com
MIT License
94 stars 9 forks source link

Hardware versions #100

Closed CamDavidsonPilon closed 2 years ago

CamDavidsonPilon commented 3 years ago

We can use the hardware, I2C channels, and EEPROM to know what version of the HAT was are using. How should the software change?

CamDavidsonPilon commented 2 years ago

This is now an immediate issue.

List of current changes needed between dev hardware versions:

  1. pin 1 in PWM_TO_PIN
  2. ADS11115 vs ADS1015
  3. Heating PWM is fixed (should be removed from config)
  4. eeprom exists

Let's look at a simple example: PWM pin 1 changed from 6 to 17 between versions 0.0 and 0.1. How should we be able to support both? (Yes, we must support all versions of boards)

(master, as of writing)

PWM_TO_PIN = {
    # map between PCB labels and GPIO pins
    1: 6,  # TODO: 17
    2: 13,  # hardware PWM1 available
    3: 16,
    4: 12,  # hardware PWM0 available
    5: 18,
}

Have some mapping in the code?

from pioreactor.version import update_for_version

PWM_TO_PIN = {
    # map between PCB labels and GPIO pins
    1: update_for_version['pwm_to_pin1']
    2: 13,  # hardware PWM1 available
    3: 16,
    4: 12,  # hardware PWM0 available
    5: 18,
}

### version.py

def get_hardware_version():
   ... logic for hardware version here

if get_hardware_version() == 0.0:
   update_for_version = {
     pwm_to_pin1: 6,
     ...
   }
elif get_hardware_version() == 0.1:
   update_for_version = {
     pwm_to_pin1: 17,
     ...
   }
...

It's very simple, which I like, but will this cover all required cases? A problem may be that over time, as new versions come out, this update_for_version gets large and starts to permeate more and more of the code base. OTOH, YAGNI.


Instead of dicts, where an update of a hardware thing needs to be applied to every dict, another idea is a hierarchy of classes, each modifying / replacing the previous one.

class _Base:
    # defaults here
    pwm_to_pin1 = 6
    ads = "ads1115"
    heater_fixed = False
    ...
    def __getitem__(self, key):
        return getattr(self, key)

class Version0P0(_Base):
    pass

class Version0P1(Version0P0):
    pwm_to_pin1 = 17
    ads = "ads1015"
    heater_fixed = True

class Version0P2(Version0P1):
    ads = "ads1115"

....

This is a bit more abstracted. It's easier to reason about "diffs" between versions (good), but seeing the current state is harder. The if statement is still needed.

CamDavidsonPilon commented 2 years ago

Another option is to have different code bases. This seems really complicated, and limits swapping HATs between RPis (since the hard disk and the HAT are on different devices).