Skip to content

Commit 4f9fad1

Browse files
committed
Support disabling LED/button for #70
This changeset adds disable_button and disable_led arguments to FanShim so that the GPIO setup for these features can be avoided. This is mostly for issues with HyperPixel raised in #70 but any should be useful for other instances where Fan SHIM might conflict pins with another board.
1 parent a9ec6b0 commit 4f9fad1

5 files changed

Lines changed: 136 additions & 10 deletions

File tree

examples/automatic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def set_automatic(status):
8989
last_change = 0
9090

9191

92-
fanshim = FanShim()
92+
fanshim = FanShim(disable_button=args.nobutton, disable_led=args.noled)
9393
fanshim.set_hold_time(1.0)
9494
fanshim.set_fan(False)
9595
armed = True

library/fanshim/__init__.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
class FanShim():
11-
def __init__(self, pin_fancontrol=18, pin_button=17, button_poll_delay=0.05):
11+
def __init__(self, pin_fancontrol=18, pin_button=17, button_poll_delay=0.05, disable_button=False, disable_led=False):
1212
"""FAN Shim.
1313
1414
:param pin_fancontrol: BCM pin for fan on/off
@@ -24,24 +24,36 @@ def __init__(self, pin_fancontrol=18, pin_button=17, button_poll_delay=0.05):
2424
self._button_hold_time = 2.0
2525
self._t_poll = None
2626

27+
self._disable_button = disable_button
28+
self._disable_led = disable_led
29+
2730
GPIO.setwarnings(False)
2831
GPIO.setmode(GPIO.BCM)
2932
GPIO.setup(self._pin_fancontrol, GPIO.OUT)
30-
GPIO.setup(self._pin_button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
3133

32-
self._led = apa102.APA102(1, 15, 14, None, brightness=0.05)
34+
if not self._disable_button:
35+
GPIO.setup(self._pin_button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
36+
37+
if not self._disable_led:
38+
self._led = apa102.APA102(1, 15, 14, None, brightness=0.05)
3339

3440
atexit.register(self._cleanup)
3541

3642
def start_polling(self):
3743
"""Start button polling."""
44+
if self._disable_button:
45+
return
46+
3847
if self._t_poll is None:
3948
self._t_poll = Thread(target=self._run)
4049
self._t_poll.daemon = True
4150
self._t_poll.start()
4251

4352
def stop_polling(self):
4453
"""Stop button polling."""
54+
if self._disable_button:
55+
return
56+
4557
if self._t_poll is not None:
4658
self._running = False
4759
self._t_poll.join()
@@ -112,6 +124,9 @@ def set_light(self, r, g, b, brightness=None):
112124
:param b: Blue (0-255)
113125
114126
"""
127+
if self._disable_led:
128+
return
129+
115130
self._led.set_pixel(0, r, g, b)
116131
if brightness is not None:
117132
self._led.set_brightness(0, brightness)

