TSL2561 Calculations wrong during very bright light?


I think there is a problem with TSL2561 lux calculations. I find when it gets very bright outside (I’m measuring light inside a window), the lux calculation displayed starts going down. This graph shows the green line of lux displayed on cayenne, dipping at about 11:20 on the graph. The red line of Visible+Infrared light has maxed-out at 65536 (2^16), and the Infrared (blue) line continues to rise, so the lux calculation starts to fall. The calculation for this devices is a little complex, but in general it is Visible+Infrared minus Infrared to get the Visible light, so I think once the Visible+Infrared hits 2^16, then the device has to be reconfigured to a smaller sample size to keep reading the very bright sun accurately. Any thoughts?



Hello Sir,
I also use the TLS2561 sensor. Can you post here the code that you use?
Thank you :slight_smile:


The graph was just the lux as calculated from the automatic cayenne system, and then some mqtt code to store the Vis+Infra and the Infra values in the cayenne history. Here is my code for testing the device - I could not find any good code for doing the lux calculations in different modes that I could get working. It all seemed to be written using integer math, but this uses floating point math. The calcs here match the Lux meter on my cellphone quite nicely, but the automatic cayenne calc is always much lower. Perhaps I am screwing up the automatic cayenne calculation by sampling to store the raw data ??

Please let me know if I read the datasheet wrong, and these lux calcs are wrong!

# python 2.7
# code scavanged and modified

import smbus
import time
import datetime

TSLaddr = 0x39 	#Default I2C address, alternate 0x29, 0x49 
TSLcmd = 0x80 	#Command
chan0 = 0x0C 	#Read Channel0 sensor date
chan1 = 0x0E 	#Read channel1 sensor data
TSLon = 0x03 	#Switch sensors on
TSLoff = 0x00 	#Switch sensors off

#Exposure settings
NormShort = 0x00 	# x1 Gain 13.7 miliseconds
NormMed = 0x01 		# x1 Gain 101 miliseconds
NormLong = 0x02 	# x1 Gain 402 miliseconds
NormManual = 0x03 	# x1 Gain Manual
DarkShort = 0x10 	# x16 Gain 13.7 miliseconds
DarkMed = 0x11		# x16 Gain 100 miliseconds
DarkLong = 0x12 	# x16 Gain 402 miliseconds
DarkManual = 0x13 	# x16 Gain Manual

#Manual Settings
ManDelay = 2 	#Manual Exposure in Seconds
StartMan = 0x1F #Start Manual Exposure
EndMan = 0x1E 	#End Manual Exposure

	#Enter in [] the Exposure Setting to use 
	sequence = [NormLong,NormMed,NormShort,DarkLong,DarkMed,DarkShort]
	print("Unknown Exposure Setting used, defaulting to NormLong (x1 402ms")
	sequence = [NormLong]*vRepeat
# Get I2C bus
bus = smbus.SMBus(1)
writebyte = bus.write_byte_data

writebyte(TSLaddr, 0x00 | TSLcmd, TSLon) #Power On

def manual(delay,mode):
	"""manual exposure"""
	bus.write_byte_data(TSLaddr, 0x01 | TSLcmd, mode) #sensativity mode
	bus.write_byte_data(TSLaddr, 0x01 | TSLcmd, StartMan) #start detection
	time.sleep(delay) #exposure
	bus.write_byte_data(TSLaddr, 0x01 | TSLcmd, EndMan) #stop detection

print "Part Number", bus.read_byte_data(TSLaddr, 0x8A)

