Skip to content

Commit ec4ddae

Browse files
authored
Merge pull request #32 from arduino/bsec+examples
Add support for Bsec sensors
2 parents fa57762 + f3b1090 commit ec4ddae

File tree

10 files changed

+218
-19
lines changed

10 files changed

+218
-19
lines changed

Arduino_BHY2/src/Arduino_BHY2.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "sensors/SensorOrientation.h"
1010
#include "sensors/SensorXYZ.h"
1111
#include "sensors/SensorQuaternion.h"
12+
#include "sensors/SensorBSEC.h"
1213
#include "sensors/SensorActivity.h"
1314
#include "sensors/Sensor.h"
1415

Arduino_BHY2/src/sensors/DataParser.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,35 @@ void DataParser::parseQuaternion(SensorDataPacket& data, DataQuaternion& vector)
2424
vector.accuracy = data.getUint16(8);
2525
}
2626

27+
void DataParser::parseBSEC(SensorDataPacket& data, DataBSEC& vector) {
28+
const float SCALE_BSEC_BVOC_EQ = 0.01f;
29+
const float SCALE_BSEC_COMP_T = 1.0f / 256;
30+
const float SCALE_BSEC_COMP_H = 1.0f / 500;
31+
32+
vector.iaq = data.getUint16(0);
33+
vector.iaq_s = data.getUint16(2);
34+
vector.b_voc_eq = data.getUint16(4) * SCALE_BSEC_BVOC_EQ; //b-VOC-eq in the FIFO frame is scaled up by 100
35+
vector.co2_eq = data.getUint24(6);
36+
vector.accuracy = data.getUint8(9);
37+
vector.comp_t = data.getInt16(10) * SCALE_BSEC_COMP_T;
38+
vector.comp_h = data.getUint16(12) * SCALE_BSEC_COMP_H;
39+
vector.comp_g = (uint32_t)(data.getFloat(14));
40+
}
41+
42+
void DataParser::parseBSECLegacy(SensorDataPacket& data, DataBSEC& vector) {
43+
vector.comp_t = data.getFloat(0);
44+
vector.comp_h = data.getFloat(4);
45+
//note that: SENSOR_DATA_FIXED_LENGTH is defined as 10 by default,
46+
//so all the fields below are 0 unless it's redefined to 29 and above
47+
vector.comp_g = (uint32_t)(data.getFloat(8));
48+
vector.iaq = (uint16_t)(data.getFloat(12));
49+
vector.iaq_s = (uint16_t)(data.getFloat(16));
50+
vector.co2_eq = (uint32_t)data.getFloat(20);
51+
vector.b_voc_eq = data.getFloat(24);
52+
vector.accuracy = data.getUint8(28);
53+
}
54+
55+
2756
void DataParser::parseData(SensorDataPacket& data, float& value, float scaleFactor, SensorPayload format) {
2857
uint8_t id = data.sensorId;
2958
switch (format) {
@@ -58,4 +87,4 @@ void DataParser::parseData(SensorDataPacket& data, float& value, float scaleFact
5887

5988
void DataParser::parseActivity(SensorDataPacket& data, uint16_t& value) {
6089
value = data.getUint16(0);
61-
}
90+
}

Arduino_BHY2/src/sensors/DataParser.h

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ struct DataOrientation {
2323
float roll;
2424

2525
String toString() {
26-
return (String)("Orientation values - heading: " + String(heading, 3)
27-
+ " pitch: " + String(pitch, 3)
26+
return (String)("Orientation values - heading: " + String(heading, 3)
27+
+ " pitch: " + String(pitch, 3)
2828
+ " roll: " + String(roll, 3) + "\n");
2929
}
3030
};
@@ -39,19 +39,45 @@ struct DataQuaternion {
3939
String toString() {
4040
return (String)("Quaternion values - X: " + String(x)
4141
+ " Y: " + String(y)
42-
+ " Z: " + String(z)
43-
+ " W: " + String(w)
44-
+ " Accuracy: " + String(accuracy)
42+
+ " Z: " + String(z)
43+
+ " W: " + String(w)
44+
+ " Accuracy: " + String(accuracy)
4545
+ "\n");
4646
}
4747
};
4848

49+
struct DataBSEC {
50+
uint16_t iaq; //iaq value for regular use case
51+
uint16_t iaq_s; //iaq value for stationary use cases
52+
float b_voc_eq; //breath VOC equivalent (ppm)
53+
uint32_t co2_eq; //CO2 equivalent (ppm) [400,]
54+
float comp_t; //compensated temperature (celcius)
55+
float comp_h; //compensated humidity
56+
uint32_t comp_g; //compensated gas resistance (Ohms)
57+
uint8_t accuracy; //accuracy level: [0-3]
58+
59+
String toString() {
60+
return (String)("BSEC output values - iaq: " + String(iaq)
61+
+ " iaq_s: " + String(iaq_s)
62+
+ " b_voc_eq: " + String(b_voc_eq, 2)
63+
+ " co2_eq: " + String(co2_eq)
64+
+ " accuracy: " + String(accuracy)
65+
+ " comp_t: " + String(comp_t, 2)
66+
+ " comp_h: " + String(comp_h, 2)
67+
+ " comp_g: " + String(comp_g)
68+
+ "\n");
69+
}
70+
};
71+
72+
4973
class DataParser {
5074
public:
5175
static void parse3DVector(SensorDataPacket& data, DataXYZ& vector);
5276
static void parseEuler(SensorDataPacket& data, DataOrientation& vector);
5377
static void parseEuler(SensorDataPacket& data, DataOrientation& vector, float scaleFactor);
5478
static void parseQuaternion(SensorDataPacket& data, DataQuaternion& vector);
79+
static void parseBSEC(SensorDataPacket& data, DataBSEC& vector);
80+
static void parseBSECLegacy(SensorDataPacket& data, DataBSEC& vector);
5581
static void parseData(SensorDataPacket& data, float& value, float scaleFactor, SensorPayload format);
5682
static void parseActivity(SensorDataPacket& data, uint16_t& value);
5783
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#ifndef SENSOR_BSEC_H_
2+
#define SENSOR_BSEC_H_
3+
4+
#include "SensorClass.h"
5+
6+
7+
class SensorBSEC : public SensorClass {
8+
public:
9+
SensorBSEC() {}
10+
SensorBSEC(uint8_t id) : SensorClass(id), _data() {}
11+
12+
/*
13+
BSEC sensor frames are:
14+
- 18 bytes for BSEC new format (SID=115 SENSOR_ID_BSEC_LEGACY)
15+
- 29 bytes for legacy format (SID=171 SENSOR_ID_BSEC)
16+
If the default size of SENSOR_DATA_FIXED_LENGTH is used (10 bytes), some fields of BSEC might be always 0.
17+
Enlarge SENSOR_DATA_FIXED_LENGTH to see all the fields of the BSEC sensor.
18+
For the new format (SID=115), if the compensated values (comp_t, comp_h, comp_g) are not important,
19+
keep SENSOR_DATA_FIXED_LENGTH to the default value (10) to save bandwidth.
20+
*/
21+
uint16_t iaq() {return _data.iaq;}
22+
uint16_t iaq_s() {return _data.iaq_s;}
23+
float b_voc_eq() {return _data.b_voc_eq;}
24+
uint32_t co2_eq() {return _data.co2_eq;}
25+
uint8_t accuracy() {return _data.accuracy;}
26+
float comp_t() {return _data.comp_t;}
27+
float comp_h() {return _data.comp_h;}
28+
uint32_t comp_g() {return _data.comp_g;}
29+
30+
31+
void setData(SensorDataPacket &data)
32+
{
33+
if (_id == SENSOR_ID_BSEC ) {
34+
DataParser::parseBSEC(data, _data);
35+
} else if (_id == SENSOR_ID_BSEC_LEGACY) {
36+
DataParser::parseBSECLegacy(data, _data);
37+
}
38+
}
39+
40+
String toString()
41+
{
42+
return _data.toString();
43+
}
44+
45+
private:
46+
DataBSEC _data;
47+
};
48+
#endif

Arduino_BHY2/src/sensors/SensorID.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ enum SensorID {
5252
SENSOR_ID_GYRO_BIAS_WU = 92, /* Gyroscope offset wake up */
5353
SENSOR_ID_MAG_BIAS_WU = 93, /* Magnetometer offset wake up */
5454
SENSOR_ID_STD_WU = 94, /* Step detector wake up */
55+
SENSOR_ID_BSEC = 115, /* BSEC 1.x output */
5556
SENSOR_ID_TEMP = 128, /* Temperature */
5657
SENSOR_ID_BARO = 129, /* Barometer */
5758
SENSOR_ID_HUM = 130, /* Humidity */
@@ -74,6 +75,7 @@ enum SensorID {
7475
SENSOR_ID_PROX = 147, /* Proximity */
7576
SENSOR_ID_LIGHT_WU = 148, /* Light wake up */
7677
SENSOR_ID_PROX_WU = 149, /* Proximity wake up */
78+
SENSOR_ID_BSEC_LEGACY = 171, /* BSEC 1.x output (legacy, deprecated) */
7779
DEBUG_DATA_EVENT = 250, /* Binary or string debug data */
7880
TIMESTAMP_SMALL_DELTA = 251, /* Incremental time change from previous read */
7981
TIMESTAMP_SMALL_DELTA_WU = 245, /* Incremental time change from previous read wake up */

Arduino_BHY2/src/sensors/SensorTypes.h

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ struct __attribute__((packed)) SensorDataPacket {
2525
float result = 0;
2626
uint8_t length = sizeof(result);
2727
if (index + length > SENSOR_DATA_FIXED_LENGTH) {
28-
length = SENSOR_DATA_FIXED_LENGTH - index;
28+
//to safe guard against overflow
29+
length = SENSOR_DATA_FIXED_LENGTH > index ? SENSOR_DATA_FIXED_LENGTH - index : 0;
2930
}
30-
memcpy(&result, &data[index], sizeof(result));
31+
if (length > 0)
32+
memcpy(&result, &data[index], length);
3133
return result;
3234
}
3335

@@ -42,29 +44,32 @@ struct __attribute__((packed)) SensorDataPacket {
4244
uint16_t result = 0;
4345
uint8_t length = sizeof(result);
4446
if (index + length > SENSOR_DATA_FIXED_LENGTH) {
45-
length = SENSOR_DATA_FIXED_LENGTH - index;
47+
length = SENSOR_DATA_FIXED_LENGTH > index ? SENSOR_DATA_FIXED_LENGTH - index : 0;
4648
}
47-
memcpy(&result, &data[index], length);
49+
if (length > 0)
50+
memcpy(&result, &data[index], length);
4851
return result;
4952
}
5053

5154
uint32_t getUint24(uint8_t index) {
5255
uint32_t result = 0;
5356
uint8_t length = 3;
5457
if (index + length > SENSOR_DATA_FIXED_LENGTH) {
55-
length = SENSOR_DATA_FIXED_LENGTH - index;
58+
length = SENSOR_DATA_FIXED_LENGTH > index ? SENSOR_DATA_FIXED_LENGTH - index : 0;
5659
}
57-
memcpy(&result, &data[index], length);
60+
if (length > 0)
61+
memcpy(&result, &data[index], length);
5862
return result;
5963
}
6064

6165
uint32_t getUint32(uint8_t index) {
6266
uint32_t result = 0;
6367
uint8_t length = sizeof(result);
6468
if (index + length > SENSOR_DATA_FIXED_LENGTH) {
65-
length = SENSOR_DATA_FIXED_LENGTH - index;
69+
length = SENSOR_DATA_FIXED_LENGTH > index ? SENSOR_DATA_FIXED_LENGTH - index : 0;
6670
}
67-
memcpy(&result, &data[index], length);
71+
if (length > 0)
72+
memcpy(&result, &data[index], length);
6873
return result;
6974
}
7075

@@ -79,19 +84,21 @@ struct __attribute__((packed)) SensorDataPacket {
7984
int16_t result = 0;
8085
uint8_t length = sizeof(result);
8186
if (index + length > SENSOR_DATA_FIXED_LENGTH) {
82-
length = SENSOR_DATA_FIXED_LENGTH - index;
87+
length = SENSOR_DATA_FIXED_LENGTH > index ? SENSOR_DATA_FIXED_LENGTH - index : 0;
8388
}
84-
memcpy(&result, &data[index], length);
89+
if (length > 0)
90+
memcpy(&result, &data[index], length);
8591
return result;
8692
}
8793

8894
int32_t getInt32(uint8_t index) {
8995
int32_t result = 0;
9096
uint8_t length = sizeof(result);
9197
if (index + length > SENSOR_DATA_FIXED_LENGTH) {
92-
length = SENSOR_DATA_FIXED_LENGTH - index;
98+
length = SENSOR_DATA_FIXED_LENGTH > index ? SENSOR_DATA_FIXED_LENGTH - index : 0;
9399
}
94-
memcpy(&result, &data[index], length);
100+
if (length > 0)
101+
memcpy(&result, &data[index], length);
95102
return result;
96103
}
97104
};

tools/bhy-controller/src/sensor/parser.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ func parseData(data *SensorData) {
8989
typeScheme = typeMap[4].(map[string]interface{})
9090
} else if (sensorScheme == "BSECOutput") {
9191
typeScheme = typeMap[5].(map[string]interface{})
92+
} else if (sensorScheme == "BSECOutputV2") {
93+
typeScheme = typeMap[6].(map[string]interface{})
94+
} else if (sensorScheme == "BSECOutputV2Full") {
95+
typeScheme = typeMap[6].(map[string]interface{})
9296
}
9397

9498
fields := typeScheme["parse-scheme"].([]interface{})

tools/bhy-controller/src/webserver/parse-scheme.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,36 @@
6363
{"name": "Temperature (compensated)", "type": "float", "scale-factor": 1},
6464
{"name": "Humidity (compensated)", "type": "float", "scale-factor": 1}
6565
]
66+
},
67+
68+
{
69+
"id": 6,
70+
"type": "BSECOutputV2",
71+
"parse-scheme":
72+
[
73+
{"name": "IAQ(Mobile)", "type": "uint16", "scale-factor": 1},
74+
{"name": "IAQ(Stationary)", "type": "uint16", "scale-factor": 1},
75+
{"name": "bVOC-Equivalents(ppm)", "type": "uint16", "scale-factor": 0.01},
76+
{"name": "CO2-Equivalents(ppm)", "type": "uint24", "scale-factor": 1},
77+
{"name": "Accuracy", "type": "uint8", "scale-factor": 1}
78+
]
79+
},
80+
81+
{
82+
"id": 7,
83+
"type": "BSECOutputV2Full",
84+
"parse-scheme":
85+
[
86+
{"name": "IAQ(Mobile)", "type": "uint16", "scale-factor": 1},
87+
{"name": "IAQ(Stationary)", "type": "uint16", "scale-factor": 1},
88+
{"name": "b-VOC-Equivalents(ppm)", "type": "uint16", "scale-factor": 0.01},
89+
{"name": "CO2-Equivalents(ppm)", "type": "uint24", "scale-factor": 1},
90+
{"name": "Accuracy", "type": "uint8", "scale-factor": 1},
91+
{"name": "Compensated-Temperature(°C)", "type": "int16", "scale-factor": 0.003906},
92+
{"name": "Compensated-Humidity(%)", "type": "uint16", "scale-factor": 0.002},
93+
{"name": "Compensated-Gas Resistance(Ohms)", "type": "float", "scale-factor": 1}
94+
]
6695
}
96+
6797
]
6898
}

tools/bhy-controller/src/webserver/sensor-type-map.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,13 @@
347347
"dashboard": 0
348348
},
349349

