Skip to content

Commit 4339d62

Browse files
feat: ported APDS9960 sensor screen. (#2838)
Co-authored-by: Marc Nause <[email protected]>
1 parent 45e000e commit 4339d62

File tree

8 files changed

+1160
-3
lines changed

8 files changed

+1160
-3
lines changed
Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
import 'dart:math';
2+
import 'package:pslab/communication/peripherals/i2c.dart';
3+
import 'package:pslab/communication/science_lab.dart';
4+
import 'package:pslab/others/logger_service.dart';
5+
6+
import '../../l10n/app_localizations.dart';
7+
import '../../providers/locator.dart';
8+
9+
class APDS9960 {
10+
AppLocalizations appLocalizations = getIt.get<AppLocalizations>();
11+
12+
static const String tag = "APDS9960";
13+
14+
static const int address = 0x39;
15+
16+
static const int enable = 0x80;
17+
static const int atime = 0x81;
18+
static const int pilt = 0x89;
19+
static const int pers = 0x8C;
20+
static const int control = 0x8F;
21+
static const int status = 0x93;
22+
static const int cdatal = 0x94;
23+
static const int pdata = 0x9C;
24+
static const int gpenth = 0xA0;
25+
static const int gexth = 0xA1;
26+
static const int gconf1 = 0xA2;
27+
static const int gconf2 = 0xA3;
28+
static const int gpulse = 0xA6;
29+
static const int gconf4 = 0xAB;
30+
static const int gflvl = 0xAE;
31+
static const int gstatus = 0xAF;
32+
static const int aiclear = 0xE7;
33+
static const int gfifoU = 0xFC;
34+
35+
static const int bitMaskEnableEn = 0x01;
36+
static const int bitMaskEnableColor = 0x02;
37+
static const int bitMaskEnableProx = 0x04;
38+
static const int bitMaskEnableGesture = 0x40;
39+
static const int bitMaskStatusGint = 0x04;
40+
static const int bitMaskGstatusGfov = 0x02;
41+
static const int bitMaskGconf4GfifoCLr = 0x04;
42+
43+
static const int bitPosPersPpers = 4;
44+
static const int bitMaskPersPpers = 0xF0;
45+
46+
static const int bitPosControlAgain = 0;
47+
static const int bitMaskControlAgain = 3;
48+
49+
final I2C i2c;
50+
51+
List<int> colorData = [0, 0, 0, 0];
52+
double lux = 0.0;
53+
int proximity = 0;
54+
int gesture = 0;
55+
56+
APDS9960._(this.i2c);
57+
58+
static Future<APDS9960> create(I2C i2c, ScienceLab scienceLab) async {
59+
final apds9960 = APDS9960._(i2c);
60+
await apds9960._initialize(scienceLab);
61+
return apds9960;
62+
}
63+
64+
Future<void> _initialize(ScienceLab scienceLab) async {
65+
if (!scienceLab.isConnected()) {
66+
throw Exception("ScienceLab not connected");
67+
}
68+
69+
try {
70+
await enableProximity(false);
71+
await enableGesture(false);
72+
await enableColor(false);
73+
74+
await setProximityInterruptThreshold([0, 0, 0]);
75+
await i2c.write(address, [0], gpenth);
76+
await i2c.write(address, [0], gexth);
77+
await i2c.write(address, [0], gconf1);
78+
await i2c.write(address, [0], gconf2);
79+
await i2c.write(address, [0], gconf4);
80+
await i2c.write(address, [0], gpulse);
81+
await i2c.write(address, [255], atime);
82+
await i2c.write(address, [0], control);
83+
84+
await clearInterrupt();
85+
86+
await setBit(gconf4, bitMaskGconf4GfifoCLr, true);
87+
88+
await i2c.write(address, [0], enable);
89+
await Future.delayed(const Duration(milliseconds: 25));
90+
91+
await _enable(true);
92+
await Future.delayed(const Duration(milliseconds: 10));
93+
94+
await setProximityInterruptThreshold([0, 5, 4]);
95+
await i2c.write(address, [0x05], gpenth);
96+
await i2c.write(address, [0x1E], gexth);
97+
await i2c.write(address, [0x82], gconf1);
98+
await i2c.write(address, [0x41], gconf2);
99+
await i2c.write(address, [0x85], gpulse);
100+
await setColorIntegrationTime(256);
101+
await setColorGain(1);
102+
103+
logger.d("APDS9960 initialized successfully");
104+
} catch (e) {
105+
logger.e("Error initializing APDS9960: $e");
106+
rethrow;
107+
}
108+
}
109+
110+
Future<void> _enable(bool value) async {
111+
await setBit(enable, bitMaskEnableEn, value);
112+
}
113+
114+
Future<void> enableProximity(bool value) async {
115+
await setBit(enable, bitMaskEnableProx, value);
116+
}
117+
118+
Future<void> enableGesture(bool value) async {
119+
await setBit(enable, bitMaskEnableGesture, value);
120+
}
121+
122+
Future<void> enableColor(bool value) async {
123+
await setBit(enable, bitMaskEnableColor, value);
124+
}
125+
126+
Future<void> setProximityInterruptThreshold(List<int> settingArray) async {
127+
if (settingArray.isNotEmpty &&
128+
settingArray[0] >= 0 &&
129+
settingArray[0] <= 255) {
130+
await i2c.write(address, [settingArray[0]], pilt);
131+
}
132+
if (settingArray.length > 1 &&
133+
settingArray[1] >= 0 &&
134+
settingArray[1] <= 255) {
135+
await i2c.write(address, [settingArray[1]], pilt);
136+
}
137+
int persist = 4;
138+
if (settingArray.length > 2 &&
139+
settingArray[2] >= 0 &&
140+
settingArray[2] <= 15) {
141+
persist = min(settingArray[2], 15);
142+
await setBits(pers, bitPosPersPpers, bitMaskPersPpers, persist);
143+
}
144+
}
145+
146+
Future<void> clearInterrupt() async {
147+
await i2c.write(address, [], aiclear);
148+
}
149+
150+
Future<void> setColorIntegrationTime(int value) async {
151+
await i2c.write(address, [256 - value], atime);
152+
}
153+
154+
Future<void> setColorGain(int value) async {
155+
await setBits(control, bitPosControlAgain, bitMaskControlAgain, value);
156+
}
157+
158+
Future<int> getProximity() async {
159+
try {
160+
List<int> data = await i2c.readBulk(address, pdata, 1);
161+
proximity = data[0] & 0xFF;
162+
return proximity;
163+
} catch (e) {
164+
logger.e("Error reading proximity: $e");
165+
rethrow;
166+
}
167+
}
168+
169+
Future<List<int>> getColorData() async {
170+
try {
171+
colorData = [
172+
await _colorData16(cdatal + 2),
173+
await _colorData16(cdatal + 4),
174+
await _colorData16(cdatal + 6),
175+
await _colorData16(cdatal),
176+
];
177+
return colorData;
178+
} catch (e) {
179+
logger.e("Error reading color data: $e");
180+
rethrow;
181+
}
182+
}
183+
184+
Future<double> getLux() async {
185+
try {
186+
await getColorData();
187+
lux = (-0.32466 * colorData[0]) +
188+
(1.57837 * colorData[1]) +
189+
(-0.73191 * colorData[2]);
190+
return lux;
191+
} catch (e) {
192+
logger.e("Error calculating lux: $e");
193+
rethrow;
194+
}
195+
}
196+
197+
Future<int> getGesture() async {
198+
try {
199+
if (await getBit(gstatus, bitMaskGstatusGfov)) {
200+
await setBit(gconf4, bitMaskGconf4GfifoCLr, true);
201+
int waitCycles = 0;
202+
while (!(await getBit(status, bitMaskStatusGint)) && waitCycles <= 30) {
203+
await Future.delayed(const Duration(milliseconds: 3));
204+
waitCycles++;
205+
}
206+
}
207+
208+
List<List<int>> frame = [];
209+
List<int> datasetsAvailableData = await i2c.readBulk(address, gflvl, 1);
210+
int datasetsAvailable = datasetsAvailableData[0] & 0xFF;
211+
212+
if (await getBit(status, bitMaskStatusGint) && datasetsAvailable > 0) {
213+
while (true) {
214+
List<int> datasetCountData = await i2c.readBulk(address, gflvl, 1);
215+
int datasetCount = datasetCountData[0] & 0xFF;
216+
if (datasetCount == 0) break;
217+
218+
List<int> buffer =
219+
await i2c.readBulk(address, gfifoU, min(128, datasetCount * 4));
220+
221+
for (int i = 0; i < datasetCount; i++) {
222+
List<int> bufferDataset = [];
223+
for (int j = 0; j < 4; j++) {
224+
bufferDataset.add(buffer[i * 4 + j] & 0xFF);
225+
}
226+
227+
bool fullySaturated = bufferDataset.every((val) => val == 255);
228+
bool fullyZero = bufferDataset.every((val) => val == 0);
229+
bool highCount = bufferDataset.every((val) => val >= 30);
230+
231+
if (!fullySaturated && !fullyZero && highCount) {
232+
if (frame.length < 2) {
233+
frame.add(bufferDataset);
234+
} else {
235+
frame[1] = bufferDataset;
236+
}
237+
}
238+
}
239+
await Future.delayed(const Duration(milliseconds: 30));
240+
}
241+
}
242+
243+
if (frame.length < 2) {
244+
gesture = 0;
245+
return gesture;
246+
}
247+
248+
List<int> frame0 = frame[0];
249+
List<int> frame1 = frame[1];
250+
251+
int frUd = _calcDelta(frame0[0], frame0[1]);
252+
int frLr = _calcDelta(frame0[2], frame0[3]);
253+
int lrUd = _calcDelta(frame1[0], frame1[1]);
254+
int lrLr = _calcDelta(frame1[2], frame1[3]);
255+
256+
int deltaUd = lrUd - frUd;
257+
int deltaLr = lrLr - frLr;
258+
259+
int stateUd = _getState(deltaUd);
260+
int stateLr = _getState(deltaLr);
261+
262+
gesture = _determineGesture(stateUd, stateLr, deltaUd, deltaLr);
263+
return gesture;
264+
} catch (e) {
265+
logger.e("Error reading gesture: $e");
266+
rethrow;
267+
}
268+
}
269+
270+
int _calcDelta(int a, int b) {
271+
if (a + b == 0) return 0;
272+
return ((a - b) * 100) ~/ (a + b);
273+
}
274+
275+
int _getState(int delta) {
276+
if (delta >= 30) return 1;
277+
if (delta <= -30) return -1;
278+
return 0;
279+
}
280+
281+
int _determineGesture(int stateUd, int stateLr, int deltaUd, int deltaLr) {
282+
if (stateUd == -1 && stateLr == 0) return 1;
283+
if (stateUd == 1 && stateLr == 0) return 2;
284+
if (stateUd == 0 && stateLr == -1) return 3;
285+
if (stateUd == 0 && stateLr == 1) return 4;
286+
287+
bool udDominant = deltaUd.abs() > deltaLr.abs();
288+
if (stateUd == -1 && stateLr == 1) return udDominant ? 1 : 4;
289+
if (stateUd == 1 && stateLr == -1) return udDominant ? 2 : 3;
290+
if (stateUd == -1 && stateLr == -1) return udDominant ? 1 : 3;
291+
if (stateUd == 1 && stateLr == 1) return udDominant ? 2 : 4;
292+
return 0;
293+
}
294+
295+
Future<bool> getBit(int register, int mask) async {
296+
try {
297+
List<int> data = await i2c.readBulk(address, register, 1);
298+
return ((data[0] & 0xFF) & mask) != 0;
299+
} catch (e) {
300+
logger.e("Error reading bit from register $register: $e");
301+
rethrow;
302+
}
303+
}
304+
305+
Future<void> setBit(int register, int mask, bool value) async {
306+
try {
307+
List<int> data = await i2c.readBulk(address, register, 1);
308+
int currentValue = data[0] & 0xFF;
309+
if (value) {
310+
currentValue |= mask;
311+
} else {
312+
currentValue &= ~mask;
313+
}
314+
await i2c.write(address, [currentValue], register);
315+
} catch (e) {
316+
logger.e("Error setting bit in register $register: $e");
317+
rethrow;
318+
}
319+
}
320+
321+
Future<void> setBits(int register, int pos, int mask, int value) async {
322+
try {
323+
List<int> data = await i2c.readBulk(address, register, 1);
324+
int currentValue = data[0] & 0xFF;
325+
currentValue = (currentValue & ~mask) | (value << pos);
326+
await i2c.write(address, [currentValue], register);
327+
} catch (e) {
328+
logger.e("Error setting bits in register $register: $e");
329+
rethrow;
330+
}
331+
}
332+
333+
Future<int> _colorData16(int register) async {
334+
try {
335+
List<int> data = await i2c.readBulk(address, register, 2);
336+
return ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
337+
} catch (e) {
338+
logger.e("Error reading 16-bit color data from register $register: $e");
339+
rethrow;
340+
}
341+
}
342+
343+
Future<Map<String, dynamic>> getRawData(int mode) async {
344+
try {
345+
Map<String, dynamic> data = {};
346+
347+
if (mode == 0) {
348+
await enableGesture(false);
349+
await enableColor(true);
350+
await enableProximity(true);
351+
352+
data['colorData'] = await getColorData();
353+
data['lux'] = await getLux();
354+
data['proximity'] = await getProximity();
355+
} else {
356+
await enableColor(false);
357+
await enableGesture(true);
358+
await enableProximity(true);
359+
360+
data['gesture'] = await getGesture();
361+
}
362+
363+
return data;
364+
} catch (e) {
365+
logger.e("Error getting raw data: $e");
366+
rethrow;
367+
}
368+
}
369+
370+
String getGestureString(int gestureValue) {
371+
switch (gestureValue) {
372+
case 1:
373+
return 'Up';
374+
case 2:
375+
return 'Down';
376+
case 3:
377+
return 'Left';
378+
case 4:
379+
return 'Right';
380+
default:
381+
return '';
382+
}
383+
}
384+
}

0 commit comments

Comments
 (0)