library/tests/conftest.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Test configuration.
2+
These allow the mocking of various Python modules
3+
that might otherwise have runtime side-effects.
4+
"""
5+
import sys
6+
import mock
7+
import pytest
8+
9+
10+
@pytest.fixture(scope='function', autouse=False)
11+
def GPIO():
12+
"""Mock RPi.GPIO module."""
13+
GPIO = mock.MagicMock()
14+
# Fudge for Python < 37 (possibly earlier)
15+
sys.modules['RPi'] = mock.Mock()
16+
sys.modules['RPi'].GPIO = GPIO
17+
sys.modules['RPi.GPIO'] = GPIO
18+
yield GPIO
19+
del sys.modules['RPi']
20+
del sys.modules['RPi.GPIO']
21+
22+
23+
@pytest.fixture(scope='function', autouse=False)
24+
def plasma():
25+
"""Mock plasma module."""
26+
plasma = mock.MagicMock()
27+
sys.modules['plasma'] = plasma
28+
yield plasma
29+
del sys.modules['plasma']
30+
31+
32+
@pytest.fixture(scope='function', autouse=False)
33+
def spidev():
34+
"""Mock spidev module."""
35+
spidev = mock.MagicMock()
36+
sys.modules['spidev'] = spidev
37+
yield spidev
38+
del sys.modules['spidev']
39+
40+
41+
@pytest.fixture(scope='function', autouse=False)
42+
def atexit():
43+
"""Mock atexit module."""
44+
atexit = mock.MagicMock()
45+
sys.modules['atexit'] = atexit
46+
yield atexit
47+
del sys.modules['atexit']
48+

library/tests/test_setup.py

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,74 @@
22
import sys
33

44

5-
def test_setup():
6-
sys.modules['RPi'] = mock.Mock()
7-
sys.modules['RPi.GPIO'] = mock.Mock()
8-
sys.modules['plasma'] = mock.Mock()
5+
def force_reimport(module):
6+
"""Force the module under test to be re-imported.
7+
8+
Because pytest runs all tests within the same scope (this makes me cry)
9+
we have to do some manual housekeeping to avoid tests polluting each other.
10+
11+
In this case, the first `from fanshim import FanShim` would import both plasma
12+
and RPi.GPIO from the first test run fixtures. Since we want *clean* fixtures
13+
for each test this is not only no good but the results are outright weird-
14+
15+
IE: functions we expect to be called will have no calls because FanShim
16+
receives an entirely different mock object to the one we're validating against.
17+
18+
Since conftest.py already does some sys.modules mangling I see no reason not to
19+
do the same thing here.
20+
"""
21+
try:
22+
del sys.modules[module]
23+
except KeyError:
24+
pass
25+
26+
27+
def test_setup(GPIO, plasma):
28+
force_reimport('fanshim')
929

1030
from fanshim import FanShim
1131
fanshim = FanShim()
12-
del fanshim
32+
33+
GPIO.setwarnings.assert_called_once_with(False)
34+
GPIO.setmode.assert_called_once_with(GPIO.BCM)
35+
GPIO.setup.assert_has_calls([
36+
mock.call(fanshim._pin_fancontrol, GPIO.OUT),
37+
mock.call(fanshim._pin_button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
38+
])
39+
40+
plasma.legacy.set_clear_on_exit.assert_called_once_with(True)
41+
plasma.legacy.set_light_count.assert_called_once_with(1)
42+
plasma.legacy.set_light.assert_called_once_with(0, 0, 0, 0)
43+
44+
45+
def test_button_disable(GPIO, plasma):
46+
force_reimport('fanshim')
47+
48+
from fanshim import FanShim
49+
fanshim = FanShim(disable_button=True)
50+
51+
GPIO.setwarnings.assert_called_once_with(False)
52+
GPIO.setmode.assert_called_once_with(GPIO.BCM)
53+
GPIO.setup.assert_called_once_with(fanshim._pin_fancontrol, GPIO.OUT)
54+
55+
56+
def test_led_disable(GPIO, plasma):
57+
force_reimport('fanshim')
58+
59+
from fanshim import FanShim
60+
fanshim = FanShim(disable_led=True)
61+
62+
GPIO.setwarnings.assert_called_once_with(False)
63+
GPIO.setmode.assert_called_once_with(GPIO.BCM)
64+
GPIO.setup.assert_has_calls([
65+
mock.call(fanshim._pin_fancontrol, GPIO.OUT),
66+
mock.call(fanshim._pin_button, GPIO.IN, pull_up_down=GPIO.PUD_UP)
67+
])
68+
69+
plasma.legacy.set_clear_on_exit.assert_not_called()
70+
plasma.legacy.set_light_count.assert_not_called()
71+
plasma.legacy.set_light.assert_not_called()
72+
73+
fanshim.set_light(0, 0, 0)
74+
plasma.legacy.set_light.assert_not_called()
75+
plasma.legacy.show.assert_not_called()

library/tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py{27,35},qa
2+
envlist = py{27,35,37},qa
33
skip_missing_interpreters = True
44

55
[testenv]

0 commit comments

Comments
 (0)