-
Notifications
You must be signed in to change notification settings - Fork 53
/
snake_eyes_bonnet.py
executable file
·143 lines (132 loc) · 6.36 KB
/
snake_eyes_bonnet.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/python3
"""
Code associated with the Adafruit Snake Eyes Bonnet for Raspberry Pi.
PYTHON 3 ONLY. Provides convenience functions for reading and filtering
the board's ADC inputs, can run in background as a thread.
Requires adafruit-blinka (CircuitPython APIs for Python on big hardware)
and adafruit-circuitpython-ads1x15.
Does NOT handle button inputs or writing to displays -- other code handles
those tasks. Former is basic GPIO stuff, latter is done by the fbx2 code.
"""
import time
from threading import Thread
import board
import busio
#import adafruit_ads1x15.ads1015 as ADS
#from adafruit_ads1x15.analog_in import AnalogIn
class AdcChannel():
"""Corresponds to ONE CHANNEL of the Snake Eye Bonnet's ADS1015
analog-to-digital converter. Provides clipping, optional inversion
and noise filtering. Output range ('value' element) is always in
range 0.0 to 1.0."""
def __init__(self, channel):
self.channel = channel # AnalogIn P0-P3
self.enabled = False # Disabled by default (until config() called)
self.min_v = 0.0 # Min expected input voltage
self.max_v = 3.3 # Max expected input voltage
self.reverse = False # If True, reverse output range (0.0 -> 1.0)
self.filter = 0.0 # Noise reduction (0.0 <= n < 1.0)
self.value = 0.5 # Initial state
def config(self, **kwargs):
"""Reconfigure one channel of the Snake Eyes ADC. Accepts several
keyword arguments that override default values/behaviors:
min_v: Minimum voltage expected from ADC (e.g. 0.0)
max_v: Maximum voltage expected from ADC (e.g. 3.3)
reverse: If True, output range will be reversed.
filter: Weighting applied to old vs new ADC reading. A value of
0.0 (the default) means no filtering will be applied.
Values approaching 1.0 make new readings slower on the
uptake (reducing minor noise) -- a value of 1.0 would
just make the original value stick permanently.
Calling this function will make the corresponding ADC channel
active (it will be polled in the SnakeEyesBonnet class run()
function). There is no corresponding disable function."""
self.enabled = True
for key, value in kwargs.items():
if key == "min_v":
self.min_v = value
elif key == "max_v":
self.max_v = value
elif key == "reverse":
self.reverse = value
elif key == "filter":
self.filter = min(max(value, 0.0), 1.0)
if self.min_v > self.max_v:
self.min_v, self.max_v = self.max_v, self.min_v
def read(self):
"""Poll ADC channel, applying scaling and filtering,
store in 'value' member as well as return value."""
if self.enabled:
voltage = self.channel.voltage
clipped = min(max(voltage, self.min_v), self.max_v)
scaled = (clipped - self.min_v) / (self.max_v - self.min_v)
if self.reverse:
scaled = 1.0 - scaled
self.value = (self.value * (self.filter) +
scaled * (1.0 - self.filter))
return self.value
class SnakeEyesBonnet(Thread):
"""SnakeEyesBonnet encapsulates various analog-to-digital converter
functionality of the Adafruit Snake Eyes Bonnet, providing up to
four channels of analog input with clipping and filtering, with
output ranging from 0.0 to 1.0 (rather than specific voltages or
integer units)."""
# channel_dict = {
# 0: ADS.P0,
# 1: ADS.P1,
# 2: ADS.P2,
# 3: ADS.P3
# }
def __init__(self, *args, **kwargs):
"""SnakeEyesBonnet constructor."""
super(SnakeEyesBonnet, self).__init__(*args, **kwargs) # Thread
self.i2c = busio.I2C(board.SCL, board.SDA)
self.ads = ADS.ADS1015(self.i2c)
self.ads.gain = 1
self.period = 1.0 / 60.0 # Polling inverval = 1/60 sec default
self.print_values = False # Don't print values by default
self.channel = []
for index in range(4):
self.channel.append(AdcChannel(
AnalogIn(self.ads, self.channel_dict[index])))
def setup_channel(self, channel, **kwargs):
"""Configure one ADC channel of the Snake Eyes Bonnet. Pass channel
number (0 to 3) as well as optional keyword arguments documented
in AdcChannel.config()."""
if 0 <= channel <= 3:
self.channel[channel].config(**kwargs)
def run(self):
"""Run in loop, polling active Snake Eyes Bonnet ADC channels and
optionally printing results. Pass 'True' to print output, else
it will run silently (updating the channel[].value member values).
Default is False, so if invoked via threading it runs in the
background. Polling interval is set using the SnakeEyesBonnet
constructor with optional 'period' keyword argument.
This function does not return. DO NOT rename this function,
it's so named to work with Python 3's threading class."""
while True:
for channel in self.channel:
if channel.enabled:
channel.read()
if self.print_values:
print("%.6f" % channel.value, end=" ")
if self.print_values:
print(flush=True)
# Note that the 'period' value is not strictly speaking the
# polling period, since there will be some overhead for the
# code above to run, plus occasional garbage collection and
# such. Period is really just the sleep time here...
time.sleep(self.period)
# If script is invoked standalone, not imported by another script,
# run example test...
if __name__ == "__main__":
BONNET = SnakeEyesBonnet()
BONNET.period = 0.25 # Set up 1/4 second polling interval
BONNET.print_values = True # Display output as we go
# Configure four ADC channels...
BONNET.setup_channel(0, reverse=False, filter=0.0)
BONNET.setup_channel(1, reverse=False, filter=0.0)
BONNET.setup_channel(2, reverse=False, filter=0.0)
BONNET.setup_channel(3, reverse=False, filter=0.0)
# Run polling loop "manually" (not as thread)...
BONNET.run()