mirror of
				https://github.com/KevinMidboe/python-gpiozero.git
				synced 2025-10-29 17:50:37 +00:00 
			
		
		
		
	Added SPI tests, simplified the shared SPI software bus implementation, and fixed several protocol errors in our MCP3xxx classes (the x2 and x1 protocols were wrong)
		
			
				
	
	
		
			340 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import (
 | |
|     unicode_literals,
 | |
|     absolute_import,
 | |
|     print_function,
 | |
|     division,
 | |
|     )
 | |
| str = type('')
 | |
| 
 | |
| 
 | |
| import sys
 | |
| import pytest
 | |
| from collections import namedtuple
 | |
| try:
 | |
|     from math import isclose
 | |
| except ImportError:
 | |
|     from gpiozero.compat import isclose
 | |
| 
 | |
| from gpiozero import *
 | |
| from gpiozero.pins.mock import MockSPIDevice, MockPin
 | |
| 
 | |
| 
 | |
| def setup_function(function):
 | |
|     import gpiozero.devices
 | |
|     gpiozero.devices.pin_factory = MockPin
 | |
| 
 | |
| def teardown_function(function):
 | |
|     MockPin.clear_pins()
 | |
| 
 | |
| def clamp(v, min_value, max_value):
 | |
|     return min(max_value, max(min_value, v))
 | |
| 
 | |
| def scale(v, ref, bits):
 | |
|     v /= ref
 | |
|     vmin = -(2 ** bits)
 | |
|     vmax = -vmin - 1
 | |
|     vrange = vmax - vmin
 | |
|     return int(((v + 1) / 2.0) * vrange + vmin)
 | |
| 
 | |
| 
 | |
| class MockMCP3xxx(MockSPIDevice):
 | |
|     def __init__(
 | |
|             self, clock_pin, mosi_pin, miso_pin, select_pin=None,
 | |
|             channels=8, bits=10):
 | |
|         super(MockMCP3xxx, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin)
 | |
|         self.vref = 3.3
 | |
|         self.channels = [0.0] * channels
 | |
|         self.channel_bits = 3
 | |
|         self.bits = bits
 | |
|         self.state = 'idle'
 | |
| 
 | |
|     def on_start(self):
 | |
|         super(MockMCP3xxx, self).on_start()
 | |
|         self.state = 'idle'
 | |
| 
 | |
|     def on_bit(self):
 | |
|         if self.state == 'idle':
 | |
|             if self.rx_buf[-1]:
 | |
|                 self.state = 'mode'
 | |
|                 self.rx_buf = []
 | |
|         elif self.state == 'mode':
 | |
|             if self.rx_buf[-1]:
 | |
|                 self.state = 'single'
 | |
|             else:
 | |
|                 self.state = 'diff'
 | |
|             self.rx_buf = []
 | |
|         elif self.state in ('single', 'diff'):
 | |
|             if len(self.rx_buf) == self.channel_bits:
 | |
|                 self.on_result(self.state == 'diff', self.rx_word())
 | |
|                 self.state = 'result'
 | |
|         elif self.state == 'result':
 | |
|             if not self.tx_buf:
 | |
|                 self.state = 'idle'
 | |
|                 self.rx_buf = []
 | |
|         else:
 | |
|             assert False
 | |
| 
 | |
|     def on_result(self, differential, channel):
 | |
|         if differential:
 | |
|             pos_channel = channel
 | |
|             neg_channel = pos_channel ^ 1
 | |
|             result = self.channels[pos_channel] - self.channels[neg_channel]
 | |
|             result = clamp(result, 0, self.vref)
 | |
|         else:
 | |
|             result = clamp(self.channels[channel], 0, self.vref)
 | |
|         result = scale(result, self.vref, self.bits)
 | |
|         self.tx_word(result, self.bits + 2)
 | |
| 
 | |
| 
 | |
| class MockMCP3xx1(MockMCP3xxx):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, bits=10):
 | |
|         super(MockMCP3xx1, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=bits)
 | |
| 
 | |
|     def on_start(self):
 | |
|         super(MockMCP3xx1, self).on_start()
 | |
|         result = self.channels[0] - self.channels[1]
 | |
|         result = clamp(result, 0, self.vref)
 | |
|         result = scale(result, self.vref, self.bits)
 | |
|         self.tx_word(result, self.bits + 3)
 | |
| 
 | |
|     def on_bit(self):
 | |
|         pass
 | |
| 
 | |
| 
 | |
| class MockMCP3xx2(MockMCP3xxx):
 | |
|     def __init__(
 | |
|             self, clock_pin, mosi_pin, miso_pin, select_pin=None,
 | |
|             bits=10):
 | |
|         super(MockMCP3xx2, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=bits)
 | |
|         self.channel_bits = 1
 | |
| 
 | |
| 
 | |
| class MockMCP33xx(MockMCP3xxx):
 | |
|     def __init__(
 | |
|             self, clock_pin, mosi_pin, miso_pin, select_pin=None,
 | |
|             channels=8):
 | |
