diff --git a/README.md b/README.md
index 9737800..105b10c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Air Quality Monitor
**Air Quality Monitor** is a lightweight web interface that displays real-time data from two environmental sensors: **HTU21D** and **BMP180**. The system uses **NGINX** to serve an HTML page that reads live data from JSON files.
-
+All the system is runing on a beagle bone black.
## Project Description
This project was designed to visualize environmental parameters in a simple, fast, and efficient way using an embedded graphical interface. The data is obtained from the following sensors:
@@ -14,18 +14,20 @@ The data is stored in two JSON files, updated by the embedded system and display
---
## Project Structure
-air-quality-monitor/
-│
-├── BMP180.json # Pressure and temperature data from BMP180
-├── HTU21D.json # Temperature and humidity data from HTU21D
-├── index.html # Main web interface
-├── index.js # JavaScript logic to fetch and display JSON data
-├── style.css # Custom CSS styles
+UI directory
+- BMP180.json # Pressure and temperature data from BMP180
+- HTU21D.json # Temperature and humidity data from HTU21D
+- index.html # Main web interface
+- index.js # JavaScript logic to fetch and display JSON data
+- style.css # Custom CSS styles
-> The `.json` files are automatically updated by C programs that communicate with the sensors via I2C.
+> The `.json` files are automatically updated by C programs that communicate with the sensors via I2C witch the librarys integrated on the repository.
---
+##
+Both sensors that we are using are conected to the I2C port 2 of the beagle bone
+
## 🚀 Installation & Deployment with NGINX
### Requirements
@@ -63,8 +65,8 @@ sudo systemctl restart nginx
## User interface
This is a simple web page that displays real-time sensor data from two devices:
- • BMP180: Shows temperature and pressure.
- • HTU21D: Shows humidity and temperature.
+- BMP180: Shows temperature and pressure.
+- HTU21D: Shows humidity and temperature.
It’s styled with a separate CSS file (style.css) and fetches live data using JavaScript (index.js), which likely reads values from two JSON files (BMP180.json and HTU21D.json).
### HTML
@@ -111,8 +113,8 @@ It’s styled with a separate CSS file (style.css) and fetches live data using J
```
-• Declares this document as HTML5.
-• lang="en" sets the document language to English, which is useful for accessibility and SEO.
+- Declares this document as HTML5.
+- lang="en" sets the document language to English, which is useful for accessibility and SEO.
##### HEAD
```html
@@ -122,10 +124,10 @@ It’s styled with a separate CSS file (style.css) and fetches live data using J
```
-• charset="UTF-8" ensures proper text encoding.
-• viewport makes the layout responsive on mobile devices.
-• title is what appears in the browser tab.
-• link imports the external CSS file (style.css) for styling.
+- charset="UTF-8" ensures proper text encoding.
+- viewport makes the layout responsive on mobile devices.
+- title is what appears in the browser tab.
+- link imports the external CSS file (style.css) for styling.
##### BODY
```html
@@ -136,14 +138,14 @@ It’s styled with a separate CSS file (style.css) and fetches live data using J
```
-• The background div might be used for a visual effect like a background image or color gradient (defined in your CSS).
-• container holds the main content (dashboard).
-• index.js is loaded at the end to ensure the DOM is ready before scripts run.
+- The background div might be used for a visual effect like a background image or color gradient (defined in your CSS).
+- container holds the main content (dashboard).
+- index.js is loaded at the end to ensure the DOM is ready before scripts run.
##### Dashboard Title
```html
Sensor Dashboard
```
-• This is the main heading of your web page.
+- This is the main heading of your web page.
##### Sensor Card: BMP180
```hmtl
@@ -155,9 +157,9 @@ It’s styled with a separate CSS file (style.css) and fetches live data using J
```
-• This block represents the BMP180 sensor.
-• The icon (from flaticon.com) visually represents pressure/temperature.
-• The id attributes (temperature, pressure) are hooks used by JavaScript to insert real values from BMP180.json.
+- This block represents the BMP180 sensor.
+- The icon (from flaticon.com) visually represents pressure/temperature.
+- The id attributes (temperature, pressure) are hooks used by JavaScript to insert real values from BMP180.json.
##### Sensor Card: HTU21D
```html
@@ -169,22 +171,22 @@ It’s styled with a separate CSS file (style.css) and fetches live data using J
```
-• This block represents the HTU21D sensor.
-• The image is a humidity icon.
-• The id attributes (humidity, tempHTU) are also updated by JavaScript using HTU21D.json.
+- This block represents the HTU21D sensor.
+- The image is a humidity icon.
+- The id attributes (humidity, tempHTU) are also updated by JavaScript using HTU21D.json.
##### JavaScript Integration
```hmtl
```
-• This line loads your script, which is responsible for:
-• Fetching the JSON files.
-• Parsing their content.
-• Replacing the placeholder values (-- °C, -- hPa, etc.) with live sensor data.
+- This line loads your script, which is responsible for:
+- Fetching the JSON files.
+- Parsing their content.
+- Replacing the placeholder values (-- °C, -- hPa, etc.) with live sensor data.
### Index.js
The script fetches data from two local JSON files:
-• BMP180.json — contains temperature and pressure.
-• HTU21D.json — contains temperature and humidity.
+- BMP180.json — contains temperature and pressure.
+- HTU21D.json — contains temperature and humidity.
Then it updates the HTML every 3 seconds so the page always shows live sensor values.
#### Full code
@@ -445,8 +447,9 @@ int getHum(int fd, double *hum);
//RESET
int getReset(int fd);
#endif
-
```
+##### Explanation
+
- HTU21D_ADDR: The I2C address of the sensor (0x40).
- HTU21D_TEMP: Command to read temperature (0xE3).
- HTU21D_HUM: Command to read humidity (0xE5).
@@ -456,4 +459,449 @@ int getReset(int fd);
2. getHum() – for reading humidity,
3. getReset() – for resetting the sensor.
+#### HTU21D.c
+```c
+#include
+#include
+#include
+//Aditional librarys
+#include
+#include
+#include "htu21d.h"
+
+#define I2C_PATH "/dev/i2c-%d"
+#define I2C_PORT 2
+int main(){
+ char filePath[20];
+ snprintf(filePath, sizeof(filePath), I2C_PATH, I2C_PORT );
+ int fd = open(filePath, O_RDWR);
+
+ if(fd<0){
+ fprintf(stderr, "Error: Unable to access HTU21D sensor: %s",strerror(errno));
+ exit(-1);
+ }
+ //measurements
+ double temperature=0;
+ double humidity=0;
+ if((getTemp(fd, &temperature)<0)||(getHum(fd, &humidity)<0)){
+ fprintf(stderr,"Error -404: Measurments not read");
+ exit(-1);
+ }
+
+
+ //printf("HTU21D Module \n");
+ //printf("%5.2fC \n", temperature);
+ //printf("%5.2fC \n", humidity);
+ printf("{");
+ printf("\"temperature\": %5.2f,", temperature);
+ printf("\"humidity\": %5.2f", humidity);
+ printf("}");
+ return 0;
+
+}
+```
+##### Explanation
+###### Librarys
+```c
+#include
+#include
+#include
+#include
+#include
+#include "htu21d.h"
+```
+These headers provide:
+- unistd.h: basic system calls (like read, write, etc.)
+- sys/ioctl.h: lets you control devices (e.g., set I2C slave address)
+- linux/i2c-dev.h: allows interaction with the I2C bus
+- i2c/smbus.h: provides high-level SMBus/I2C functions like i2c_smbus_read_i2c_block_data
+- stdio.h: for printing error messages
+- "htu21d.h": includes your own header file (constants and function declarations)
+###### getTemp() Function
+```c
+int getTemp(int fd, double *temp) {
+ getReset(fd); // Resets the sensor before reading
+
+ char buffer[3]; // Buffer to hold 3 bytes of data from sensor
+ __s32 res = i2c_smbus_read_i2c_block_data(fd, HTU21D_TEMP, 3, buffer);
+```
+- Calls getReset(fd) to ensure the sensor is in a clean state before reading.
+- Uses i2c_smbus_read_i2c_block_data to read 3 bytes from the sensor using the temperature command 0xE3.
+- These 3 bytes contain raw temperature data + CRC (which you ignore here).
+```c
+ if (res < 0) {
+ perror("ERROR-1: Failed to read Temperature");
+ return -1;
+ }
+```
+- If the read fails (e.g., I2C communication issue), it prints an error and returns -1.
+```c
+ *temp = -46.85 + 175.72 * (buffer[0] * 256 + buffer[1]) / 65536.0;
+ return 0;
+}
+```
+- The first two bytes in buffer are combined to make a 16-bit raw temperature.
+- The formula from the HTU21D datasheet is applied to convert raw data into actual temperature in Celsius.
+- The result is stored in the variable pointed to by *temp.
+- Returns 0 to indicate success.
+
+###### getHum() Function
+This function is nearly identical to getTemp(), but it reads humidity instead.
+```c
+int getHum(int fd, double *hum) {
+ getReset(fd);
+ char buffer[3];
+ __s32 res = i2c_smbus_read_i2c_block_data(fd, HTU21D_HUM, 3, buffer);
+```
+- Uses the command HTU21D_HUM (value 0xE5) to read humidity data.
+```c
+ if (res < 0) {
+ perror("ERROR -3: Failed to read Humidity");
+ return -1;
+ }
+ *hum = -6 + 125 * (buffer[0] * 256 + buffer[1]) / 65536.0;
+ return 0;
+}
+```
+- Applies the formula from the datasheet to convert the raw bytes to % humidity.
+###### getReset() Function
+```c
+int getReset(int fd) {
+ if (0 > ioctl(fd, I2C_SLAVE, HTU21D_ADDR)) {
+ perror("ERROR -2: Failed in reset");
+ return -2;
+ }
+ i2c_smbus_write_byte(fd, HTU21D_RESET);
+ return 0;
+}
+```
+- ioctl() tells the I2C driver which slave device you want to talk to (address 0x40).
+- Then i2c\_smbus\_write\_byte() sends the reset command 0xFE to the HTU21D.
+- Resets the sensor, which is sometimes necessary to avoid bad reads or stuck states.
+
+### BMP180 library
+The library contains 3 files, bmp180.c, bmp180.h and main.c
+#### bmp180.c
+```c
+#include "bmp180.h"
+#include
+#include
+#include
+#include
+#include
+
+#define BMP180_ADDR 0x77
+
+// Lectura de 16 bits de un registro (dos bytes)
+static int read16(int fd, uint8_t reg, int16_t *value) {
+ uint8_t buf[2];
+ if (write(fd, ®, 1) != 1) return -1;
+ if (read(fd, buf, 2) != 2) return -1;
+ *value = (buf[0] << 8) | buf[1];
+ return 0;
+}
+
+// Escritura de 8 bits a un registro
+static int write8(int fd, uint8_t reg, uint8_t value) {
+ uint8_t buf[2] = {reg, value};
+ if (write(fd, buf, 2) != 2) return -1;
+ return 0;
+}
+
+// Leer calibración desde el sensor
+int bmp180_init(int fd, bmp180_calib_data_t *calib) {
+ if (read16(fd, 0xAA, &calib->AC1) < 0) return -1;
+ if (read16(fd, 0xAC, &calib->AC2) < 0) return -1;
+ if (read16(fd, 0xAE, &calib->AC3) < 0) return -1;
+ if (read16(fd, 0xB0, (int16_t*)&calib->AC4) < 0) return -1;
+ if (read16(fd, 0xB2, (int16_t*)&calib->AC5) < 0) return -1;
+ if (read16(fd, 0xB4, (int16_t*)&calib->AC6) < 0) return -1;
+ if (read16(fd, 0xB6, &calib->B1) < 0) return -1;
+ if (read16(fd, 0xB8, &calib->B2) < 0) return -1;
+ if (read16(fd, 0xBA, &calib->MB) < 0) return -1;
+ if (read16(fd, 0xBC, &calib->MC) < 0) return -1;
+ if (read16(fd, 0xBE, &calib->MD) < 0) return -1;
+ return 0;
+}
+
+// Leer temperatura sin procesar (raw temp)
+static int bmp180_read_raw_temperature(int fd, int32_t *raw_temp) {
+ if (write8(fd, 0xF4, 0x2E) < 0) return -1; // start temp measurement
+ usleep(4500); // esperar 4.5 ms
+ int16_t value;
+ if (read16(fd, 0xF6, &value) < 0) return -1;
+ *raw_temp = value;
+ return 0;
+}
+
+// Leer presión sin procesar (raw pressure)
+static int bmp180_read_raw_pressure(int fd, int32_t *raw_press, int oss) {
+ if (write8(fd, 0xF4, 0x34 + (oss << 6)) < 0) return -1; // start pressure measurement
+ usleep(25000); // esperar 25 ms para oss=0 (simple oversampling)
+ uint8_t buf[3];
+ uint8_t reg = 0xF6;
+ if (write(fd, ®, 1) != 1) return -1;
+ if (read(fd, buf, 3) != 3) return -1;
+ *raw_press = ((buf[0] << 16) | (buf[1] << 8) | buf[2]) >> (8 - oss);
+ return 0;
+}
+
+int bmp180_read_temperature(int fd, bmp180_calib_data_t *calib, double *temperature) {
+ int32_t UT;
+ if (bmp180_read_raw_temperature(fd, &UT) < 0) return -1;
+
+ int32_t X1 = ((UT - calib->AC6) * calib->AC5) >> 15;
+ int32_t X2 = (calib->MC << 11) / (X1 + calib->MD);
+ int32_t B5 = X1 + X2;
+ *temperature = ((B5 + 8) >> 4) / 10.0;
+ return 0;
+}
+
+int bmp180_read_pressure(int fd, bmp180_calib_data_t *calib, double *pressure) {
+ int oss = 0; // oversampling setting 0..3
+ int32_t UP;
+ if (bmp180_read_raw_pressure(fd, &UP, oss) < 0) return -1;
+
+ // Recalcular B5 para temperatura, necesario para presión
+ int32_t UT;
+ if (bmp180_read_raw_temperature(fd, &UT) < 0) return -1;
+
+ int32_t X1 = ((UT - calib->AC6) * calib->AC5) >> 15;
+ int32_t X2 = (calib->MC << 11) / (X1 + calib->MD);
+ int32_t B5 = X1 + X2;
+
+ int32_t B6 = B5 - 4000;
+ X1 = (calib->B2 * ((B6 * B6) >> 12)) >> 11;
+ X2 = (calib->AC2 * B6) >> 11;
+ int32_t X3 = X1 + X2;
+ int32_t B3 = (((calib->AC1 * 4 + X3) << oss) + 2) >> 2;
+
+ X1 = (calib->AC3 * B6) >> 13;
+ X2 = (calib->B1 * ((B6 * B6) >> 12)) >> 16;
+ X3 = ((X1 + X2) + 2) >> 2;
+
+ uint32_t B4 = (calib->AC4 * (uint32_t)(X3 + 32768)) >> 15;
+ uint32_t B7 = ((uint32_t)UP - B3) * (50000 >> oss);
+
+ int32_t p;
+ if (B7 < 0x80000000) {
+ p = (B7 << 1) / B4;
+ } else {
+ p = (B7 / B4) << 1;
+ }
+
+ X1 = (p >> 8) * (p >> 8);
+ X1 = (X1 * 3038) >> 16;
+ X2 = (-7357 * p) >> 16;
+
+ p = p + ((X1 + X2 + 3791) >> 4);
+
+ *pressure = p / 100.0; // Pa a hPa (mbar)
+ return 0;
+}
+```
+##### explanation
+###### Headers and defines
+```c
+#include "bmp180.h"
+#include
+#include
+#include
+#include
+#include
+
+#define BMP180_ADDR 0x77
+```
+- Includes standard and I2C Linux headers.
+- Defines the I2C address of the BMP180 sensor.
+###### read16() – Read 16-bit (2-byte) value from a register
+```c
+static int read16(int fd, uint8_t reg, int16_t *value)
+```
+- Sends the register address via write().
+- Reads 2 bytes and stores the result as a big-endian 16-bit signed integer.
+- Used to read calibration values and sensor data.
+###### write8() – Write 8-bit value to a register
+```c
+static int write8(int fd, uint8_t reg, uint8_t value)
+```
+- Sends 2 bytes: register address and value.
+- Used to trigger temperature or pressure measurements by writing command codes to control register 0xF4
+###### bmp180\_init() – Read all calibration data from sensor
+```c
+int bmp180_init(int fd, bmp180_calib_data_t *calib)
+```
+- Reads 11 calibration values from registers 0xAA to 0xBF.
+- These are factory-set and unique to each sensor.
+- Needed to apply temperature and pressure compensation formulas correctly.
+###### bmp180\_read\_raw\_temperature() – Trigger and read unprocessed temperature
+```c
+static int bmp180_read_raw_temperature(int fd, int32_t *raw_temp)
+```
+- Writes 0x2E to control register 0xF4 to start temperature conversion.
+- Waits 4.5 ms (conversion time).
+- Reads result from 0xF6 and 0xF7.
+###### bmp180\_read\_raw\_pressure() – Trigger and read unprocessed pressure
+```c
+static int bmp180_read_raw_pressure(int fd, int32_t *raw_press, int oss)
+```
+- Writes 0x34 + (oss << 6) to register 0xF4 to start pressure conversion.
+- Waits 25 ms for conversion (OSS=0).
+- Reads 3 bytes from 0xF6, 0xF7, 0xF8 and shifts them to get a 19-bit value.
+- oss (oversampling setting) affects resolution and delay.
+###### bmp180\_read\_temperature() – Convert raw temperature to °C
+```c
+int bmp180_read_temperature(int fd, bmp180_calib_data_t *calib, double *temperature)
+```
+- Uses raw temperature and calibration data.
+- Follows Bosch’s datasheet compensation formula:
+X1 = ((UT - AC6) * AC5) >> 15
+X2 = (MC << 11) / (X1 + MD)
+B5 = X1 + X2
+T = (B5 + 8) >> 4 → gives temperature in 0.1°C
+- Final result is divided by 10.0 to get °C.
+###### bmp180\_read\_pressure() – Convert raw pressure to hPa
+```c
+int bmp180_read_pressure(int fd, bmp180_calib_data_t *calib, double *pressure)
+```
+- Uses raw pressure and temperature (needed for B5).
+- Applies long compensation formula from datasheet:
+1. Many intermediate steps using calibration values.
+2. Computes B3, B4, B6, B7, X1, X2, X3, and finally p.
+- Final pressure p is in Pa; divide by 100.0 to get hPa (mbar).
+#### bmp180.h
+```c
+#ifndef BMP180_H
+#define BMP180_H
+
+#include
+
+typedef struct {
+ int16_t AC1, AC2, AC3;
+ uint16_t AC4, AC5, AC6;
+ int16_t B1, B2;
+ int16_t MB, MC, MD;
+} bmp180_calib_data_t;
+
+int bmp180_init(int fd, bmp180_calib_data_t *calib);
+int bmp180_read_temperature(int fd, bmp180_calib_data_t *calib, double *temperature);
+int bmp180_read_pressure(int fd, bmp180_calib_data_t *calib, double *pressure);
+
+#endif
+```
+##### Explanation
+###### Header
+```h
+#ifndef BMP180_H
+#define BMP180_H
+
+#include
+```
+- Header guard to prevent multiple inclusion.
+- Includes standard integer types (uint8_t, int16_t, etc.).
+###### Structure: bmp180\_calib\_data\_t
+```h
+typedef struct {
+ int16_t AC1, AC2, AC3;
+ uint16_t AC4, AC5, AC6;
+ int16_t B1, B2;
+ int16_t MB, MC, MD;
+} bmp180_calib_data_t;
+```
+- Holds all calibration constants (11 total).
+- Types match datasheet: some are signed, others unsigned.
+###### Function declarations
+```h
+int bmp180_init(int fd, bmp180_calib_data_t *calib);
+int bmp180_read_temperature(int fd, bmp180_calib_data_t *calib, double *temperature);
+int bmp180_read_pressure(int fd, bmp180_calib_data_t *calib, double *pressure);
+```
+#### main.c
+```c
+#include
+#include
+#include
+#include
+#include
+#include "bmp180.h"
+
+#define I2C_BUS "/dev/i2c-2"
+#define BMP180_ADDR 0x77
+
+int main() {
+ int fd = open(I2C_BUS, O_RDWR);
+ if (fd < 0) {
+ perror("Error abriendo el bus I2C");
+ return 1;
+ }
+
+ if (ioctl(fd, I2C_SLAVE, BMP180_ADDR) < 0) {
+ perror("Error configurando la dirección I2C");
+ close(fd);
+ return 1;
+ }
+
+ bmp180_calib_data_t calib;
+ if (bmp180_init(fd, &calib) < 0) {
+ fprintf(stderr, "Error leyendo datos de calibración BMP180\n");
+ close(fd);
+ return 1;
+ }
+
+ double temperature, pressure;
+
+ if (bmp180_read_temperature(fd, &calib, &temperature) < 0) {
+ fprintf(stderr, "Error leyendo temperatura BMP180\n");
+ close(fd);
+ return 1;
+ }
+
+ if (bmp180_read_pressure(fd, &calib, &pressure) < 0) {
+ fprintf(stderr, "Error leyendo presión BMP180\n");
+ close(fd);
+ return 1;
+ }
+
+ printf("{\"temperature\": %.2f, \"pressure\": %.2f}\n", temperature, pressure);
+
+ close(fd);
+ return 0;
+}
+
+```
+##### Explanation
+###### Open I2C Bus
+```c
+int fd = open("/dev/i2c-2", O_RDWR);
+```
+- Opens I2C device for read/write.
+###### Set I2C Slave Address
+```c
+ioctl(fd, I2C_SLAVE, BMP180_ADDR);
+```
+- Tells the kernel which I2C device address (0x77) to communicate with.
+###### Initialize BMP180
+```c
+bmp180_init(fd, &calib);
+```
+- Reads calibration constants from the sensor.
+###### Read Temperature and Pressure
+```c
+bmp180_read_temperature(fd, &calib, &temperature);
+bmp180_read_pressure(fd, &calib, &pressure);
+```
+- Uses previously stored calibration values to read and convert real temperature and pressure values.
+###### Output in JSON Format
+```c
+printf("{\"temperature\": %.2f, \"pressure\": %.2f}\n", temperature, pressure);
+```
+- Very useful becouse we are integrating with a frontend (e.g. web UI or logger).
+###### Cleanup
+```c
+close(fd);
+```
+- Closes the I2C device after finishing communication.
+
+## Results
+Here are some screenshots to prove the functionality of the UI.