Skip to content

Commit 7f40ecf

Browse files
committed
Add available() method to gas sensor
This change catches an IOError when setting up the gas sensor and provides an `available()` method for determining if a sensor is present.
1 parent 47089ae commit 7f40ecf

File tree

4 files changed

+179
-145
lines changed

4 files changed

+179
-145
lines changed

library/enviroplus/gas.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
ads1015.I2C_ADDRESS_DEFAULT = ads1015.I2C_ADDRESS_ALTERNATE
1212
_is_setup = False
13+
_is_available = False
1314
_adc_enabled = False
1415
_adc_gain = 6.148
1516

@@ -41,13 +42,19 @@ def __repr__(self):
4142

4243

4344
def setup():
44-
global adc, adc_type, _is_setup
45+
global adc, adc_type, _is_setup, _is_available
4546
if _is_setup:
4647
return
4748
_is_setup = True
4849

49-
adc = ads1015.ADS1015(i2c_addr=0x49)
50-
adc_type = adc.detect_chip_type()
50+
try:
51+
adc = ads1015.ADS1015(i2c_addr=0x49)
52+
adc_type = adc.detect_chip_type()
53+
_is_available = True
54+
except IOError:
55+
_is_available = False
56+
return
57+
5158
adc.set_mode('single')
5259
adc.set_programmable_gain(MICS6814_GAIN)
5360
if adc_type == 'ADS1115':
@@ -62,6 +69,11 @@ def setup():
6269
atexit.register(cleanup)
6370

6471

72+
def available():
73+
setup()
74+
return _is_available
75+
76+
6577
def enable_adc(value=True):
6678
"""Enable reading from the additional ADC pin."""
6779
global _adc_enabled
@@ -81,6 +93,10 @@ def cleanup():
8193
def read_all():
8294
"""Return gas resistence for oxidising, reducing and NH3"""
8395
setup()
96+
97+
if not _is_available:
98+
raise RuntimeError("Gas sensor not connected.")
99+
84100
ox = adc.get_voltage('in0/gnd')
85101
red = adc.get_voltage('in1/gnd')
86102
nh3 = adc.get_voltage('in2/gnd')
@@ -119,7 +135,6 @@ def read_oxidising():
119135
120136
Eg chlorine, nitrous oxide
121137
"""
122-
setup()
123138
return read_all().oxidising
124139

125140

@@ -128,17 +143,14 @@ def read_reducing():
128143
129144
Eg hydrogen, carbon monoxide
130145
"""
131-
setup()
132146
return read_all().reducing
133147

134148

135149
def read_nh3():
136150
"""Return gas resistance for nh3/ammonia"""
137-
setup()
138151
return read_all().nh3
139152

140153

141154
def read_adc():
142155
"""Return spare ADC channel value"""
143-
setup()
144156
return read_all().adc

library/tests/conftest.py

