Air Quality Monitor
Portable C application for collecting, storing, and analyzing air quality sensor readings
Loading...
Searching...
No Matches
pms5003_plugin.c
1#include "../include/sensor/sensor.h"
2#include <errno.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <time.h>
7#ifndef _WIN32
8#include <fcntl.h>
9#include <termios.h>
10#include <unistd.h>
11#ifndef CRTSCTS
12#define CRTSCTS 0
13#endif
14#endif
15
16#ifdef _WIN32
17#define SENSOR_PLUGIN_EXPORT __declspec(dllexport)
18#else
19#define SENSOR_PLUGIN_EXPORT
20#endif
21
22static void fill_timestamp(char *buf, size_t len) {
23 time_t now = time(NULL);
24 const struct tm *ptm = localtime(&now);
25 if (!ptm) {
26 snprintf(buf, len, "1970-01-01 00:00:00");
27 return;
28 }
29 if (strftime(buf, len, "%Y-%m-%d %H:%M:%S", ptm) == 0) {
30 snprintf(buf, len, "1970-01-01 00:00:00");
31 }
32}
33
34static int pms5003_init(void) {
35#ifndef _WIN32
36 printf("PMS5003 plugin initialized (real UART on Linux/macOS if available; fallback mock).\n");
37#else
38 printf("PMS5003 plugin initialized (mock on Windows).\n");
39#endif
40 return 0;
41}
42
43#ifndef _WIN32
44static int open_serial_9600(const char *device) {
45 int fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
46 if (fd < 0)
47 return -1;
48 struct termios tty;
49 memset(&tty, 0, sizeof(tty));
50 if (tcgetattr(fd, &tty) != 0) {
51 close(fd);
52 return -1;
53 }
54 cfsetospeed(&tty, B9600);
55 cfsetispeed(&tty, B9600);
56 tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
57 tty.c_iflag &= ~(IGNBRK | IXON | IXOFF | IXANY);
58 tty.c_lflag = 0;
59 tty.c_oflag = 0;
60 tty.c_cc[VMIN] = 1;
61 tty.c_cc[VTIME] = 2;
62 tty.c_cflag |= (CLOCAL | CREAD);
63 tty.c_cflag &= ~(PARENB | PARODD | CSTOPB | CRTSCTS);
64 if (tcsetattr(fd, TCSANOW, &tty) != 0) {
65 close(fd);
66 return -1;
67 }
68 tcflush(fd, TCIOFLUSH);
69 return fd;
70}
71
72// parse and validate a 32-byte PMS5003 frame (bytes already include the
73// 0x42 0x4D header). Pure function -- no I/O -- so it can be unit tested
74// without a real UART connection.
75// returns 0 on success (pm25/pm10 populated), -1 if the checksum fails.
76int pms5003_parse_frame(const unsigned char frame[32], int *pm25, int *pm10) {
77 if (!frame || !pm25 || !pm10)
78 return -1;
79
80 unsigned int sum = 0;
81 for (int k = 0; k < 30; k++)
82 sum += frame[k];
83 unsigned int chk = ((unsigned int)frame[30] << 8) | frame[31];
84 if ((sum & 0xFFFF) != chk)
85 return -1;
86
87 *pm25 = ((int)frame[12] << 8) | frame[13];
88 *pm10 = ((int)frame[14] << 8) | frame[15];
89 return 0;
90}
91
92static int read_frame(int fd, unsigned char *buf, size_t want) {
93 size_t got = 0;
94 while (got < want) {
95 ssize_t n = read(fd, buf + got, want - got);
96 if (n <= 0)
97 return -1;
98 got += (size_t)n;
99 }
100 return 0;
101}
102
103static int read_pms5003(float *pm25, float *pm10) {
104 const char *dev = getenv("PMS5003_DEVICE");
105 if (!dev || !dev[0])
106 dev = "/dev/ttyUSB0";
107 int fd = open_serial_9600(dev);
108 if (fd < 0)
109 return -1;
110
111 unsigned char b;
112 int found = 0;
113 for (int i = 0; i < 2048; i++) {
114 if (read(fd, &b, 1) != 1)
115 break;
116 if (!found && b == 0x42) {
117 found = 1;
118 continue;
119 }
120 if (found && b == 0x4d) {
121 unsigned char frame[32];
122 frame[0] = 0x42;
123 frame[1] = 0x4d;
124 if (read_frame(fd, frame + 2, 30) != 0) {
125 close(fd);
126 return -1;
127 }
128 int val_pm25 = 0, val_pm10 = 0;
129 if (pms5003_parse_frame(frame, &val_pm25, &val_pm10) != 0) {
130 close(fd);
131 return -1;
132 }
133 *pm25 = (float)val_pm25;
134 *pm10 = (float)val_pm10;
135 close(fd);
136 return 0;
137 }
138 found = 0;
139 }
140 close(fd);
141 return -1;
142}
143#endif
144
145static int pms5003_read_sample(AirQualityData *out) {
146 if (!out)
147 return -1;
148 memset(out, 0, sizeof(*out));
149 out->sensor_id = 5003;
150 snprintf(out->model, sizeof(out->model), "PMS5003");
151 fill_timestamp(out->timestamp, sizeof(out->timestamp));
152
153 // PMS5003 particulate readings (real UART if available; mock fallback).
154 out->pm25 = (float)(rand() % 5000) / 100.0f; // 0..49.99
155 out->pm10 = out->pm25 + (float)(rand() % 7000) / 100.0f; // PM10 >= PM2.5
156#ifndef _WIN32
157 float real_pm25 = 0.0f, real_pm10 = 0.0f;
158 if (read_pms5003(&real_pm25, &real_pm10) == 0) {
159 out->pm25 = real_pm25;
160 out->pm10 = real_pm10;
161 }
162#endif
163 out->co = 0.0f;
164 out->no2 = 0.0f;
165 out->o3 = 0.0f;
166 out->so2 = 0.0f;
167 return 0;
168}
169
170static int pms5003_shutdown(void) {
171 printf("PMS5003 plugin shutdown.\n");
172 return 0;
173}
174
175SENSOR_PLUGIN_EXPORT SensorPlugin sensor_plugin = {
176 .api_version = SENSOR_PLUGIN_API_VERSION,
177 .name = "PMS5003",
178 .plugin_version = "1.0.0",
179 .description = "Simulated PMS5003 particulate readings.",
180 .sensor_id = 5003,
181 .init = pms5003_init,
182 .read_sample = pms5003_read_sample,
183 .shutdown = pms5003_shutdown,
184};
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
Runtime contract every sensor plugin (.so/.dylib/.dll) must implement.
Definition sensor.h:35
int api_version
Must equal SENSOR_PLUGIN_API_VERSION at build time.
Definition sensor.h:37