Skip to content

Commit c1c6675

Browse files
committed
environmental: add multi-sensor support and extended data reporting
- Add device tree and driver configuration for BME280, CCS811, HM330X, and SEN0466 sensors on I2C2 bus. - Update environmental module to sample and report temperature, pressure, humidity, CO2, VOC, PM1.0, PM2.5, PM10, CO, and secondary temperature. - Extend environmental_msg struct and logging to include new sensor data fields. - Update Kconfig and prj.conf to enable new sensor drivers and logging. - Refactor environmental state object to hold multiple sensor device pointers. - Update cloud module to forward all new environmental data fields to nRF Cloud. Refs: #25 Signed-off-by: Natalia Pluta <pluta.natalia.m@gmail.com>
1 parent 0f94198 commit c1c6675

6 files changed

Lines changed: 267 additions & 40 deletions

File tree

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,55 @@
11
/*
2-
* Copyright (c) 2025 Nordic Semiconductor ASA
3-
*
4-
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
2+
* Copyright (c) 2025 Natalia Pluta
3+
* SPDX-License-Identifier: Apache-2.0
54
*/
5+
6+
&pinctrl {
7+
i2c2_default_alt: i2c2_default_alt {
8+
group1 {
9+
psels = <NRF_PSEL(TWIM_SDA, 0, 30)>,
10+
<NRF_PSEL(TWIM_SCL, 0, 31)>;
11+
};
12+
};
13+
14+
i2c2_sleep_alt: i2c2_sleep_alt {
15+
group1 {
16+
psels = <NRF_PSEL(TWIM_SDA, 0, 30)>,
17+
<NRF_PSEL(TWIM_SCL, 0, 31)>;
18+
low-power-enable;
19+
};
20+
};
21+
};
22+
23+
&i2c2 {
24+
status = "okay";
25+
pinctrl-0 = <&i2c2_default_alt>;
26+
pinctrl-1 = <&i2c2_sleep_alt>;
27+
pinctrl-names = "default", "sleep";
28+
29+
bme280: bme280@77 {
30+
compatible = "bosch,bme280";
31+
reg = <0x77>;
32+
status = "okay";
33+
};
34+
35+
ccs811: ccs811@5b {
36+
compatible = "ams,ccs811";
37+
reg = <0x5b>;
38+
status = "okay";
39+
reset-gpios = <&gpio0 23 GPIO_ACTIVE_LOW>;
40+
irq-gpios = <&gpio0 24 GPIO_ACTIVE_LOW>;
41+
wake-gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
42+
};
43+
44+
hm3301: hm3301@40 {
45+
compatible = "seeed,hm330x";
46+
reg = <0x40>;
47+
status = "okay";
48+
};
49+
50+
sen0466: sen0466@74 {
51+
compatible = "dfrobot,sen0466";
52+
reg = <0x74>;
53+
status = "okay";
54+
};
55+
};

app_2/prj.conf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=1280
1212
CONFIG_DK_LIBRARY=y
1313
CONFIG_ZVFS_OPEN_MAX=10
1414

15+
# Enable I2C and sensor subsystem for multiple sensors
16+
CONFIG_SENSOR=y
17+
CONFIG_I2C=y
18+
CONFIG_BME280=y
19+
CONFIG_CCS811=y
20+
# CONFIG_CCS811_ENV_COMPENSATION=y
21+
CONFIG_HM330X=y
22+
CONFIG_SEN0466=y
23+
1524
# TFM logging over uart0
1625
CONFIG_TFM_LOG_LEVEL_SILENCE=n
1726
CONFIG_TFM_SECURE_UART0=y
@@ -188,6 +197,7 @@ CONFIG_APP_LOCATION_LOG_LEVEL_DBG=y
188197
CONFIG_APP_CLOUD_LOG_LEVEL_DBG=y
189198
CONFIG_APP_NETWORK_LOG_LEVEL_DBG=y
190199
CONFIG_APP_LOG_LEVEL_DBG=y
200+
CONFIG_APP_ENVIRONMENTAL_LOG_LEVEL_DBG=y
191201

