Skip to content

Commit 3ae895c

Browse files
authored
Merge pull request #17 from caternuson/DN40_color_temp
DN40 lux and CT and other fixes
2 parents 3300a31 + 7499765 commit 3ae895c

File tree

2 files changed

+145
-99
lines changed

2 files changed

+145
-99
lines changed

adafruit_tcs34725.py

+142-90
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
3030
See examples/tcs34725_simpletest.py for an example of the usage.
3131
32-
* Author(s): Tony DiCola
32+
* Author(s): Tony DiCola, Carter Nelson
3333
3434
Implementation Notes
3535
--------------------
@@ -83,18 +83,6 @@
8383
# pylint: enable=bad-whitespace
8484

8585

86-
def _temperature_and_lux(data):
87-
"""Convert the 4-tuple of raw RGBC data to color temperature and lux values. Will return
88-
2-tuple of color temperature and lux."""
89-
r, g, b, _ = data
90-
x = -0.14282 * r + 1.54924 * g + -0.95641 * b
91-
y = -0.32466 * r + 1.57837 * g + -0.73191 * b
92-
z = -0.68202 * r + 0.77073 * g + 0.56332 * b
93-
divisor = x + y + z
94-
n = (x / divisor - 0.3320) / (0.1858 - y / divisor)
95-
cct = 449.0 * n**3 + 3525.0 * n**2 + 6823.3 * n + 5520.33
96-
return cct, y
97-
9886
class TCS34725:
9987
"""Driver for the TCS34725 color sensor."""
10088

@@ -105,44 +93,55 @@ class TCS34725:
10593

10694
def __init__(self, i2c, address=0x29):
10795
self._device = i2c_device.I2CDevice(i2c, address)
108-
sensor_id = self._read_u8(_REGISTER_SENSORID)
10996
self._active = False
11097
self.integration_time = 2.4
98+
self._glass_attenuation = None
99+
self.glass_attenuation = 1.0
111100
# Check sensor ID is expectd value.
112101
sensor_id = self._read_u8(_REGISTER_SENSORID)
113102
if sensor_id not in (0x44, 0x10):
114103
raise RuntimeError('Could not find sensor, check wiring!')
115104

116-
def _read_u8(self, address):
117-
# Read an 8-bit unsigned value from the specified 8-bit address.
118-
with self._device as i2c:
119-
self._BUFFER[0] = (address | _COMMAND_BIT) & 0xFF
120-
i2c.write(self._BUFFER, end=1, stop=False)
121-
i2c.readinto(self._BUFFER, end=1)
122-
return self._BUFFER[0]
105+
@property
106+
def lux(self):
107+
"""The lux value computed from the color channels."""
108+
return self._temperature_and_lux_dn40()[0]
123109

124-
def _read_u16(self, address):
125-
# Read a 16-bit BE unsigned value from the specified 8-bit address.
126-
with self._device as i2c:
127-
self._BUFFER[0] = (address | _COMMAND_BIT) & 0xFF
128-
i2c.write(self._BUFFER, end=1, stop=False)
129-
i2c.readinto(self._BUFFER, end=2)
130-
return (self._BUFFER[0] << 8) | self._BUFFER[1]
110+
@property
111+
def color_temperature(self):
112+
"""The color temperature in degrees Kelvin."""
113+
return self._temperature_and_lux_dn40()[1]
131114

132-
def _write_u8(self, address, val):
133-
# Write an 8-bit unsigned value to the specified 8-bit address.
134-
with self._device as i2c:
135-
self._BUFFER[0] = (address | _COMMAND_BIT) & 0xFF
136-
self._BUFFER[1] = val & 0xFF
137-
i2c.write(self._BUFFER, end=2)
115+
@property
116+
def color_rgb_bytes(self):
117+
"""Read the RGB color detected by the sensor. Returns a 3-tuple of
118+
red, green, blue component values as bytes (0-255).
119+
"""
120+
r, g, b, clear = self.color_raw
121+
# Avoid divide by zero errors ... if clear = 0 return black
122+
if clear == 0:
123+
return (0, 0, 0)
124+
# pylint: disable=bad-whitespace
125+
red = int(pow((int((r/clear) * 256) / 255), 2.5) * 255)
126+
green = int(pow((int((g/clear) * 256) / 255), 2.5) * 255)
127+
blue = int(pow((int((b/clear) * 256) / 255), 2.5) * 255)
128+
# Handle possible 8-bit overflow
129+
if red > 255:
130+
red = 255
131+
if green > 255:
132+
green = 255
133+
if blue > 255:
134+
blue = 255
135+
return (red, green, blue)
138136

