Skip to content

Commit 70a727a

Browse files
authored
Merge pull request #74 from pimoroni/feature/gas-detect
Add available() method to gas sensor for #66
2 parents 8e575e7 + e3df80e commit 70a727a

File tree

4 files changed

+201
-145
lines changed

4 files changed

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

0 commit comments

Comments
 (0)