192202
# Task Watchdog
193203
CONFIG_TASK_WDT=y

app_2/src/modules/cloud/cloud.c

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ LOG_MODULE_REGISTER(cloud, CONFIG_APP_CLOUD_LOG_LEVEL);
4141

4242
#define CUSTOM_JSON_APPID_VAL_CONEVAL "CONEVAL"
4343
#define CUSTOM_JSON_APPID_VAL_BATTERY "BATTERY"
44+
#define CUSTOM_JSON_APPID_VAL_CO2 "CO2"
45+
#define CUSTOM_JSON_APPID_VAL_VOC "VOC"
46+
#define CUSTOM_JSON_APPID_VAL_PM1_0 "PM1_0"
47+
#define CUSTOM_JSON_APPID_VAL_PM2_5 "PM2_5"
48+
#define CUSTOM_JSON_APPID_VAL_PM10 "PM10"
49+
#define CUSTOM_JSON_APPID_VAL_CO "CO"
4450

4551
#define AGNSS_MAX_DATA_SIZE 3800
4652

@@ -86,7 +92,7 @@ enum priv_cloud_msg {
8692

8793
/* Create private cloud channel for internal messaging that is not intended for external use.
8894
* The channel is needed to communicate from asynchronous callbacks to the state machine and
89-
* ensure state transitions only happen from the cloud module thread where the state machine
95+
* ensure state transitions only happen from the cloud module thread where the state machine
9096
* is running.
9197
*/
9298
ZBUS_CHAN_DEFINE(PRIV_CLOUD_CHAN, enum priv_cloud_msg, NULL, NULL, ZBUS_OBSERVERS(cloud_subscriber),
@@ -789,6 +795,54 @@ static void state_connected_ready_run(void *obj)
789795
return;
790796
}
791797

798+
err = nrf_cloud_coap_sensor_send(CUSTOM_JSON_APPID_VAL_CO2, msg.co2,
799+
msg.timestamp, confirmable);
800+
if (err) {
801+
LOG_ERR("nrf_cloud_coap_sensor_send (CO2), error: %d", err);
802+
send_request_failed();
803+
return;
804+
}
805+
806+
err = nrf_cloud_coap_sensor_send(CUSTOM_JSON_APPID_VAL_VOC, msg.voc,
807+
msg.timestamp, confirmable);
808+
if (err) {
809+
LOG_ERR("nrf_cloud_coap_sensor_send (VOC), error: %d", err);
810+
send_request_failed();
811+
return;
812+
}
813+
814+
err = nrf_cloud_coap_sensor_send(CUSTOM_JSON_APPID_VAL_PM1_0, msg.pm1_0,
815+
msg.timestamp, confirmable);
816+
if (err) {
817+
LOG_ERR("nrf_cloud_coap_sensor_send (PM1_0), error: %d", err);
818+
send_request_failed();
819+
return;
820+
}
821+
822+
err = nrf_cloud_coap_sensor_send(CUSTOM_JSON_APPID_VAL_PM2_5, msg.pm2_5,
823+
msg.timestamp, confirmable);
824+
if (err) {
825+
LOG_ERR("nrf_cloud_coap_sensor_send (PM2_5), error: %d", err);
826+
send_request_failed();
827+
return;
828+
}
829+
830+
err = nrf_cloud_coap_sensor_send(CUSTOM_JSON_APPID_VAL_PM10, msg.pm10,
831+
msg.timestamp, confirmable);
832+
if (err) {
833+
LOG_ERR("nrf_cloud_coap_sensor_send (PM10), error: %d", err);
834+
send_request_failed();
835+
return;
836+
}
837+
838+
err = nrf_cloud_coap_sensor_send(CUSTOM_JSON_APPID_VAL_CO, msg.co,
839+
msg.timestamp, confirmable);
840+
if (err) {
841+
LOG_ERR("nrf_cloud_coap_sensor_send (CO), error: %d", err);
842+
send_request_failed();
843+
return;
844+
}
845+
792846
return;
793847
}
794848