139-
def _write_u16(self, address, val):
140-
# Write a 16-bit BE unsigned value to the specified 8-bit address.
141-
with self._device as i2c:
142-
self._BUFFER[0] = (address | _COMMAND_BIT) & 0xFF
143-
self._BUFFER[1] = (val >> 8) & 0xFF
144-
self._BUFFER[2] = val & 0xFF
145-
i2c.write(self._BUFFER)
137+
@property
138+
def color(self):
139+
"""Read the RGB color detected by the sensor. Returns an int with 8 bits per channel.
140+
Examples: Red = 16711680 (0xff0000), Green = 65280 (0x00ff00),
141+
Blue = 255 (0x0000ff), SlateGray = 7372944 (0x708090)
142+
"""
143+
r, g, b = self.color_rgb_bytes
144+
return (r << 16) | (g << 8) | b
146145

147146
@property
148147
def active(self):
@@ -208,10 +207,6 @@ def interrupt(self, val):
208207
with self._device:
209208
self._device.write(b'\xe6')
210209

211-
def _valid(self):
212-
# Check if the status bit is set and the chip is ready.
213-
return bool(self._read_u8(_REGISTER_STATUS) & 0x01)
214-
215210
@property
216211
def color_raw(self):
217212
"""Read the raw RGBC color detected by the sensor. Returns a 4-tuple of
@@ -230,51 +225,6 @@ def color_raw(self):
230225
self.active = was_active
231226
return data
232227

233-
@property
234-
def color_rgb_bytes(self):
235-
"""Read the RGB color detected by the sensor. Returns a 3-tuple of
236-
red, green, blue component values as bytes (0-255).
237-
"""
238-
r, g, b, clear = self.color_raw
239-
# Avoid divide by zero errors ... if clear = 0 return black
240-
if clear == 0:
241-
return (0, 0, 0)
242-
# pylint: disable=bad-whitespace
243-
red = int(pow((int((r/clear) * 256) / 255), 2.5) * 255)
244-
green = int(pow((int((g/clear) * 256) / 255), 2.5) * 255)
245-
blue = int(pow((int((b/clear) * 256) / 255), 2.5) * 255)
246-
# Handle possible 8-bit overflow
247-
if red > 255:
248-
red = 255
249-
if green > 255:
250-
green = 255
251-
if blue > 255:
252-
blue = 255
253-
return (red, green, blue)
254-
255-
@property
256-
def color(self):
257-
"""Read the RGB color detected by the sensor. Returns an int with 8 bits per channel.
258-
259-
Examples: Red = 16711680 (0xff0000), Green = 65280 (0x00ff00),
260-
Blue = 255 (0x0000ff), SlateGray = 7372944 (0x708090)
261-
"""
262-
r, g, b = self.color_rgb_bytes
263-
return (r << 16) | (g << 8) | b
264-
265-
266-
@property
267-
def temperature(self):
268-
"""Return the detected color temperature in degrees."""
269-
temp, _ = _temperature_and_lux(self.color_raw)
270-
return temp
271-
272-
@property
273-
def lux(self):
274-
"""Return the detected light level in lux."""
275-
_, lux = _temperature_and_lux(self.color_raw)
276-
return lux
277-
278228
@property
279229
def cycles(self):
280230
"""The persistence cycles of the sensor."""
@@ -314,3 +264,105 @@ def max_value(self):
314264
@max_value.setter
315265
def max_value(self, val):
316266
self._write_u16(_REGISTER_AIHT, val)
267+
268+
def _temperature_and_lux_dn40(self):
269+
"""Converts the raw R/G/B values to color temperature in degrees
270+
Kelvin using the algorithm described in DN40 from Taos (now AMS).
271+
Also computes lux. Returns tuple with both values or tuple of Nones
272+
if computation can not be done.
273+
"""
274+
# pylint: disable=bad-whitespace, invalid-name, too-many-locals
275+
276+
# Initial input values
277+
ATIME = self._read_u8(_REGISTER_ATIME)
278+
ATIME_ms = (256 - ATIME) * 2.4
279+
AGAINx = self.gain
280+
R, G, B, C = self.color_raw
281+
282+
# Device specific values (DN40 Table 1 in Appendix I)
283+
GA = self.glass_attenuation # Glass Attenuation Factor
284+
DF = 310.0 # Device Factor
285+
R_Coef = 0.136 # |
286+
G_Coef = 1.0 # | used in lux computation
287+
B_Coef = -0.444 # |
288+
CT_Coef = 3810 # Color Temperature Coefficient
289+
CT_Offset = 1391 # Color Temperatuer Offset
290+
291+
# Analog/Digital saturation (DN40 3.5)
292+
SATURATION = 65535 if 256 - ATIME > 63 else 1024 * (256 - ATIME)
293+
294+
# Ripple saturation (DN40 3.7)
295+
if ATIME_ms < 150:
296+
SATURATION -= SATURATION / 4
297+
298+
# Check for saturation and mark the sample as invalid if true
299+
if C >= SATURATION:
300+
return None, None
301+
302+
# IR Rejection (DN40 3.1)
303+
IR = (R + G + B - C) / 2 if R + G + B > C else 0.
304+
R2 = R - IR
305+
G2 = G - IR
306+
B2 = B - IR
307+
308+
# Lux Calculation (DN40 3.2)
309+
G1 = R_Coef * R2 + G_Coef * G2 + B_Coef * B2
310+
CPL = (ATIME_ms * AGAINx) / (GA * DF)
311+
lux = G1 / CPL
312+
313+
# CT Calculations (DN40 3.4)
314+
CT = CT_Coef * B2 / R2 + CT_Offset
315+
316+
return lux, CT
317+
318+
@property
319+
def glass_attenuation(self):
320+
"""The Glass Attenuation (FA) factor used to compensate for lower light
321+
levels at the device due to the possible presence of glass. The GA is
322+
the inverse of the glass transmissivity (T), so GA = 1/T. A transmissivity
323+
of 50% gives GA = 1 / 0.50 = 2. If no glass is present, use GA = 1.
324+
See Application Note: DN40-Rev 1.0 – Lux and CCT Calculations using
325+
ams Color Sensors for more details.
326+
"""
327+
return self._glass_attenuation
328+
329+
@glass_attenuation.setter
330+
def glass_attenuation(self, value):
331+
if value < 1:
332+
raise ValueError("Glass attenuation factor must be at least 1.")
333+
self._glass_attenuation = value
334+
335+
def _valid(self):
336+
# Check if the status bit is set and the chip is ready.
337+
return bool(self._read_u8(_REGISTER_STATUS) & 0x01)
338+
339+
def _read_u8(self, address):
340+
# Read an 8-bit unsigned value from the specified 8-bit address.
341+
with self._device as i2c:
342+
self._BUFFER[0] = (address | _COMMAND_BIT) & 0xFF
343+
i2c.write(self._BUFFER, end=1, stop=False)
344+
i2c.readinto(self._BUFFER, end=1)
345+
return self._BUFFER[0]
346+
347+
def _read_u16(self, address):
348+
# Read a 16-bit unsigned value from the specified 8-bit address.
349+
with self._device as i2c:
350+
self._BUFFER[0] = (address | _COMMAND_BIT) & 0xFF
351+
i2c.write(self._BUFFER, end=1, stop=False)
352+
i2c.readinto(self._BUFFER, end=2)
353+
return (self._BUFFER[1] << 8) | self._BUFFER[0]
354+
355+
def _write_u8(self, address, val):
356+
# Write an 8-bit unsigned value to the specified 8-bit address.
357+
with self._device as i2c:
358+
self._BUFFER[0] = (address | _COMMAND_BIT) & 0xFF
359+
self._BUFFER[1] = val & 0xFF
360+
i2c.write(self._BUFFER, end=2)
361+
362+
def _write_u16(self, address, val):
363+
# Write a 16-bit unsigned value to the specified 8-bit address.
364+
with self._device as i2c:
365+
self._BUFFER[0] = (address | _COMMAND_BIT) & 0xFF
366+
self._BUFFER[1] = val & 0xFF
367+
self._BUFFER[2] = (val >> 8) & 0xFF
368+
i2c.write(self._BUFFER)

examples/tcs34725_simpletest.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,9 @@
1414

1515
# Main loop reading color and printing it every second.
1616
while True:
17-
# Read the color as RGB bytes (0-255 values).
18-
r, g, b = sensor.color_rgb_bytes
19-
print('Detected color: #{0:02X}{1:02X}{2:02X}'.format(r, g, b))
2017
# Read the color temperature and lux of the sensor too.
21-
try:
22-
temp = sensor.temperature
23-
lux = sensor.lux
24-
print('Temperature: {0}K Lux: {1}'.format(temp, lux))
25-
except ZeroDivisionError:
26-
print("No light to measure")
18+
temp = sensor.color_temperature
19+
lux = sensor.lux
20+
print('Temperature: {0}K Lux: {1}'.format(temp, lux))
2721
# Delay for a second and repeat.
2822
time.sleep(1.0)

0 commit comments

Comments
 (0)