Air Quality Monitor
Portable C application for collecting, storing, and analyzing air quality sensor readings
Loading...
Searching...
No Matches
interval_collection.c
1#include "core/aqm_db.h"
2#include "core/aqm_platform.h"
3#include "core/globals.h"
4#include "data/insert_data.h"
5#include "sensor/sensor_loader.h"
6#include "sensor/sensor_utils.h"
7#include <sqlite3.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <time.h>
12
13static void mock_generate_air_quality(AirQualityData *data);
14static void fill_timestamp(char *buf, size_t len);
15
16void interval_collection(void) {
17 // Check if multi-sensor mode is enabled
18 int use_multi_sensor = 0;
19 if (active_sensor_count > 0) {
20 use_multi_sensor = 1;
21 }
22
23 SensorModule modules[MAX_SENSORS];
24 int enabled_module_count = 0;
25 int use_plugin = 0;
26 SensorModule single_module;
27
28 if (use_multi_sensor) {
29 printf("Multi-sensor mode: %d sensor(s) configured.\n", active_sensor_count);
30
31 // Load all enabled sensors
32 for (int i = 0; i < active_sensor_count; i++) {
33 if (!sensor_configs[i].enabled) {
34 continue;
35 }
36
37 const char *mode = sensor_configs[i].mode;
38 const char *plugin_path = sensor_configs[i].plugin_path[0] ? sensor_configs[i].plugin_path : resolve_default_plugin_path(mode);
39
40 if (!plugin_path) {
41 printf("Warning: Could not resolve plugin path for sensor %s. Skipping.\n", mode);
42 continue;
43 }
44
45 if (sensor_module_load(plugin_path, &modules[enabled_module_count]) == 0) {
46 if (modules[enabled_module_count].plugin->init() == 0) {
47 printf("Sensor %s (%s) initialized successfully.\n",
48 modules[enabled_module_count].plugin->name, mode);
49 enabled_module_count++;
50 } else {
51 fprintf(stderr, "Plugin init failed for %s; ignoring.\n", plugin_path);
52 sensor_module_unload(&modules[enabled_module_count]);
53 }
54 } else {
55 fprintf(stderr, "Could not load plugin from %s. Skipping.\n", plugin_path);
56 }
57 }
58
59 if (enabled_module_count == 0) {
60 printf("No sensors could be initialized. Falling back to single sensor mode.\n");
61 use_multi_sensor = 0;
62 }
63 }
64
65 if (!use_multi_sensor) {
66 // Use single sensor mode (original behavior)
67 const char *mode = sensor_mode[0] ? sensor_mode : "mock";
68 const char *plugin_path_env = sensor_plugin_path[0] ? sensor_plugin_path : NULL;
69
70 const char *plugin_path = plugin_path_env && plugin_path_env[0] ? plugin_path_env : resolve_default_plugin_path(mode);
71
72 if (sensor_plugins_enabled && plugin_path) {
73 if (sensor_module_load(plugin_path, &single_module) == 0) {
74 if (single_module.plugin->init() == 0) {
75 use_plugin = 1;
76 } else {
77 fprintf(stderr, "Plugin init failed; ignoring plugin: %s\n", plugin_path);
78 sensor_module_unload(&single_module);
79 }
80 } else {
81 fprintf(stderr, "Could not load plugin from %s. Continuing with builtin sensors.\n", plugin_path);
82 }
83 }
84
85 printf("Samples to collect (>=1). Interval between samples: %d s.\n", collection_interval);
86 if (use_plugin)
87 printf("Sensor mode: plugin (%s)\n", single_module.plugin->name);
88 else
89 printf("Sensor mode: mock\n");
90 } else {
91 printf("Samples to collect (>=1). Interval between samples: %d s.\n", collection_interval);
92 printf("Collecting from %d sensor(s).\n", enabled_module_count);
93 }
94
95 printf("How many samples? ");
96 char input[64];
97 int count = 0;
98 if (!fgets(input, sizeof(input), stdin) || !aqm_parse_int(input, &count) || count < 1) {
99 printf("Invalid count; aborting.\n");
100 if (use_multi_sensor) {
101 for (int i = 0; i < enabled_module_count; i++) {
102 modules[i].plugin->shutdown();
103 sensor_module_unload(&modules[i]);
104 }
105 } else if (use_plugin) {
106 single_module.plugin->shutdown();
107 sensor_module_unload(&single_module);
108 }
109 return;
110 }
111
112 if (collection_interval < 1) {
113 printf("collection_interval must be >= 1 (check configuration).\n");
114 if (use_multi_sensor) {
115 for (int i = 0; i < enabled_module_count; i++) {
116 modules[i].plugin->shutdown();
117 sensor_module_unload(&modules[i]);
118 }
119 } else if (use_plugin) {
120 single_module.plugin->shutdown();
121 sensor_module_unload(&single_module);
122 }
123 return;
124 }
125
126 sqlite3 *db = NULL;
127 if (aqm_db_open(&db) != 0) {
128 fprintf(stderr, "Unable to open database for data collection.\n");
129 if (use_multi_sensor) {
130 for (int i = 0; i < enabled_module_count; i++) {
131 modules[i].plugin->shutdown();
132 sensor_module_unload(&modules[i]);
133 }
134 } else if (use_plugin) {
135 single_module.plugin->shutdown();
136 sensor_module_unload(&single_module);
137 }
138 return;
139 }
140
141 if (sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK) {
142 fprintf(stderr, "Failed to start database transaction: %s\n", sqlite3_errmsg(db));
143 }
144
145 for (int i = 0; i < count; i++) {
146 if (use_multi_sensor) {
147 // Collect from all enabled sensors
148 for (int j = 0; j < enabled_module_count; j++) {
149 AirQualityData data;
150 if (modules[j].plugin->read_sample(&data) != 0) {
151 fprintf(stderr, "Plugin read failed (%s); using mock sample.\n", modules[j].plugin->name);
152 mock_generate_air_quality(&data);
153 } else if (!data.model[0]) {
154 snprintf(data.model, sizeof(data.model), "%s", modules[j].plugin->name);
155 }
156 insert_data_sqlite(db, &data);
157 }
158 } else {
159 // Single sensor mode
160 AirQualityData data;
161 if (use_plugin) {
162 if (single_module.plugin->read_sample(&data) != 0) {
163 fprintf(stderr, "Plugin read failed (%s); using mock sample.\n", single_module.plugin->name);
164 mock_generate_air_quality(&data);
165 } else if (!data.model[0]) {
166 snprintf(data.model, sizeof(data.model), "%s", single_module.plugin->name);
167 }
168 } else {
169 mock_generate_air_quality(&data);
170 }
171 insert_data_sqlite(db, &data);
172 }
173
174 if (i + 1 < count)
175 aqm_sleep_seconds((unsigned)collection_interval);
176 }
177
178 if (sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK) {
179 fprintf(stderr, "Failed to commit database transaction: %s\n", sqlite3_errmsg(db));
180 sqlite3_exec(db, "ROLLBACK;", NULL, NULL, NULL);
181 }
182
183 aqm_db_close(db);
184
185 if (use_multi_sensor) {
186 printf("Data collection finished (%d sample(s) from %d sensor(s)).\n", count, enabled_module_count);
187 for (int i = 0; i < enabled_module_count; i++) {
188 modules[i].plugin->shutdown();
189 sensor_module_unload(&modules[i]);
190 }
191 } else {
192 printf("Data collection finished (%d sample(s)).\n", count);
193 if (use_plugin) {
194 single_module.plugin->shutdown();
195 sensor_module_unload(&single_module);
196 }
197 }
198}
199
200static void mock_generate_air_quality(AirQualityData *data) {
201 data->sensor_id = (rand() % 10) + 1;
202 snprintf(data->model, sizeof(data->model), "mock");
203 fill_timestamp(data->timestamp, sizeof(data->timestamp));
204
205 data->pm25 = (float)((rand() % 1000) / 10.0);
206 data->pm10 = (float)((rand() % 1000) / 10.0);
207 data->co = (float)((rand() % 1000) / 10.0);
208 data->no2 = (float)((rand() % 1000) / 1000.0);
209 data->o3 = (float)((rand() % 1000) / 1000.0);
210 data->so2 = (float)((rand() % 1000) / 1000.0);
211}
212
213static void fill_timestamp(char *buf, size_t len) {
214 time_t t = time(NULL);
215 const struct tm *ptm = localtime(&t);
216 if (!ptm) {
217 snprintf(buf, len, "1970-01-01 00:00:00");
218 return;
219 }
220 if (strftime(buf, len, "%Y-%m-%d %H:%M:%S", ptm) == 0) {
221 snprintf(buf, len, "1970-01-01 00:00:00");
222 }
223}
224
One air quality reading: pollutant values plus sensor/timestamp metadata.
Definition globals.h:27
char timestamp[32]
Reading timestamp, formatted "%Y-%m-%d %H:%M:%S".
Definition globals.h:31
char model[64]
Sensor model name, e.g.
Definition globals.h:33
int sensor_id
Numeric sensor identifier, matches SensorPlugin::sensor_id.
Definition globals.h:29
char mode[32]
Sensor mode identifier, e.g.
Definition globals.h:50
char plugin_path[256]
Path to the plugin library; empty string means use the default resolved by resolve_default_plugin_pat...
Definition globals.h:53
A dynamically loaded sensor plugin and its OS-level handle.
int(* read_sample)(AirQualityData *out)
Reads one sample into *out.
Definition sensor.h:53
int(* shutdown)(void)
Called once before unloading, for cleanup.
Definition sensor.h:55
const char * name
Human-readable sensor name, e.g.
Definition sensor.h:39
int(* init)(void)
Called once after loading.
Definition sensor.h:47