app_2/src/modules/environmental/Kconfig.environmental

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
menuconfig APP_ENVIRONMENTAL
88
bool "Environmental module"
9-
depends on BME680
9+
depends on BME280
1010
default y
1111

1212
if APP_ENVIRONMENTAL

app_2/src/modules/environmental/environmental.c

Lines changed: 127 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <zephyr/logging/log.h>
99
#include <zephyr/zbus/zbus.h>
1010
#include <zephyr/drivers/sensor.h>
11+
#include <zephyr/drivers/sensor/sen0466.h>
1112
#include <zephyr/task_wdt/task_wdt.h>
1213
#include <zephyr/smf.h>
1314

@@ -59,13 +60,23 @@ struct environmental_state_object {
5960
/* Buffer for last zbus message */
6061
uint8_t msg_buf[MAX_MSG_SIZE];
6162

62-
/* Pointer to the BME680 sensor device */
63-
const struct device *const bme680;
63+
/* Pointer to the sensors devices */
64+
const struct device *const bme280;
65+
const struct device *const ccs811;
66+
const struct device *const hm330x;
67+
const struct device *const sen0466;
6468

6569
/* Sensor values */
6670
double temperature;
6771
double pressure;
6872
double humidity;
73+
double co2;
74+
double voc;
75+
double pm1_0;
76+
double pm2_5;
77+
double pm10;
78+
double co;
79+
double temperature_sen0466;
6980
};
7081

7182
/* Forward declarations of state handlers */
@@ -76,46 +87,118 @@ static const struct smf_state states[] = {
7687
[STATE_RUNNING] = SMF_CREATE_STATE(NULL, state_running_run, NULL, NULL, NULL),
7788
};
7889

