From bc22e3fdc680460ab771d632dcc9194eb7931091 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 17 Nov 2023 11:23:32 +0000 Subject: [PATCH 1/8] Port to gpiod/gpiodevice. --- grow/__init__.py | 41 +++++++++-------- grow/pump.py | 40 ++++++++++------- grow/pwm.py | 106 ++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 16 +++++-- tests/conftest.py | 45 +++++++------------ tests/test_lock.py | 6 +-- tests/test_setup.py | 6 +-- 7 files changed, 187 insertions(+), 73 deletions(-) create mode 100644 grow/pwm.py diff --git a/grow/__init__.py b/grow/__init__.py index 77ac843..0a72521 100644 --- a/grow/__init__.py +++ b/grow/__init__.py @@ -4,18 +4,28 @@ import threading import time -import RPi.GPIO as GPIO +import gpiodevice + +from . import pwm + +PLATFORMS = { + "Raspberry Pi 5": {"piezo": ("PIN33", pwm.OUTL)}, + "Raspberry Pi 4": {"piezo": ("GPIO13", pwm.OUTL)}, +} class Piezo(): - def __init__(self, gpio_pin=13): - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - GPIO.setup(gpio_pin, GPIO.OUT, initial=GPIO.LOW) - self.pwm = GPIO.PWM(gpio_pin, 440) - self.pwm.start(0) + def __init__(self, gpio_pin=None): + + if gpio_pin is None: + gpio_pin = gpiodevice.get_pins_for_platform(PLATFORMS)[0] + elif isinstance(gpio_pin, str): + gpio_pin = gpiodevice.get_pin(gpio_pin, "piezo", pwm.OUTL) + + self.pwm = pwm.PWM(gpio_pin) self._timeout = None - atexit.register(self._exit) + pwm.PWM.start_thread() + atexit.register(pwm.PWM.stop_thread) def frequency(self, value): """Change the piezo frequency. @@ -23,17 +33,15 @@ def frequency(self, value): Loosely corresponds to musical pitch, if you suspend disbelief. """ - self.pwm.ChangeFrequency(value) + self.pwm.set_frequency(value) - def start(self, frequency=None): + def start(self, frequency): """Start the piezo. - Sets the Duty Cycle to 100% + Sets the Duty Cycle to 50% """ - if frequency is not None: - self.frequency(frequency) - self.pwm.ChangeDutyCycle(1) + self.pwm.start(frequency=frequency, duty_cycle=0.5) def stop(self): """Stop the piezo. @@ -41,7 +49,7 @@ def stop(self): Sets the Duty Cycle to 0% """ - self.pwm.ChangeDutyCycle(0) + self.pwm.stop() def beep(self, frequency=440, timeout=0.1, blocking=True, force=False): """Beep the piezo for time seconds. @@ -67,6 +75,3 @@ def beep(self, frequency=440, timeout=0.1, blocking=True, force=False): self.start(frequency=frequency) self._timeout.start() return True - - def _exit(self): - self.pwm.stop() diff --git a/grow/pump.py b/grow/pump.py index fe11ded..7c874d3 100644 --- a/grow/pump.py +++ b/grow/pump.py @@ -2,13 +2,20 @@ import threading import time -import RPi.GPIO as GPIO +import gpiodevice -PUMP_1_PIN = 17 -PUMP_2_PIN = 27 -PUMP_3_PIN = 22 +from . import pwm + +PUMP_1_PIN = "PIN11" # 17 +PUMP_2_PIN = "PIN13" # 27 +PUMP_3_PIN = "PIN15" # 22 PUMP_PWM_FREQ = 10000 -PUMP_MAX_DUTY = 90 +PUMP_MAX_DUTY = 0.9 + +PLATFORMS = { + "Raspberry Pi 5": {"pump1": ("PIN11", pwm.OUTL), "pump2": ("PIN12", pwm.OUTL), "pump3": ("PIN15", pwm.OUTL)}, + "Raspberry Pi 4": {"pump1": ("GPIO17", pwm.OUTL), "pump2": ("GPIO27", pwm.OUTL), "pump3": ("GPIO22", pwm.OUTL)}, +} global_lock = threading.Lock() @@ -17,6 +24,8 @@ class Pump(object): """Grow pump driver.""" + PINS = None + def __init__(self, channel=1): """Create a new pump. @@ -26,21 +35,18 @@ def __init__(self, channel=1): """ - self._gpio_pin = [PUMP_1_PIN, PUMP_2_PIN, PUMP_3_PIN][channel - 1] + if Pump.PINS is None: + Pump.PINS = gpiodevice.get_pins_for_platform(PLATFORMS) - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - GPIO.setup(self._gpio_pin, GPIO.OUT, initial=GPIO.LOW) - self._pwm = GPIO.PWM(self._gpio_pin, PUMP_PWM_FREQ) - self._pwm.start(0) + self._gpio_pin = Pump.PINS[channel - 1] - self._timeout = None + self._pwm = pwm.PWM(self._gpio_pin, PUMP_PWM_FREQ) + self._pwm.start(0) - atexit.register(self._stop) + pwm.PWM.start_thread() + atexit.register(pwm.PWM.stop_thread) - def _stop(self): - self._pwm.stop(0) - GPIO.setup(self._gpio_pin, GPIO.IN) + self._timeout = None def set_speed(self, speed): """Set pump speed (PWM duty cycle).""" @@ -52,7 +58,7 @@ def set_speed(self, speed): elif not global_lock.acquire(blocking=False): return False - self._pwm.ChangeDutyCycle(int(PUMP_MAX_DUTY * speed)) + self._pwm.set_duty_cycle(PUMP_MAX_DUTY * speed) self._speed = speed return True diff --git a/grow/pwm.py b/grow/pwm.py new file mode 100644 index 0000000..233898e --- /dev/null +++ b/grow/pwm.py @@ -0,0 +1,106 @@ +import time +from threading import Thread + +import gpiod +import gpiodevice +from gpiod.line import Direction, Value + +OUTL = gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.INACTIVE) + + +class PWM: + _pwms: list = [] + _t_pwm: Thread = None + _pwm_running: bool = False + + @staticmethod + def start_thread(): + if PWM._t_pwm is None: + PWM._pwm_running = True + PWM._t_pwm = Thread(target=PWM._run) + PWM._t_pwm.start() + + @staticmethod + def stop_thread(): + if PWM._t_pwm is not None: + PWM._pwm_running = False + PWM._t_pwm.join() + PWM._t_pwm = None + + @staticmethod + def _add(pwm): + PWM._pwms.append(pwm) + + @staticmethod + def _remove(pwm): + index = PWM._pwms.index(pwm) + del PWM._pwms[index] + if len(PWM._pwms) == 0: + PWM.stop_thread() + + @staticmethod + def _run(): + while PWM._pwm_running: + PWM.run() + + @staticmethod + def run(): + for pwm in PWM._pwms: + pwm.next(time.time()) + + def __init__(self, pin, frequency=0, duty_cycle=0, lines=None, offset=None): + self.duty_cycle = 0 + self.frequency = 0 + self.duty_period = 0 + self.period = 0 + self.running = False + self.time_start = None + self.state = Value.ACTIVE + + self.set_frequency(frequency) + self.set_duty_cycle(duty_cycle) + + if isinstance(pin, tuple): + self.lines, self.offset = pin + else: + self.lines, self.offset = gpiodevice.get_pin(pin, "PWM", OUTL) + + PWM._add(self) + + def set_frequency(self, frequency): + if frequency == 0: + return + self.frequency = frequency + self.period = 1.0 / frequency + self.duty_period = self.duty_cycle * self.period + + def set_duty_cycle(self, duty_cycle): + self.duty_cycle = duty_cycle + self.duty_period = self.duty_cycle * self.period + + def start(self, duty_cycle=None, frequency=None, start_time=None): + if duty_cycle is not None: + self.set_duty_cycle(duty_cycle) + + if frequency is not None: + self.set_frequency(frequency) + + self.time_start = time.time() if start_time is None else start_time + + self.running = True + + def next(self, t): + if not self.running: + return + d = t - self.time_start + d %= self.period + new_state = Value.ACTIVE if d < self.duty_period else Value.INACTIVE + if new_state != self.state: + self.lines.set_value(self.offset, new_state) + self.state = new_state + + def stop(self): + self.running = False + + def __del__(self): + PWM._remove(self) diff --git a/pyproject.toml b/pyproject.toml index 5f1295f..a5a0966 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,8 +36,10 @@ classifiers = [ "Topic :: System :: Hardware", ] dependencies = [ - "ltr559", - "st7735>=0.0.5", + "gpiodevice", + "gpiod>=2.1.3", + "ltr559>=1.0.0", + "st7735>=1.0.0", "pyyaml", "fonts", "font-roboto" @@ -121,5 +123,11 @@ ignore = [ [tool.pimoroni] apt_packages = [] -configtxt = [] -commands = [] +configtxt = [ + "dtoverlay=spi0-cs,cs0_pin=14" # Re-assign CS0 from BCM 8 so that Grow can use it +] +commands = [ + "printf \"Setting up i2c and SPI..\\n\"", + "sudo raspi-config nonint do_spi 0", + "sudo raspi-config nonint do_i2c 0" +] diff --git a/tests/conftest.py b/tests/conftest.py index d1ac05b..3c8915c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,31 +18,20 @@ def __init__(self, i2c_bus): @pytest.fixture(scope='function', autouse=True) def cleanup(): yield None - try: - del sys.modules['grow'] - except KeyError: - pass - try: - del sys.modules['grow.moisture'] - except KeyError: - pass - try: - del sys.modules['grow.pump'] - except KeyError: - pass + for module in ['grow', 'grow.moisture', 'grow.pump']: + try: + del sys.modules[module] + except KeyError: + continue @pytest.fixture(scope='function', autouse=False) def GPIO(): - """Mock RPi.GPIO module.""" - GPIO = mock.MagicMock() - # Fudge for Python < 37 (possibly earlier) - sys.modules['RPi'] = mock.Mock() - sys.modules['RPi'].GPIO = GPIO - sys.modules['RPi.GPIO'] = GPIO - yield GPIO - del sys.modules['RPi'] - del sys.modules['RPi.GPIO'] + """Mock gpiod module.""" + gpiod = mock.MagicMock() + sys.modules['gpiod'] = gpiod + yield gpiod + del sys.modules['gpiod'] @pytest.fixture(scope='function', autouse=False) @@ -55,13 +44,13 @@ def spidev(): @pytest.fixture(scope='function', autouse=False) -def smbus(): - """Mock smbus module.""" - smbus = mock.MagicMock() - smbus.SMBus = SMBusFakeDevice - sys.modules['smbus'] = smbus - yield smbus - del sys.modules['smbus'] +def smbus2(): + """Mock smbus2 module.""" + smbus2 = mock.MagicMock() + smbus2.SMBus = SMBusFakeDevice + sys.modules['smbus2'] = smbus2 + yield smbus2 + del sys.modules['smbus2'] @pytest.fixture(scope='function', autouse=False) diff --git a/tests/test_lock.py b/tests/test_lock.py index 6c263c3..b09c744 100644 --- a/tests/test_lock.py +++ b/tests/test_lock.py @@ -1,7 +1,7 @@ import time -def test_pumps_actually_stop(GPIO, smbus): +def test_pumps_actually_stop(gpiod, smbus2): from grow.pump import Pump ch1 = Pump(channel=1) @@ -11,7 +11,7 @@ def test_pumps_actually_stop(GPIO, smbus): assert ch1.get_speed() == 0 -def test_pumps_are_mutually_exclusive(GPIO, smbus): +def test_pumps_are_mutually_exclusive(gpiod, smbus2): from grow.pump import Pump, global_lock ch1 = Pump(channel=1) @@ -29,7 +29,7 @@ def test_pumps_are_mutually_exclusive(GPIO, smbus): assert ch3.dose(speed=0.5, blocking=False) is False -def test_pumps_run_sequentially(GPIO, smbus): +def test_pumps_run_sequentially(gpiod, smbus2): from grow.pump import Pump, global_lock ch1 = Pump(channel=1) diff --git a/tests/test_setup.py b/tests/test_setup.py index 6ae8523..6ba611f 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1,7 +1,7 @@ import mock -def test_moisture_setup(GPIO, smbus): +def test_moisture_setup(gpiod, smbus2): from grow.moisture import Moisture ch1 = Moisture(channel=1) @@ -15,7 +15,7 @@ def test_moisture_setup(GPIO, smbus): ]) -def test_moisture_read(GPIO, smbus): +def test_moisture_read(gpiod, smbus2): from grow.moisture import Moisture assert Moisture(channel=1).saturation == 1.0 @@ -27,7 +27,7 @@ def test_moisture_read(GPIO, smbus): assert Moisture(channel=3).moisture == 0 -def test_pump_setup(GPIO, smbus): +def test_pump_setup(gpiod, smbus2): from grow.pump import PUMP_PWM_FREQ, Pump ch1 = Pump(channel=1) From 9eda924814cf2475c0891a970d6bc27222447f75 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Nov 2023 15:57:04 +0000 Subject: [PATCH 2/8] Fix VENV_DIR path. --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 26f0f4f..6cc5732 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ DATESTAMP=$(date "+%Y-%m-%d-%H-%M-%S") CONFIG_BACKUP=false APT_HAS_UPDATED=false RESOURCES_TOP_DIR=$HOME/Pimoroni -VENV_BASH_SNIPPET=$RESOURCES_DIR/auto_venv.sh +VENV_BASH_SNIPPET=$RESOURCES_TOP_DIR/auto_venv.sh VENV_DIR=$HOME/.virtualenvs/pimoroni WD=$(pwd) USAGE="./install.sh (--unstable)" @@ -77,7 +77,7 @@ find_config() { venv_bash_snippet() { if [ ! -f "$VENV_BASH_SNIPPET" ]; then cat << EOF > "$VENV_BASH_SNIPPET" -# Add `source $RESOURCES_DIR/auto_venv.sh` to your ~/.bashrc to activate +# Add `source "$VENV_BASH_SNIPPET"` to your ~/.bashrc to activate # the Pimoroni virtual environment automagically! VENV_DIR="$VENV_DIR" if [ ! -f \$VENV_DIR/bin/activate ]; then From dd0e25da5b73df6e241772349eb851db9235855e Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 26 Feb 2025 14:10:47 +0000 Subject: [PATCH 3/8] CI: Fix ruff invocation. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 44c8654..4726cef 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ commands = python -m build --no-isolation python -m twine check dist/* isort --check . - ruff . + ruff check . codespell . deps = check-manifest From d98b69462277af94b1a460af95c7292d042a6008 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 26 Feb 2025 14:11:00 +0000 Subject: [PATCH 4/8] Port moisture to gpiod. --- grow/moisture.py | 72 ++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/grow/moisture.py b/grow/moisture.py index f426e6b..021c80a 100644 --- a/grow/moisture.py +++ b/grow/moisture.py @@ -1,6 +1,13 @@ import time +import select +import threading +from datetime import timedelta +import atexit + +import gpiodevice +from gpiod import LineSettings +from gpiod.line import Edge, Bias -import RPi.GPIO as GPIO MOISTURE_1_PIN = 23 MOISTURE_2_PIN = 8 @@ -25,46 +32,55 @@ def __init__(self, channel=1, wet_point=None, dry_point=None): """ self._gpio_pin = [MOISTURE_1_PIN, MOISTURE_2_PIN, MOISTURE_3_PIN, MOISTURE_INT_PIN][channel - 1] - GPIO.setwarnings(False) - GPIO.setmode(GPIO.BCM) - GPIO.setup(self._gpio_pin, GPIO.IN) - self._count = 0 self._reading = 0 self._history = [] self._history_length = 200 - self._last_pulse = time.time() self._new_data = False self._wet_point = wet_point if wet_point is not None else 0.7 self._dry_point = dry_point if dry_point is not None else 27.6 - self._time_last_reading = time.time() - try: - GPIO.add_event_detect(self._gpio_pin, GPIO.RISING, callback=self._event_handler, bouncetime=1) - except RuntimeError as e: - if self._gpio_pin == 8: - raise RuntimeError("""Unable to set up edge detection on BCM8. -Please ensure you add the following to /boot/config.txt and reboot: + self._last_reading = 0 + + self._int, self._offset = gpiodevice.get_pin(self._gpio_pin, f"grow-m-ch{channel}", LineSettings( + edge_detection=Edge.RISING, bias=Bias.PULL_DOWN, debounce_period=timedelta(milliseconds=10) + )) + + self._poll_thread_event = threading.Event() + self._poll_thread = threading.Thread(target=self._thread_poll) + self._poll_thread.start() -dtoverlay=spi0-cs,cs0_pin=14 # Re-assign CS0 from BCM 8 so that Grow can use it + atexit.register(self._thread_stop) -""") - else: - raise e + def __del__(self): + self._thread_stop() - self._time_start = time.time() + def _thread_stop(self): + self._poll_thread_event.set() + self._poll_thread.join() - def _event_handler(self, pin): - self._count += 1 - self._last_pulse = time.time() - if self._time_elapsed >= 1.0: - self._reading = self._count / self._time_elapsed + def _thread_poll(self): + poll = select.poll() + try: + poll.register(self._int.fd, select.POLLIN) + except TypeError: + return + while not self._poll_thread_event.wait(1.0): + if not poll.poll(0): + # No pulses in 1s, this is not a valid reading + continue + + # We got pulses, since we waited for 1s we can be relatively + # sure the number of pulses is approximately the sensor frequency + events = self._int.read_edge_events() + self._last_reading = time.time() + self._reading = len(events) # Pulse frequency self._history.insert(0, self._reading) self._history = self._history[:self._history_length] - self._count = 0 - self._time_last_reading = time.time() self._new_data = True + poll.unregister(self._int.fd) + @property def history(self): history = [] @@ -76,10 +92,6 @@ def history(self): return history - @property - def _time_elapsed(self): - return time.time() - self._time_last_reading - def set_wet_point(self, value=None): """Set the sensor wet point. @@ -123,7 +135,7 @@ def moisture(self): @property def active(self): """Check if the moisture sensor is producing a valid reading.""" - return (time.time() - self._last_pulse) < 1.0 and self._reading > 0 and self._reading < 28 + return (time.time() - self._last_reading) <= 1.0 and self._reading > 0 and self._reading < 28 @property def new_data(self): From 3ff4f65a619f9f11c9408b8c535a8d755593471c Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 26 Feb 2025 14:11:19 +0000 Subject: [PATCH 5/8] Switch PWM to use an Event. --- grow/pwm.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/grow/pwm.py b/grow/pwm.py index 233898e..31ef30f 100644 --- a/grow/pwm.py +++ b/grow/pwm.py @@ -1,5 +1,5 @@ import time -from threading import Thread +from threading import Event, Thread import gpiod import gpiodevice @@ -11,19 +11,18 @@ class PWM: _pwms: list = [] _t_pwm: Thread = None - _pwm_running: bool = False + _t_pwm_event: Event = Event() @staticmethod def start_thread(): if PWM._t_pwm is None: - PWM._pwm_running = True PWM._t_pwm = Thread(target=PWM._run) PWM._t_pwm.start() @staticmethod def stop_thread(): if PWM._t_pwm is not None: - PWM._pwm_running = False + PWM._t_pwm_event.set() PWM._t_pwm.join() PWM._t_pwm = None @@ -34,13 +33,13 @@ def _add(pwm): @staticmethod def _remove(pwm): index = PWM._pwms.index(pwm) - del PWM._pwms[index] + PWM._pwms.pop(index) if len(PWM._pwms) == 0: PWM.stop_thread() @staticmethod def _run(): - while PWM._pwm_running: + while not PWM._t_pwm_event.is_set(): PWM.run() @staticmethod From ff8bd71fd5022dea538acb13767ae6fbe97ea258 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 26 Feb 2025 14:11:49 +0000 Subject: [PATCH 6/8] CI: Fixup tests. --- tests/conftest.py | 22 +++++++++++++++++----- tests/test_lock.py | 17 ++++++++++++++--- tests/test_setup.py | 31 ++++++++----------------------- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3c8915c..0fb9b13 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,12 +26,24 @@ def cleanup(): @pytest.fixture(scope='function', autouse=False) -def GPIO(): +def gpiod(): """Mock gpiod module.""" - gpiod = mock.MagicMock() - sys.modules['gpiod'] = gpiod - yield gpiod - del sys.modules['gpiod'] + sys.modules["gpiod"] = mock.Mock() + sys.modules["gpiod.line"] = mock.Mock() + yield sys.modules["gpiod"] + del sys.modules["gpiod.line"] + del sys.modules["gpiod"] + + +@pytest.fixture(scope="function", autouse=False) +def gpiodevice(): + gpiodevice = mock.Mock() + gpiodevice.get_pins_for_platform.return_value = [(mock.Mock(), 0), (mock.Mock(), 0), (mock.Mock(), 0)] + gpiodevice.get_pin.return_value = (mock.Mock(), 0) + + sys.modules["gpiodevice"] = gpiodevice + yield gpiodevice + del sys.modules["gpiodevice"] @pytest.fixture(scope='function', autouse=False) diff --git a/tests/test_lock.py b/tests/test_lock.py index b09c744..4d44cad 100644 --- a/tests/test_lock.py +++ b/tests/test_lock.py @@ -1,8 +1,9 @@ import time -def test_pumps_actually_stop(gpiod, smbus2): +def test_pumps_actually_stop(gpiod, gpiodevice, smbus2): from grow.pump import Pump + from grow.pwm import PWM ch1 = Pump(channel=1) @@ -10,9 +11,13 @@ def test_pumps_actually_stop(gpiod, smbus2): time.sleep(0.1) assert ch1.get_speed() == 0 + PWM.stop_thread() -def test_pumps_are_mutually_exclusive(gpiod, smbus2): + + +def test_pumps_are_mutually_exclusive(gpiod, gpiodevice, smbus2): from grow.pump import Pump, global_lock + from grow.pwm import PWM ch1 = Pump(channel=1) ch2 = Pump(channel=2) @@ -28,9 +33,13 @@ def test_pumps_are_mutually_exclusive(gpiod, smbus2): assert ch3.dose(speed=0.5) is False assert ch3.dose(speed=0.5, blocking=False) is False + PWM.stop_thread() + -def test_pumps_run_sequentially(gpiod, smbus2): + +def test_pumps_run_sequentially(gpiod, gpiodevice, smbus2): from grow.pump import Pump, global_lock + from grow.pwm import PWM ch1 = Pump(channel=1) ch2 = Pump(channel=2) @@ -45,3 +54,5 @@ def test_pumps_run_sequentially(gpiod, smbus2): assert ch3.dose(speed=0.5, timeout=0.1, blocking=False) is True assert global_lock.locked() is True time.sleep(0.3) + + PWM.stop_thread() diff --git a/tests/test_setup.py b/tests/test_setup.py index 6ba611f..f193abf 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1,21 +1,16 @@ import mock -def test_moisture_setup(gpiod, smbus2): +def test_moisture_setup(gpiod, gpiodevice, smbus2): from grow.moisture import Moisture + from datetime import timedelta ch1 = Moisture(channel=1) ch2 = Moisture(channel=2) ch3 = Moisture(channel=3) - GPIO.setup.assert_has_calls([ - mock.call(ch1._gpio_pin, GPIO.IN), - mock.call(ch2._gpio_pin, GPIO.IN), - mock.call(ch3._gpio_pin, GPIO.IN) - ]) - -def test_moisture_read(gpiod, smbus2): +def test_moisture_read(gpiod, gpiodevice, smbus2): from grow.moisture import Moisture assert Moisture(channel=1).saturation == 1.0 @@ -27,24 +22,14 @@ def test_moisture_read(gpiod, smbus2): assert Moisture(channel=3).moisture == 0 -def test_pump_setup(gpiod, smbus2): +def test_pump_setup(gpiod, gpiodevice, smbus2): from grow.pump import PUMP_PWM_FREQ, Pump + from grow.pwm import PWM ch1 = Pump(channel=1) ch2 = Pump(channel=2) ch3 = Pump(channel=3) - GPIO.setup.assert_has_calls([ - mock.call(ch1._gpio_pin, GPIO.OUT, initial=GPIO.LOW), - mock.call(ch2._gpio_pin, GPIO.OUT, initial=GPIO.LOW), - mock.call(ch3._gpio_pin, GPIO.OUT, initial=GPIO.LOW) - ]) - - GPIO.PWM.assert_has_calls([ - mock.call(ch1._gpio_pin, PUMP_PWM_FREQ), - mock.call().start(0), - mock.call(ch2._gpio_pin, PUMP_PWM_FREQ), - mock.call().start(0), - mock.call(ch3._gpio_pin, PUMP_PWM_FREQ), - mock.call().start(0) - ]) + # Threads. Not even once. + PWM.stop_thread() + From c452289fa61497c701af8ab0597d721cd07ae4c1 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 26 Feb 2025 14:30:17 +0000 Subject: [PATCH 7/8] CI: Apply isort suggestions. --- grow/moisture.py | 7 +++---- tests/test_setup.py | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/grow/moisture.py b/grow/moisture.py index 021c80a..20e700f 100644 --- a/grow/moisture.py +++ b/grow/moisture.py @@ -1,13 +1,12 @@ -import time +import atexit import select import threading +import time from datetime import timedelta -import atexit import gpiodevice from gpiod import LineSettings -from gpiod.line import Edge, Bias - +from gpiod.line import Bias, Edge MOISTURE_1_PIN = 23 MOISTURE_2_PIN = 8 diff --git a/tests/test_setup.py b/tests/test_setup.py index f193abf..df07982 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -2,9 +2,10 @@ def test_moisture_setup(gpiod, gpiodevice, smbus2): - from grow.moisture import Moisture from datetime import timedelta + from grow.moisture import Moisture + ch1 = Moisture(channel=1) ch2 = Moisture(channel=2) ch3 = Moisture(channel=3) From c119e5f7bfb76d1dfc9755ba169c173182cdfd91 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 26 Feb 2025 14:46:38 +0000 Subject: [PATCH 8/8] CI: Make ruff happy. --- tests/test_setup.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tests/test_setup.py b/tests/test_setup.py index df07982..6e761d2 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1,14 +1,9 @@ -import mock - - def test_moisture_setup(gpiod, gpiodevice, smbus2): - from datetime import timedelta - from grow.moisture import Moisture - ch1 = Moisture(channel=1) - ch2 = Moisture(channel=2) - ch3 = Moisture(channel=3) + _ = Moisture(channel=1) + _ = Moisture(channel=2) + _ = Moisture(channel=3) def test_moisture_read(gpiod, gpiodevice, smbus2): @@ -24,12 +19,12 @@ def test_moisture_read(gpiod, gpiodevice, smbus2): def test_pump_setup(gpiod, gpiodevice, smbus2): - from grow.pump import PUMP_PWM_FREQ, Pump + from grow.pump import Pump from grow.pwm import PWM - ch1 = Pump(channel=1) - ch2 = Pump(channel=2) - ch3 = Pump(channel=3) + _ = Pump(channel=1) + _ = Pump(channel=2) + _ = Pump(channel=3) # Threads. Not even once. PWM.stop_thread()