350+
"115": {
351+
"name": "BSEC Output",
352+
"scheme": "BSECOutputV2",
353+
"value": 0,
354+
"dashboard": 1
355+
},
356+
350357
"128": {
351358
"name": "TEMPERATURE",
352359
"scheme": "singleRead",
@@ -576,7 +583,7 @@
576583
},
577584

578585
"171": {
579-
"name": "BSEC Output",
586+
"name": "BSEC Output (deprecated)",
580587
"scheme": "BSECOutput",
581588
"value": 0,
582589
"dashboard": 0

tools/bhy-controller/src/webserver/sensor.html

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,44 @@ <h3>Read sensor data</h3>
429429
sensorTypes[sensor].value = cnt + 1;
430430
}
431431

432+
if (cnt > 150) {
433+
Plotly.relayout(chartIdx,{
434+
xaxis: {
435+
range: [cnt-150,cnt]
436+
}
437+
});
438+
}
439+
} else if (sensorTypes[sensor].scheme == "BSECOutputV2") {
440+
//Parse BSEC values
441+
const BSECValues = parsedStringValue.split(" ");
442+
console.log("Split: ", BSECValues);
443+
444+
var val1 = BSECValues[1]; //iaq
445+
var val2 = BSECValues[5]; //iaq-s
446+
var val3 = BSECValues[9] * 1000; //b-voc-eq
447+
var val4 = BSECValues[13]; //co2-eq
448+
var val5 = BSECValues[17]; //status
449+
450+
if (sensorTypes[sensor].value == 0) { //Plot doesn't exist yet because no valid data have been received
451+
if (val4 != 0) { //First valid data received: draw it in the chart, valid CO2-eq output is always greater than 0
452+
Plotly.newPlot(chartIdx,
453+
[
454+
{y:[val1],name:'IAQ',type:'line'}, {y:[val2],name:'IAQ-S',type:'line'},
455+
{y:[val3],name:'bVOC-eq (ppb)',type:'line'}, {y:[val4],name:'CO2-eq (ppm)',type:'line'}
456+
]);
457+
//Update json to signal that the reception started
458+
cnt = sensorTypes[sensor].value + 1;
459+
sensorTypes[sensor].value = cnt;
460+
}
461+
} else { //Plot already exists
462+
Plotly.extendTraces(chartIdx,{y:[[val1]]}, [0]);
463+
Plotly.extendTraces(chartIdx,{y:[[val2]]}, [1]);
464+
Plotly.extendTraces(chartIdx,{y:[[val3]]}, [2]);
465+
Plotly.extendTraces(chartIdx,{y:[[val4]]}, [3]);
466+
cnt = sensorTypes[sensor].value;
467+
sensorTypes[sensor].value = cnt + 1;
468+
}
469+
432470
if (cnt > 150) {
433471
Plotly.relayout(chartIdx,{
434472
xaxis: {
@@ -467,6 +505,13 @@ <h3>Read sensor data</h3>
467505
parse_scheme = parseScheme["types"][4]["parse-scheme"];
468506
} else if (scheme == "BSECOutput") {
469507
parse_scheme = parseScheme["types"][5]["parse-scheme"];
508+
} else if (scheme == "BSECOutputV2") {
509+
var size = data.getUint8(1);
510+
if (size <= 10) {
511+
parse_scheme = parseScheme["types"][6]["parse-scheme"];
512+
} else {
513+
parse_scheme = parseScheme["types"][7]["parse-scheme"];
514+
}
470515
}
471516

472517
parse_scheme.forEach(element => {

0 commit comments

Comments
 (0)