79-
static void sample_sensors(const struct device *const bme680)
90+
static void sample_sensors(const struct environmental_state_object *state)
8091
{
8192
int err;
82-
struct sensor_value temp = {0};
83-
struct sensor_value press = {0};
84-
struct sensor_value humidity = {0};
85-
86-
err = sensor_sample_fetch(bme680);
87-
if (err) {
88-
LOG_ERR("sensor_sample_fetch, error: %d", err);
89-
SEND_FATAL_ERROR();
90-
return;
93+
struct sensor_value temp = {0}, press = {0}, humidity = {0};
94+
struct sensor_value co2 = {0}, voc = {0};
95+
struct sensor_value pm1_0 = {0}, pm2_5 = {0}, pm10 = {0};
96+
struct sensor_value co = {0}, temp_sen0466 = {0};
97+
98+
// BME280
99+
if (state->bme280) {
100+
err = sensor_sample_fetch(state->bme280);
101+
if (err) {
102+
LOG_ERR("BME280: sensor_sample_fetch, error: %d", err);
103+
SEND_FATAL_ERROR();
104+
return;
105+
}
106+
err = sensor_channel_get(state->bme280, SENSOR_CHAN_AMBIENT_TEMP, &temp);
107+
if (err) {
108+
LOG_WRN("BME280: temp read failed: %d", err);
109+
}
110+
err = sensor_channel_get(state->bme280, SENSOR_CHAN_PRESS, &press);
111+
if (err) {
112+
LOG_WRN("BME280: press read failed: %d", err);
113+
}
114+
err = sensor_channel_get(state->bme280, SENSOR_CHAN_HUMIDITY, &humidity);
115+
if (err) {
116+
LOG_WRN("BME280: humidity read failed: %d", err);
117+
}
91118
}
92119

93-
err = sensor_channel_get(bme680, SENSOR_CHAN_AMBIENT_TEMP, &temp);
94-
if (err) {
95-
LOG_ERR("sensor_channel_get, error: %d", err);
96-
SEND_FATAL_ERROR();
97-
return;
120+
// CCS811
121+
if (state->ccs811) {
122+
err = sensor_sample_fetch(state->ccs811);
123+
if (err) {
124+
LOG_WRN("CCS811: sensor_sample_fetch, error: %d", err);
125+
} else {
126+
err = sensor_channel_get(state->ccs811, SENSOR_CHAN_CO2, &co2);
127+
if (err) {
128+
LOG_WRN("CCS811: CO2 read failed: %d", err);
129+
}
130+
err = sensor_channel_get(state->ccs811, SENSOR_CHAN_VOC, &voc);
131+
if (err) {
132+
LOG_WRN("CCS811: VOC read failed: %d", err);
133+
}
134+
}
98135
}
99136

100-
err = sensor_channel_get(bme680, SENSOR_CHAN_PRESS, &press);
101-
if (err) {
102-
LOG_ERR("sensor_channel_get, error: %d", err);
103-
SEND_FATAL_ERROR();
104-
return;
137+
// HM330X
138+
if (state->hm330x) {
139+
err = sensor_sample_fetch(state->hm330x);
140+
if (err) {
141+
LOG_WRN("HM330X: sensor_sample_fetch, error: %d", err);
142+
} else {
143+
err = sensor_channel_get(state->hm330x, SENSOR_CHAN_PM_1_0, &pm1_0);
144+
if (err) {
145+
LOG_WRN("HM330X: PM1.0 read failed: %d", err);
146+
}
147+
err = sensor_channel_get(state->hm330x, SENSOR_CHAN_PM_2_5, &pm2_5);
148+
if (err) {
149+
LOG_WRN("HM330X: PM2.5 read failed: %d", err);
150+
}
151+
err = sensor_channel_get(state->hm330x, SENSOR_CHAN_PM_10, &pm10);
152+
if (err) {
153+
LOG_WRN("HM330X: PM10 read failed: %d", err);
154+
}
155+
}
105156
}
106157

107-
err = sensor_channel_get(bme680, SENSOR_CHAN_HUMIDITY, &humidity);
108-
if (err) {
109-
LOG_ERR("sensor_channel_get, error: %d", err);
110-
SEND_FATAL_ERROR();
111-
return;
158+
// SEN0466
159+
if (state->sen0466) {
160+
err = sensor_sample_fetch(state->sen0466);
161+
if (err) {
162+
LOG_WRN("SEN0466: sensor_sample_fetch, error: %d", err);
163+
} else {
164+
err = sensor_channel_get(state->sen0466, SENSOR_CHAN_SEN0466_CO, &co);
165+
if (err) {
166+
LOG_WRN("SEN0466: CO read failed: %d", err);
167+
}
168+
err = sensor_channel_get(state->sen0466, SENSOR_CHAN_SEN0466_TEMP,
169+
&temp_sen0466);
170+
if (err) {
171+
LOG_WRN("SEN0466: temp read failed: %d", err);
172+
}
173+
}
112174
}
113175

176+
// Transform and store in state object fields
177+
((struct environmental_state_object *)state)->temperature = sensor_value_to_double(&temp);
178+
((struct environmental_state_object *)state)->pressure = sensor_value_to_double(&press);
179+
((struct environmental_state_object *)state)->humidity = sensor_value_to_double(&humidity);
180+
((struct environmental_state_object *)state)->co2 = sensor_value_to_double(&co2);
181+
((struct environmental_state_object *)state)->voc = sensor_value_to_double(&voc);
182+
((struct environmental_state_object *)state)->pm1_0 = sensor_value_to_double(&pm1_0);
183+
((struct environmental_state_object *)state)->pm2_5 = sensor_value_to_double(&pm2_5);
184+
((struct environmental_state_object *)state)->pm10 = sensor_value_to_double(&pm10);
185+
((struct environmental_state_object *)state)->co = sensor_value_to_double(&co);
186+
((struct environmental_state_object *)state)->temperature_sen0466 =
187+
sensor_value_to_double(&temp_sen0466);
188+
114189
struct environmental_msg msg = {
115190
.type = ENVIRONMENTAL_SENSOR_SAMPLE_RESPONSE,
116-
.temperature = sensor_value_to_double(&temp),
117-
.pressure = sensor_value_to_double(&press),
118-
.humidity = sensor_value_to_double(&humidity),
191+
.temperature = ((struct environmental_state_object *)state)->temperature,
192+
.pressure = ((struct environmental_state_object *)state)->pressure,
193+
.humidity = ((struct environmental_state_object *)state)->humidity,
194+
.co2 = ((struct environmental_state_object *)state)->co2,
195+
.voc = ((struct environmental_state_object *)state)->voc,
196+
.pm1_0 = ((struct environmental_state_object *)state)->pm1_0,
197+
.pm2_5 = ((struct environmental_state_object *)state)->pm2_5,
198+
.pm10 = ((struct environmental_state_object *)state)->pm10,
199+
.co = ((struct environmental_state_object *)state)->co,
200+
.temperature_sen0466 =
201+
((struct environmental_state_object *)state)->temperature_sen0466,
119202
};
120203

121204
#if defined(CONFIG_APP_ENVIRONMENTAL_TIMESTAMP)
@@ -125,9 +208,15 @@ static void sample_sensors(const struct device *const bme680)
125208
}
126209
#endif /* CONFIG_APP_ENVIRONMENTAL_TIMESTAMP */
127210

128-
/* Log the environmental values and limit to 2 decimals */
129-
LOG_DBG("Temperature: %.2f C, Pressure: %.2f Pa, Humidity: %.2f %%", msg.temperature,
130-
msg.pressure, msg.humidity);
211+
LOG_DBG("Sampled: T:%.2fC P:%.2fPa H:%.2f%% CO2:%.2f VOC:%.2f PM1.0:%.2f PM2.5:%.2f "
212+
"PM10:%.2f CO:%.2f T2:%.2fC",
213+
msg.temperature, msg.pressure, msg.humidity, msg.co2, msg.voc, msg.pm1_0, msg.pm2_5,
214+
msg.pm10, msg.co, msg.temperature_sen0466);
215+
216+
LOG_INF("BME280: T:%.2fC P:%.2fkPa H:%.2f%%", msg.temperature, msg.pressure, msg.humidity);
217+
LOG_INF("CCS811: CO2:%.2f VOC:%.2f", msg.co2, msg.voc);
218+
LOG_INF("HM330X: PM1.0:%.2f PM2.5:%.2f PM10:%.2f", msg.pm1_0, msg.pm2_5, msg.pm10);
219+
LOG_INF("SEN0466: CO:%.2f T2:%.2fC", msg.co, msg.temperature_sen0466);
131220

132221
err = zbus_chan_pub(&ENVIRONMENTAL_CHAN, &msg, K_NO_WAIT);
133222
if (err) {
@@ -156,7 +245,7 @@ static void state_running_run(void *obj)
156245

157246
if (msg.type == ENVIRONMENTAL_SENSOR_SAMPLE_REQUEST) {
158247
LOG_DBG("Environmental values sample request received, getting data");
159-
sample_sensors(state_object->bme680);
248+
sample_sensors(state_object);
160249
}
161250
}
162251
}
@@ -171,7 +260,10 @@ static void env_module_thread(void)
171260
(CONFIG_APP_ENVIRONMENTAL_MSG_PROCESSING_TIMEOUT_SECONDS * MSEC_PER_SEC);
172261
const k_timeout_t zbus_wait_ms = K_MSEC(wdt_timeout_ms - execution_time_ms);
173262
struct environmental_state_object environmental_state = {
174-
.bme680 = DEVICE_DT_GET(DT_NODELABEL(bme680)),
263+
.bme280 = DEVICE_DT_GET(DT_NODELABEL(bme280)),
264+
.ccs811 = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(ccs811)),
265+
.hm330x = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(hm330x)),
266+
.sen0466 = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(sen0466)),
175267
};
176268

177269
LOG_DBG("Environmental module task started");

0 commit comments

Comments
 (0)