Lines changed: 99 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,99 @@
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-
from i2cdevice import MockSMBus
9-
10-
11-
class SMBusFakeDevice(MockSMBus):
12-
def __init__(self, i2c_bus):
13-
MockSMBus.__init__(self, i2c_bus)
14-
self.regs[0x00:0x01] = 0x0f, 0x00
15-
16-
17-
@pytest.fixture(scope='function', autouse=True)
18-
def cleanup():
19-
yield None
20-
try:
21-
del sys.modules['enviroplus']
22-
except KeyError:
23-
pass
24-
try:
25-
del sys.modules['enviroplus.noise']
26-
except KeyError:
27-
pass
28-
try:
29-
del sys.modules['enviroplus.gas']
30-
except KeyError:
31-
pass
32-
33-
34-
@pytest.fixture(scope='function', autouse=False)
35-
def GPIO():
36-
"""Mock RPi.GPIO module."""
37-
GPIO = mock.MagicMock()
38-
# Fudge for Python < 37 (possibly earlier)
39-
sys.modules['RPi'] = mock.Mock()
40-
sys.modules['RPi'].GPIO = GPIO
41-
sys.modules['RPi.GPIO'] = GPIO
42-
yield GPIO
43-
del sys.modules['RPi']
44-
del sys.modules['RPi.GPIO']
45-
46-
47-
@pytest.fixture(scope='function', autouse=False)
48-
def spidev():
49-
"""Mock spidev module."""
50-
spidev = mock.MagicMock()
51-
sys.modules['spidev'] = spidev
52-
yield spidev
53-
del sys.modules['spidev']
54-
55-
56-
@pytest.fixture(scope='function', autouse=False)
57-
def smbus():
58-
"""Mock smbus module."""
59-
smbus = mock.MagicMock()
60-
smbus.SMBus = SMBusFakeDevice
61-
sys.modules['smbus'] = smbus
62-
yield smbus
63-
del sys.modules['smbus']
64-
65-
66-
@pytest.fixture(scope='function', autouse=False)
67-
def atexit():
68-
"""Mock atexit module."""
69-
atexit = mock.MagicMock()
70-
sys.modules['atexit'] = atexit
71-
yield atexit
72-
del sys.modules['atexit']
73-
74-
75-
@pytest.fixture(scope='function', autouse=False)
76-
def sounddevice():
77-
"""Mock sounddevice module."""
78-
sounddevice = mock.MagicMock()
79-
sys.modules['sounddevice'] = sounddevice
80-
yield sounddevice
81-
del sys.modules['sounddevice']
82-
83-
84-
@pytest.fixture(scope='function', autouse=False)
85-
def numpy():
86-
"""Mock numpy module."""
87-
numpy = mock.MagicMock()
88-
sys.modules['numpy'] = numpy
89-
yield numpy
90-
del sys.modules['numpy']
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+
from i2cdevice import MockSMBus
9+
10+
11+
class SMBusFakeDevice(MockSMBus):
12+
def __init__(self, i2c_bus):
13+
MockSMBus.__init__(self, i2c_bus)
14+
self.regs[0x00:0x01] = 0x0f, 0x00
15+
16+
17+
@pytest.fixture(scope='function', autouse=True)
18+
def cleanup():
19+
yield None
20+
try:
21+
del sys.modules['enviroplus']
22+
except KeyError:
23+
pass
24+
try:
25+
del sys.modules['enviroplus.noise']
26+
except KeyError:
27+
pass
28+
try:
29+
del sys.modules['enviroplus.gas']
30+
except KeyError:
31+
pass
32+
33+
34+
@pytest.fixture(scope='function', autouse=False)
35+
def GPIO():
36+
"""Mock RPi.GPIO module."""
37+
GPIO = mock.MagicMock()
38+
# Fudge for Python < 37 (possibly earlier)
39+
sys.modules['RPi'] = mock.Mock()
40+
sys.modules['RPi'].GPIO = GPIO
41+
sys.modules['RPi.GPIO'] = GPIO
42+
yield GPIO
43+
del sys.modules['RPi']
44+
del sys.modules['RPi.GPIO']
45+
46+
47+
@pytest.fixture(scope='function', autouse=False)
48+
def spidev():
49+
"""Mock spidev module."""
50+
spidev = mock.MagicMock()
51+
sys.modules['spidev'] = spidev
52+
yield spidev
53+
del sys.modules['spidev']
54+
55+
56+
@pytest.fixture(scope='function', autouse=False)
57+
def smbus():
58+
"""Mock smbus module."""
59+
smbus = mock.MagicMock()
60+
smbus.SMBus = SMBusFakeDevice
61+
sys.modules['smbus'] = smbus
62+
yield smbus
63+
del sys.modules['smbus']
64+
65+
66+
@pytest.fixture(scope='function', autouse=False)
67+
def mocksmbus():
68+
"""Mock smbus module."""
69+
smbus = mock.MagicMock()
70+
sys.modules['smbus'] = smbus
71+
yield smbus
72+
del sys.modules['smbus']
73+
74+
75+
@pytest.fixture(scope='function', autouse=False)
76+
def atexit():
77+
"""Mock atexit module."""
78+
atexit = mock.MagicMock()
79+
sys.modules['atexit'] = atexit
80+
yield atexit
81+
del sys.modules['atexit']
82+
83+
84+
@pytest.fixture(scope='function', autouse=False)
85+
def sounddevice():
86+
"""Mock sounddevice module."""
87+
sounddevice = mock.MagicMock()
88+
sys.modules['sounddevice'] = sounddevice
89+
yield sounddevice
90+
del sys.modules['sounddevice']
91+
92+
93+
@pytest.fixture(scope='function', autouse=False)
94+
def numpy():
95+
"""Mock numpy module."""
96+
numpy = mock.MagicMock()
97+
sys.modules['numpy'] = numpy
98+
yield numpy
99+
del sys.modules['numpy']