|         super(MockMCP33xx, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels, 12)
 | |
| 
 | |
|     def on_result(self, differential, channel):
 | |
|         if differential:
 | |
|             pos_channel = channel
 | |
|             neg_channel = pos_channel ^ 1
 | |
|             result = self.channels[pos_channel] - self.channels[neg_channel]
 | |
|             result = clamp(result, -self.vref, self.vref)
 | |
|         else:
 | |
|             result = clamp(self.channels[channel], 0, self.vref)
 | |
|         result = scale(result, self.vref, self.bits)
 | |
|         if result < 0:
 | |
|             result += 8192
 | |
|         self.tx_word(result, self.bits + 3)
 | |
| 
 | |
| 
 | |
| class MockMCP3001(MockMCP3xx1):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3001, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, bits=10)
 | |
| 
 | |
| 
 | |
| class MockMCP3002(MockMCP3xx2):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3002, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, bits=10)
 | |
| 
 | |
| 
 | |
| class MockMCP3004(MockMCP3xxx):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3004, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=4, bits=10)
 | |
| 
 | |
| 
 | |
| class MockMCP3008(MockMCP3xxx):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3008, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=8, bits=10)
 | |
| 
 | |
| 
 | |
| class MockMCP3201(MockMCP3xx1):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3201, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, bits=12)
 | |
| 
 | |
| 
 | |
| class MockMCP3202(MockMCP3xx2):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3202, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, bits=12)
 | |
| 
 | |
| 
 | |
| class MockMCP3204(MockMCP3xxx):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3204, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=4, bits=12)
 | |
| 
 | |
| 
 | |
| class MockMCP3208(MockMCP3xxx):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3208, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=8, bits=12)
 | |
| 
 | |
| 
 | |
| class MockMCP3301(MockMCP3xxx):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3301, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=12)
 | |
| 
 | |
|     def on_start(self):
 | |
|         super(MockMCP3301, self).on_start()
 | |
|         result = self.channels[0] - self.channels[1]
 | |
|         result = clamp(result, -self.vref, self.vref)
 | |
|         result = scale(result, self.vref, self.bits)
 | |
|         if result < 0:
 | |
|             result += 8192
 | |
|         self.tx_word(result, self.bits + 4)
 | |
| 
 | |
| 
 | |
| class MockMCP3302(MockMCP33xx):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3302, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=4)
 | |
| 
 | |
| 
 | |
| class MockMCP3304(MockMCP33xx):
 | |
|     def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
 | |
|         super(MockMCP3304, self).__init__(
 | |
|             clock_pin, mosi_pin, miso_pin, select_pin, channels=8)
 | |
| 
 | |
| 
 | |
| def single_mcp_test(mock, pot, channel, bits):
 | |
|     scale = 2**bits
 | |
|     tolerance = 1 / scale
 | |
|     mock.channels[channel] = 0.0
 | |
|     assert pot.raw_value == 0
 | |
|     assert isclose(pot.value, 0.0, abs_tol=tolerance)
 | |
|     mock.channels[channel] = mock.vref / 2
 | |
|     assert pot.raw_value == (scale / 2) - 1
 | |
|     assert isclose(pot.value, 0.5, abs_tol=tolerance)
 | |
|     mock.channels[channel] = mock.vref
 | |
|     assert pot.raw_value == scale - 1
 | |
|     assert isclose(pot.value, 1.0, abs_tol=tolerance)
 | |
| 
 | |
| def differential_mcp_test(mock, pot, pos_channel, neg_channel, bits, full=False):
 | |
|     scale = 2**bits
 | |
|     tolerance = 1 / scale
 | |
|     mock.channels[pos_channel] = 0.0
 | |
|     mock.channels[neg_channel] = 0.0
 | |
|     assert pot.raw_value == 0
 | |
|     assert isclose(pot.value, 0.0, abs_tol=tolerance)
 | |
|     mock.channels[pos_channel] = mock.vref / 2
 | |
|     assert pot.raw_value == (scale / 2) - 1
 | |
|     assert isclose(pot.value, 0.5, abs_tol=tolerance)
 | |
|     mock.channels[pos_channel] = mock.vref
 | |
|     assert pot.raw_value == scale - 1
 | |
|     assert isclose(pot.value, 1.0, abs_tol=tolerance)
 | |
|     mock.channels[neg_channel] = mock.vref / 2
 | |
|     assert pot.raw_value == (scale / 2) - 1
 | |
|     assert isclose(pot.value, 0.5, abs_tol=tolerance)
 | |
|     mock.channels[pos_channel] = mock.vref / 2
 | |
|     assert pot.raw_value == 0
 | |
|     assert isclose(pot.value, 0.0, abs_tol=tolerance)
 | |
|     mock.channels[pos_channel] = 0.0
 | |
|     mock.channels[neg_channel] = mock.vref
 | |
|     if full:
 | |
|         assert pot.raw_value == -scale
 | |
|         assert isclose(pot.value, -1.0, abs_tol=tolerance)
 | |
