#include <linux/i2c-dev.h>
/* ---- Configuration ---- */
#define GPIO_CHIP "/dev/gpiochip0"
#define I2C_BUS "/dev/i2c-1"
#define LOG_FILE "doorbell_log.csv"
/* ---- BME280 Registers ---- */
#define REG_CTRL_HUM 0xF2
#define REG_CTRL_MEAS 0xF4
#define REG_PRESS_MSB 0xF7
#define REG_CALIB_T1 0x88
#define REG_CALIB_H1 0xA1
#define REG_CALIB_H2 0xE1
static volatile int running = 1;
static struct gpiod_chip *chip = NULL;
static struct gpiod_line *button = NULL;
static struct gpiod_line *led = NULL;
static FILE *logfile = NULL;
static struct bme280_calib {
int16_t dig_P2, dig_P3, dig_P4, dig_P5;
int16_t dig_P6, dig_P7, dig_P8, dig_P9;
int16_t dig_H2, dig_H4, dig_H5;
/* ---- Signal Handler ---- */
static void sigint_handler(int sig)
/* ---- I2C Helpers ---- */
static int i2c_write_byte(uint8_t reg, uint8_t value)
uint8_t buf[2] = { reg, value };
return (write(i2c_fd, buf, 2) == 2) ? 0 : -1;
static int i2c_read_bytes(uint8_t reg, uint8_t *buf, int len)
if (write(i2c_fd, ®, 1) != 1) return -1;
if (read(i2c_fd, buf, len) != len) return -1;
/* ---- BME280 Calibration ---- */
static int bme280_read_calibration(void)
uint8_t buf[26], hbuf[7];
if (i2c_read_bytes(REG_CALIB_T1, buf, 26) < 0) return -1;
calib.dig_T1 = (uint16_t)(buf[1] << 8 | buf[0]);
calib.dig_T2 = (int16_t)(buf[3] << 8 | buf[2]);
calib.dig_T3 = (int16_t)(buf[5] << 8 | buf[4]);
calib.dig_P1 = (uint16_t)(buf[7] << 8 | buf[6]);
calib.dig_P2 = (int16_t)(buf[9] << 8 | buf[8]);
calib.dig_P3 = (int16_t)(buf[11] << 8 | buf[10]);
calib.dig_P4 = (int16_t)(buf[13] << 8 | buf[12]);
calib.dig_P5 = (int16_t)(buf[15] << 8 | buf[14]);
calib.dig_P6 = (int16_t)(buf[17] << 8 | buf[16]);
calib.dig_P7 = (int16_t)(buf[19] << 8 | buf[18]);
calib.dig_P8 = (int16_t)(buf[21] << 8 | buf[20]);
calib.dig_P9 = (int16_t)(buf[23] << 8 | buf[22]);
if (i2c_read_bytes(REG_CALIB_H1, &calib.dig_H1, 1) < 0) return -1;
if (i2c_read_bytes(REG_CALIB_H2, hbuf, 7) < 0) return -1;
calib.dig_H2 = (int16_t)(hbuf[1] << 8 | hbuf[0]);
calib.dig_H4 = (int16_t)((hbuf[3] << 4) | (hbuf[4] & 0x0F));
calib.dig_H5 = (int16_t)((hbuf[5] << 4) | (hbuf[4] >> 4));
calib.dig_H6 = (int8_t)hbuf[6];
/* ---- BME280 Compensation (Bosch datasheet formulas) ---- */
static double bme280_compensate_temp(int32_t adc_T)
v1 = ((((adc_T >> 3) - ((int32_t)calib.dig_T1 << 1)))
* ((int32_t)calib.dig_T2)) >> 11;
v2 = (((((adc_T >> 4) - ((int32_t)calib.dig_T1))
* ((adc_T >> 4) - ((int32_t)calib.dig_T1))) >> 12)
* ((int32_t)calib.dig_T3)) >> 14;
return (t_fine * 5 + 128) / 256 / 100.0;
static double bme280_compensate_pres(int32_t adc_P)
v1 = ((int64_t)t_fine) - 128000;
v2 = v1 * v1 * (int64_t)calib.dig_P6;
v2 = v2 + ((v1 * (int64_t)calib.dig_P5) << 17);
v2 = v2 + (((int64_t)calib.dig_P4) << 35);
v1 = ((v1 * v1 * (int64_t)calib.dig_P3) >> 8)
+ ((v1 * (int64_t)calib.dig_P2) << 12);
v1 = (((((int64_t)1) << 47) + v1)) * ((int64_t)calib.dig_P1) >> 33;
p = (((p << 31) - v2) * 3125) / v1;
v1 = (((int64_t)calib.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
v2 = (((int64_t)calib.dig_P8) * p) >> 19;
p = ((p + v1 + v2) >> 8) + (((int64_t)calib.dig_P7) << 4);
return (double)p / 256.0 / 100.0;
static double bme280_compensate_hum(int32_t adc_H)
v = (((((adc_H << 14) - (((int32_t)calib.dig_H4) << 20)
- (((int32_t)calib.dig_H5) * v)) + 16384) >> 15)
* (((((((v * ((int32_t)calib.dig_H6)) >> 10)
* (((v * ((int32_t)calib.dig_H3)) >> 11) + 32768)) >> 10)
+ 2097152) * ((int32_t)calib.dig_H2) + 8192) >> 14));
v = v - (((((v >> 15) * (v >> 15)) >> 7) * ((int32_t)calib.dig_H1)) >> 4);
v = (v > 419430400) ? 419430400 : v;
return (double)(v >> 12) / 1024.0;
/* ---- BME280 Read All ---- */
static int bme280_read(double *temp, double *pres, double *hum)
int32_t adc_T, adc_P, adc_H;
if (i2c_read_bytes(REG_PRESS_MSB, data, 8) < 0)
adc_P = ((int32_t)data[0] << 12) | ((int32_t)data[1] << 4) | (data[2] >> 4);
adc_T = ((int32_t)data[3] << 12) | ((int32_t)data[4] << 4) | (data[5] >> 4);
adc_H = ((int32_t)data[6] << 8) | (int32_t)data[7];
*temp = bme280_compensate_temp(adc_T);
*pres = bme280_compensate_pres(adc_P);
*hum = bme280_compensate_hum(adc_H);
/* ---- BME280 Init ---- */
static int bme280_init(void)
i2c_fd = open(I2C_BUS, O_RDWR);
if (ioctl(i2c_fd, I2C_SLAVE, BME280_ADDR) < 0) {
perror("ioctl I2C_SLAVE");
if (i2c_read_bytes(REG_CHIP_ID, &chip_id, 1) < 0)
fprintf(stderr, "Unexpected chip ID: 0x%02X (expected 0x60)\n", chip_id);
if (bme280_read_calibration() < 0)
/* Humidity oversampling x1 */
i2c_write_byte(REG_CTRL_HUM, 0x01);
/* Temp x1, pressure x1, normal mode */
i2c_write_byte(REG_CTRL_MEAS, 0x27);
/* Standby 1000ms, filter off */
i2c_write_byte(REG_CONFIG, 0xA0);
printf("BME280 initialized (chip ID: 0x60)\n");
/* ---- GPIO Init ---- */
static int gpio_init(void)
chip = gpiod_chip_open(GPIO_CHIP);
perror("gpiod_chip_open");
/* Configure button with pull-up, falling edge events */
button = gpiod_chip_get_line(chip, BUTTON_LINE);
perror("get button line");
struct gpiod_line_request_config btn_cfg = {
.consumer = "doorbell-monitor",
.request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE,
.flags = GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP,
ret = gpiod_line_request(button, &btn_cfg, 0);
perror("request button");
/* Configure LED as output */
led = gpiod_chip_get_line(chip, LED_LINE);
ret = gpiod_line_request_output(led, "doorbell-monitor", 0);
printf("GPIO initialized: button=GPIO%d, LED=GPIO%d\n",
static void cleanup(void)
gpiod_line_set_value(led, 0);
if (button) gpiod_line_release(button);
if (chip) gpiod_chip_close(chip);
if (i2c_fd >= 0) close(i2c_fd);
if (logfile) fclose(logfile);
printf("\nCleanup complete.\n");
/* ---- Get Timestamp String ---- */
static void get_timestamp(char *buf, size_t len)
struct tm *tm_info = localtime(&now);
strftime(buf, len, "%Y-%m-%d %H:%M:%S", tm_info);
/* ---- Main Loop ---- */
struct gpiod_line_event event;
struct timespec timeout = { .tv_sec = 1, .tv_nsec = 0 };
signal(SIGINT, sigint_handler);
logfile = fopen(LOG_FILE, "a");
/* Write CSV header if file is empty */
fseek(logfile, 0, SEEK_END);
if (ftell(logfile) == 0) {
fprintf(logfile, "timestamp,press_count,led_state,"
"temp_c,pressure_hpa,humidity_pct\n");
printf("\n--- Doorbell Monitor Running ---\n");
printf("Press the button on GPIO%d. Ctrl+C to quit.\n\n", BUTTON_LINE);
printf("%-20s %-6s %-4s %-8s %-10s %-8s\n",
"Timestamp", "Press", "LED", "Temp(C)", "Press(hPa)", "Hum(%%)");
ret = gpiod_line_event_wait(button, &timeout);
if (running) perror("event_wait");
/* Timeout, loop again (allows Ctrl+C check) */
/* Read the event to clear it */
ret = gpiod_line_event_read(button, &event);
gpiod_line_set_value(led, led_state);
/* Simple debounce: ignore events for 200ms */
if (bme280_read(&temp, &pres, &hum) < 0) {
fprintf(stderr, "Warning: failed to read BME280\n");
get_timestamp(ts, sizeof(ts));
printf("%-20s %-6d %-4s %-8.2f %-10.2f %-8.2f\n",
led_state ? "ON" : "OFF",
fprintf(logfile, "%s,%d,%d,%.2f,%.2f,%.2f\n",
ts, press_count, led_state, temp, pres, hum);
Comments