for mode in sequence:
	if mode != 3 and mode != 19: 	#Selected built in delay for exposure. If Manual  mode not set
		writebyte(TSLaddr, 0x01 | TSLcmd, mode)		

	else: 				#use manual exposure

	#Read ch0 Word then ch1
	data = bus.read_i2c_block_data(TSLaddr, chan0 | TSLcmd, 2)
	data1 = bus.read_i2c_block_data(TSLaddr, chan1 | TSLcmd, 2)

	# Convert the data to Integer
	ch0 = data[1] * 256 + data[0]
	ch1 = data1[1] * 256 + data1[0]	
    if ch0 > 0:
	VIratio = 1.0 * ch1/ch0
	VIratio = 2  # no light

	#print ("VIratio = ", VIratio)

	if VIratio <= 0.52:
		lux = 0.0315 * ch0 - 0.0593 * ch0 * ((VIratio)**1.4)
	elif VIratio > 0.52 and VIratio <= 0.65:
		lux = 0.0229 *ch0 - 0.0291 * ch1
	elif VIratio > 0.65 and VIratio <= 0.8:
		lux = 0.00157 * ch0 - 0.00180 * ch1
	elif VIratio > 0.8 and VIratio <= 1.3:
		lux = 0.00338 * ch0 - 0.00260 * ch1
	elif VIratio > 1.3:
		lux = 0

	status = "Normal"
	if ch0 > 0:
		if mode == NormLong:
			slux = lux * 16;
			if ch1 >= 65535 or ch0 >= 65535:
				status = "Exceed NormLong clipping"
		elif mode == NormMed:
			slux = lux * 65.136 	# 4071 / 1000 * 16
			if ch1 >= 37177 or ch0 >= 37177:
				status = "Exceed NormMed clipping"
		elif mode == NormShort:
			slux = lux * 479.6	# 29975 / 1000 * 16
			if ch1 >= 5047 or ch0 >= 5047:
				status = "Exceed NormShort clipping"
		elif mode == DarkLong:
			slux = lux 
			if ch1 >= 65535 or ch0 >= 65535:
				status = "Exceed DarkLong clipping"
		elif mode == DarkMed:
			slux = lux * 4.071 	# 4071 / 1000	 
			if ch1 >= 37177 or ch0 >= 37177:
				status = "Exceed DarkMed clipping"
		elif mode == DarkShort:
			slux = lux * 29.975 	# 29975 / 1000
			if ch1 >= 5047 or ch0 >= 5047:
				status = "Exceed DarkShort clipping"
			print "Out of modes - manual not implemented"

		print datetime.datetime.now(), "Mode: ", mode," Ratio: ", round(VIratio,2)," V+IR",ch0, " IR",ch1, "Lux", round(lux), "Scaled Lux", round(slux), " ", status
		#either no light or clipping value exceeded due to too much light
		print datetime.datetime.now(), "Mode: ", mode," V+IR",ch0, " IR",ch1, "No Light"

#Power Off
#writebyte(TSLaddr, 0x00 | TSLcmd, TSLoff)


My conclusion is that cayenne sets up the device as the 402ms sample, and then just reads the samples.so the blue line (my calculation of lux) and the red line (cayenne calculation) match when the light is low (the bottom level of green spots), but when the light increases and the device reaches its limits, I switch to the 101ms sample (middle level of green), and the to the 13ms sample (top level of green), and the lux number is higher, but the cayenne lux calculations decline, given inaccurate readings.

Interesting to note, at 11:00 on the graph, the 13ms sample is turned on, but the device is also maxed-out on its capabilities at about 5000 (the dotted red line), and so this device cannot give accurate readings of a very bright sunshine … through a dirty window, in November at 51 degrees North at 3500 feet.


Are you using the Cayenne agent connection method or MQTT? I can test this on my device and see what the results are.


Hi, I am using both methods.
The cayenne agent sets up the device in the default sampling method which only measures light up to about 15,000 lux. My mqtt program switches the setup to measure up to about 45,000 which is the practical limit for the device. When the light exceeds 15,000 the results become somewhat random, and the results are even worse when the device is reconfigured and cayenne cannot interpret the results correctly.

The 0 - 15,000 range is reasonable for indoor light, but not useful if you have sunlight involved, which can easily hit 45,000 or 100,000. The 0 - 15,000 range will get more precise results in a dim room, but it seems to me that the 0 - 45,000 is generally more useful.


Can you share the data?

There may be a filtering algorithm we can apply to reject spurious responses.




Can you share the MQTT code you are using? I’ll try out both methods.