Skip to content

Commit 9024d74

Browse files
authored
feat: implement TSL2561 light sensor (#3235)
1 parent 9062293 commit 9024d74

7 files changed

Lines changed: 641 additions & 113 deletions

File tree

assets/images/tsl2561.png

227 KB
Loading
Lines changed: 94 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import 'dart:async';
21
import 'package:pslab/communication/peripherals/i2c.dart';
32
import 'package:pslab/communication/science_lab.dart';
43
import 'package:pslab/others/logger_service.dart';
54

65
class TSL2561 {
76
static const String tag = "TSL2561";
87

8+
static const List<int> addresses = [0x39, 0x29, 0x49];
9+
910
static const int commandBit = 0x80;
1011
static const int wordBit = 0x20;
1112

@@ -18,116 +19,118 @@ class TSL2561 {
1819
static const int registerChan0Low = 0x0C;
1920
static const int registerChan1Low = 0x0E;
2021

21-
static const int integrationTime13ms = 0x00;
22-
static const int integrationTime101ms = 0x01;
23-
static const int integrationTime402ms = 0x02;
24-
25-
static const int gain0x = 0x00;
26-
static const int gain16x = 0x10;
22+
static const int integrationTime13Ms = 0x00;
23+
static const int integrationTime101Ms = 0x01;
24+
static const int integrationTime402Ms = 0x02;
2725

28-
static const List<int> addresses = [0x39, 0x29, 0x49];
26+
static const int gain0X = 0x00;
27+
static const int gain16X = 0x10;
2928

3029
final I2C i2c;
31-
int address = 0x39;
32-
int timing = integrationTime13ms;
33-
int gain = gain16x;
34-
35-
TSL2561(this.i2c, ScienceLab scienceLab) {
36-
() async {
37-
if (scienceLab.isConnected()) {
38-
for (final addr in addresses) {
39-
address = addr;
40-
await disable();
41-
logger.d("$tag: Checking address 0x${address.toRadixString(16)}");
42-
try {
43-
int id = await i2c.readByte(address, registerId);
44-
if (id != 0xffffffff && (id & 0x0A) == 0x0A) {
45-
logger.d("$tag: TSL2561 found!");
46-
break;
47-
} else {
48-
logger.d("$tag: TSL2561 not found.");
49-
}
50-
} catch (e) {
51-
logger.e("$tag: Error reading ID: $e");
52-
}
53-
}
54-
await enable();
55-
await _wait();
56-
await i2c
57-
.writeBulk(address, [commandBit | registerTiming, timing | gain]);
58-
}
59-
}();
30+
int? _address;
31+
int _timing = integrationTime13Ms;
32+
int _gain = gain16X;
33+
34+
double fullSpectrum = 0.0;
35+
double infrared = 0.0;
36+
double visible = 0.0;
37+
38+
TSL2561._(this.i2c);
39+
40+
static Future<TSL2561> create(I2C i2c, ScienceLab scienceLab) async {
41+
final tsl2561 = TSL2561._(i2c);
42+
await tsl2561._initializeSensor(scienceLab);
43+
return tsl2561;
6044
}
6145

62-
Future<int> getID() async {
63-
try {
64-
List<int> idList = await i2c.readBulk(address, registerId, 1);
65-
if (idList.isEmpty) return -1;
66-
int id = int.parse(idList[0].toRadixString(16), radix: 16);
67-
logger.d("$tag: ID: $id");
68-
return id;
69-
} catch (e) {
70-
logger.e("$tag: Error getting ID: $e");
71-
rethrow;
46+
Future<void> _initializeSensor(ScienceLab scienceLab) async {
47+
if (!scienceLab.isConnected()) {
48+
throw Exception("ScienceLab not connected");
7249
}
73-
}
7450

75-
Future<double?> getRaw() async {
51+
bool sensorFound = false;
52+
7653
try {
77-
List<int> infraList = await i2c.readBulk(
78-
address, commandBit | wordBit | registerChan1Low, 2);
79-
List<int> fullList = await i2c.readBulk(
80-
address, commandBit | wordBit | registerChan0Low, 2);
54+
for (int addr in addresses) {
55+
_address = addr;
56+
await disable();
8157

82-
if (infraList.isNotEmpty && fullList.isNotEmpty) {
83-
int full = ((fullList[1] & 0xff) << 8) | (fullList[0] & 0xff);
84-
int infra = ((infraList[1] & 0xff) << 8) | (infraList[0] & 0xff);
85-
return (full - infra).toDouble();
86-
} else {
87-
return 0.0;
58+
logger.d("$tag: Checking address 0x${addr.toRadixString(16)}");
59+
60+
List<int> idData = await i2c.readBulk(addr, registerId | commandBit, 1);
61+
62+
if (idData.isNotEmpty) {
63+
int id = idData[0];
64+
logger.d(
65+
"$tag: RAW ID READ at 0x${addr.toRadixString(16)} is: 0x${id.toRadixString(16)} (Decimal: $id)");
66+
67+
if (id != 255) {
68+
logger.d("$tag: Sensor accepted at 0x${addr.toRadixString(16)}!");
69+
sensorFound = true;
70+
break;
71+
}
72+
}
73+
}
74+
75+
if (!sensorFound) {
76+
throw Exception(
77+
'TSL2561 sensor not found on I2C bus. Check SDA/SCL wiring.');
8878
}
89-
} catch (e) {
90-
logger.e("$tag: Error reading raw values: $e");
91-
return 0.0;
92-
}
93-
}
9479

95-
Future<void> setGain(int gainValue) async {
96-
switch (gainValue) {
97-
case 1:
98-
gain = gain0x;
99-
break;
100-
case 16:
101-
gain = gain16x;
102-
break;
103-
default:
104-
gain = gain16x;
80+
await enable();
81+
await Future.delayed(const Duration(milliseconds: 15));
82+
await setGainAndTiming(_gain, _timing);
83+
} catch (e) {
84+
logger.e("Error initializing TSL2561: $e");
85+
rethrow;
10586
}
106-
await i2c.writeBulk(address, [commandBit | registerTiming, gain | timing]);
10787
}
10888

10989
Future<void> enable() async {
110-
await i2c
111-
.writeBulk(address, [commandBit | registerControl, controlPowerOn]);
90+
if (_address == null) return;
91+
await i2c.write(_address!, [controlPowerOn], commandBit | registerControl);
11292
}
11393

11494
Future<void> disable() async {
115-
await i2c
116-
.writeBulk(address, [commandBit | registerControl, controlPowerOff]);
95+
if (_address == null) return;
96+
await i2c.write(_address!, [controlPowerOff], commandBit | registerControl);
11797
}
11898

119-
Future<void> _wait() async {
120-
switch (timing) {
121-
case integrationTime13ms:
122-
await Future.delayed(const Duration(milliseconds: 14));
123-
break;
124-
case integrationTime101ms:
125-
await Future.delayed(const Duration(milliseconds: 102));
126-
break;
127-
case integrationTime402ms:
128-
default:
129-
await Future.delayed(const Duration(milliseconds: 403));
130-
break;
99+
Future<void> setGainAndTiming(int gain, int timing) async {
100+
if (_address == null) return;
101+
_gain = gain;
102+
_timing = timing;
103+
await i2c.write(_address!, [_gain | _timing], commandBit | registerTiming);
104+
}
105+
106+
Future<Map<String, double>> getRawData() async {
107+
if (_address == null) throw Exception("Sensor not initialized");
108+
109+
try {
110+
List<int> infraList = await i2c.readBulk(
111+
_address!, commandBit | wordBit | registerChan1Low, 2);
112+
List<int> fullList = await i2c.readBulk(
113+
_address!, commandBit | wordBit | registerChan0Low, 2);
114+
115+
if (infraList.length >= 2 && fullList.length >= 2) {
116+
int fullInt = ((fullList[1] & 0xFF) << 8) | (fullList[0] & 0xFF);
117+
int infraInt = ((infraList[1] & 0xFF) << 8) | (infraList[0] & 0xFF);
118+
119+
fullSpectrum = fullInt.toDouble();
120+
infrared = infraInt.toDouble();
121+
visible = (fullInt - infraInt).toDouble();
122+
123+
return {
124+
'full': fullSpectrum,
125+
'infrared': infrared,
126+
'visible': visible,
127+
};
128+
} else {
129+
throw Exception("Incomplete data received from TSL2561");
130+
}
131+
} catch (e) {
132+
logger.e("Error getting raw data: $e");
133+
rethrow;
131134
}
132135
}
133136
}

lib/l10n/app_en.arb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,5 +670,13 @@
670670
"saw" : "Saw",
671671
"comingSoon": "Coming Soon",
672672
"startRecording": "Start Recording",
673-
"stopRecording": "Stop Recording"
673+
"stopRecording": "Stop Recording",
674+
"tsl2561" : "TSL2561",
675+
"full": "Full Spectrum",
676+
"infrared": "Infrared",
677+
"infraredRaw" : "Infrared (raw)",
678+
"visibleLight": "Visible Light",
679+
"visibleLightRaw" : "Visible Light (raw)",
680+
"luxRaw" : "Luminosity (lux/raw)",
681+
"raw" : "raw"
674682
}

lib/providers/luxmeter_state_provider.dart

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -226,45 +226,46 @@ class LuxMeterStateProvider extends ChangeNotifier {
226226
_scienceLab = getIt<ScienceLab>();
227227
if (_scienceLab == null || !_scienceLab!.isConnected()) {
228228
onSensorError?.call('ScienceLab not connected');
229+
return;
229230
}
230231

231232
_i2c = I2C(_scienceLab!.mPacketHandler);
232-
_tsl2561 = TSL2561(_i2c!, _scienceLab!);
233+
234+
_tsl2561 = await TSL2561.create(_i2c!, _scienceLab!);
233235

234236
int intervalMs = _configProvider?.config.updatePeriod ?? 1000;
235237

236-
int gain = _configProvider?.config.sensorGain ?? 16;
237-
_tsl2561!.setGain(gain);
238+
if (intervalMs < 100) intervalMs = 100;
238239

239-
_startTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
240+
int configGain = _configProvider?.config.sensorGain ?? 16;
241+
int tslGain = (configGain == 1) ? TSL2561.gain0X : TSL2561.gain16X;
240242

241-
int lastUpdate = 0;
243+
await _tsl2561!.setGainAndTiming(tslGain, TSL2561.integrationTime101Ms);
242244

243-
_luxTimer = Timer.periodic(Duration(milliseconds: 10), (timer) async {
245+
_startTime = DateTime.now().millisecondsSinceEpoch / 1000.0;
246+
247+
_luxTimer =
248+
Timer.periodic(Duration(milliseconds: intervalMs), (timer) async {
244249
try {
245-
final lux = await _tsl2561?.getRaw();
246-
if (lux != null) {
247-
_currentLux = lux;
248-
_sensorAvailable = true;
250+
final rawData = await _tsl2561?.getRawData();
249251

250-
double currentTime =
252+
if (rawData != null) {
253+
_currentLux = rawData['visible'] ?? 0.0;
254+
_sensorAvailable = true;
255+
_currentTime =
251256
(DateTime.now().millisecondsSinceEpoch / 1000.0) - _startTime;
252257

253-
if ((currentTime * 1000 - lastUpdate) >= intervalMs) {
254-
lastUpdate = (currentTime * 1000).toInt();
255-
_currentTime = currentTime;
256-
_updateData();
257-
notifyListeners();
258-
}
258+
_updateData();
259+
notifyListeners();
259260
}
260261
} catch (e) {
261262
logger.e("TSL2561 read error: $e");
262263
_handleSensorError(e);
263264
}
264265
});
265266

266-
logger
267-
.d('TSL2561 initialized with gain $gain at interval $intervalMs ms');
267+
logger.d(
268+
'TSL2561 initialized with gain ${configGain}x at interval $intervalMs ms');
268269
} catch (e) {
269270
logger.e("Error initializing TSL2561: $e");
270271
_handleSensorError(e);

0 commit comments

Comments
 (0)