|     else:
 | |
|         assert pot.raw_value == 0
 | |
|         assert isclose(pot.value, 0.0, abs_tol=tolerance)
 | |
| 
 | |
| 
 | |
| def test_MCP3001():
 | |
|     mock = MockMCP3001(11, 10, 9, 8)
 | |
|     with MCP3001() as pot:
 | |
|         differential_mcp_test(mock, pot, 0, 1, 10)
 | |
| 
 | |
| def test_MCP3002():
 | |
|     mock = MockMCP3002(11, 10, 9, 8)
 | |
|     with pytest.raises(ValueError):
 | |
|         MCP3002(channel=5)
 | |
|     with MCP3002(channel=1) as pot:
 | |
|         single_mcp_test(mock, pot, 1, 10)
 | |
|     with MCP3002(channel=1, differential=True) as pot:
 | |
|         differential_mcp_test(mock, pot, 1, 0, 10)
 | |
| 
 | |
| def test_MCP3004():
 | |
|     mock = MockMCP3004(11, 10, 9, 8)
 | |
|     with pytest.raises(ValueError):
 | |
|         MCP3004(channel=5)
 | |
|     with MCP3004(channel=3) as pot:
 | |
|         single_mcp_test(mock, pot, 3, 10)
 | |
|     with MCP3004(channel=3, differential=True) as pot:
 | |
|         differential_mcp_test(mock, pot, 3, 2, 10)
 | |
| 
 | |
| def test_MCP3008():
 | |
|     mock = MockMCP3008(11, 10, 9, 8)
 | |
|     with pytest.raises(ValueError):
 | |
|         MCP3008(channel=9)
 | |
|     with MCP3008(channel=0) as pot:
 | |
|         single_mcp_test(mock, pot, 0, 10)
 | |
|     with MCP3008(channel=0, differential=True) as pot:
 | |
|         differential_mcp_test(mock, pot, 0, 1, 10)
 | |
| 
 | |
| def test_MCP3201():
 | |
|     mock = MockMCP3201(11, 10, 9, 8)
 | |
|     with MCP3201() as pot:
 | |
|         differential_mcp_test(mock, pot, 0, 1, 12)
 | |
| 
 | |
| def test_MCP3202():
 | |
|     mock = MockMCP3202(11, 10, 9, 8)
 | |
|     with pytest.raises(ValueError):
 | |
|         MCP3202(channel=5)
 | |
|     with MCP3202(channel=1) as pot:
 | |
|         single_mcp_test(mock, pot, 1, 12)
 | |
|     with MCP3202(channel=1, differential=True) as pot:
 | |
|         differential_mcp_test(mock, pot, 1, 0, 12)
 | |
| 
 | |
| def test_MCP3204():
 | |
|     mock = MockMCP3204(11, 10, 9, 8)
 | |
|     with pytest.raises(ValueError):
 | |
|         MCP3204(channel=5)
 | |
|     with MCP3204(channel=1) as pot:
 | |
|         single_mcp_test(mock, pot, 1, 12)
 | |
|     with MCP3204(channel=1, differential=True) as pot:
 | |
|         differential_mcp_test(mock, pot, 1, 0, 12)
 | |
| 
 | |
| def test_MCP3208():
 | |
|     mock = MockMCP3208(11, 10, 9, 8)
 | |
|     with pytest.raises(ValueError):
 | |
|         MCP3208(channel=9)
 | |
|     with MCP3208(channel=7) as pot:
 | |
|         single_mcp_test(mock, pot, 7, 12)
 | |
|     with MCP3208(channel=7, differential=True) as pot:
 | |
|         differential_mcp_test(mock, pot, 7, 6, 12)
 | |
| 
 | |
| def test_MCP3301():
 | |
|     mock = MockMCP3301(11, 10, 9, 8)
 | |
|     with MCP3301() as pot:
 | |
|         differential_mcp_test(mock, pot, 0, 1, 12, full=True)
 | |
| 
 | |
| def test_MCP3302():
 | |
|     mock = MockMCP3302(11, 10, 9, 8)
 | |
|     with pytest.raises(ValueError):
 | |
|         MCP3302(channel=4)
 | |
|     with MCP3302(channel=0) as pot:
 | |
|         single_mcp_test(mock, pot, 0, 12)
 | |
|     with MCP3302(channel=0, differential=True) as pot:
 | |
|         differential_mcp_test(mock, pot, 0, 1, 12, full=True)
 | |
| 
 | |
| def test_MCP3304():
 | |
|     mock = MockMCP3304(11, 10, 9, 8)
 | |
|     with pytest.raises(ValueError):
 | |
|         MCP3304(channel=9)
 | |
|     with MCP3304(channel=5) as pot:
 | |
|         single_mcp_test(mock, pot, 5, 12)
 | |
|     with MCP3304(channel=5, differential=True) as pot:
 | |
|         differential_mcp_test(mock, pot, 5, 4, 12, full=True)
 | |
| 
 |