library/tests/test_noise.py

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,48 @@
1-
import pytest
2-
3-
4-
def test_noise_setup(sounddevice, numpy):
5-
from enviroplus.noise import Noise
6-
7-
noise = Noise(sample_rate=16000, duration=0.1)
8-
del noise
9-
10-
11-
def test_noise_get_amplitudes_at_frequency_ranges(sounddevice, numpy):
12-
from enviroplus.noise import Noise
13-
14-
noise = Noise(sample_rate=16000, duration=0.1)
15-
noise.get_amplitudes_at_frequency_ranges([
16-
(100, 500),
17-
(501, 1000)
18-
])
19-
20-
sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64')
21-
22-
23-
def test_noise_get_noise_profile(sounddevice, numpy):
24-
from enviroplus.noise import Noise
25-
26-
numpy.mean.return_value = 10.0
27-
28-
noise = Noise(sample_rate=16000, duration=0.1)
29-
amp_low, amp_mid, amp_high, amp_total = noise.get_noise_profile(
30-
noise_floor=100,
31-
low=0.12,
32-
mid=0.36,
33-
high=None)
34-
35-
sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64')
36-
37-
assert amp_total == 10.0
38-
39-
40-
def test_get_amplitude_at_frequency_range(sounddevice, numpy):
41-
from enviroplus.noise import Noise
42-
43-
noise = Noise(sample_rate=16000, duration=0.1)
44-
45-
noise.get_amplitude_at_frequency_range(0, 8000)
46-
47-
with pytest.raises(ValueError):
48-
noise.get_amplitude_at_frequency_range(0, 16000)
1+
import pytest
2+
3+
4+
def test_noise_setup(sounddevice, numpy):
5+
from enviroplus.noise import Noise
6+
7+
noise = Noise(sample_rate=16000, duration=0.1)
8+
del noise
9+
10+
11+
def test_noise_get_amplitudes_at_frequency_ranges(sounddevice, numpy):
12+
from enviroplus.noise import Noise
13+
14+
noise = Noise(sample_rate=16000, duration=0.1)
15+
noise.get_amplitudes_at_frequency_ranges([
16+
(100, 500),
17+
(501, 1000)
18+
])
19+
20+
sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64')
21+
22+
23+
def test_noise_get_noise_profile(sounddevice, numpy):
24+
from enviroplus.noise import Noise
25+
26+
numpy.mean.return_value = 10.0
27+
28+
noise = Noise(sample_rate=16000, duration=0.1)
29+
amp_low, amp_mid, amp_high, amp_total = noise.get_noise_profile(
30+
noise_floor=100,
31+
low=0.12,
32+
mid=0.36,
33+
high=None)
34+
35+
sounddevice.rec.assert_called_with(0.1 * 16000, samplerate=16000, blocking=True, channels=1, dtype='float64')
36+
37+
assert amp_total == 10.0
38+
39+
40+
def test_get_amplitude_at_frequency_range(sounddevice, numpy):
41+
from enviroplus.noise import Noise
42+
43+
noise = Noise(sample_rate=16000, duration=0.1)
44+
45+
noise.get_amplitude_at_frequency_range(0, 8000)
46+
47+
with pytest.raises(ValueError):
48+
noise.get_amplitude_at_frequency_range(0, 16000)

library/tests/test_setup.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ def test_gas_setup(GPIO, smbus):
55
gas.setup()
66

77

8+
def test_gas_unavailable(GPIO, mocksmbus):
9+
from enviroplus import gas
10+
mocksmbus.SMBus(1).read_i2c_block_data.side_effect = IOError("Oh noes!")
11+
gas._is_setup = False
12+
assert gas.available() == False
13+
14+
15+
def test_gas_available(GPIO, mocksmbus):
16+
from enviroplus import gas
17+
gas._is_setup = False
18+
assert gas.available() == True
19+
20+
821
def test_gas_read_all(GPIO, smbus):
922
from enviroplus import gas
1023
gas._is_setup = False

0 commit comments

Comments
 (0)