#include <Wire.h>
#include <Adafruit_BMP085.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <Encoder.h>
// --- I2C OLED Display Definitions ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, 0x3C);
// --- Sensor Object Initialization ---
Adafruit_BMP085 bmp;
// --- Pin Definitions ---
// Pins 2 and 3 are used for external interrupts (by Encoder library).
const int ENCODER_CLK_PIN = 2; // Clock (CLK)
const int ENCODER_DT_PIN = 3; // Data (DT)
const int ENCODER_SW_PIN = 6; // Switch (SW) - Now handled via polling
const int BUZZER_PIN = 8;
const int LED_GREEN = 9;
const int LED_YELLOW = 10;
const int LED_RED = 11;
// --- Rotary Encoder Initialization ---
Encoder knob(ENCODER_DT_PIN, ENCODER_CLK_PIN);
// --- Global Variables & Constants ---
const float MAX_UPPER_RANGE = 1095.0;
const float MIN_LOWER_RANGE = 900.0;
const int ALERT_TONE = 1000;
// Encoder State Variables
long oldEncoderPosition = 0;
// Menu State Machine
enum MenuState { MONITOR, SET_UPPER, SET_LOWER };
MenuState currentState = MONITOR;
int currentMenuSelection = 0; // 0=Upper, 1=Lower
// User-set Limits (hPa)
float setpointPressure = 1000.0;
float upperLimitSet = 1010.0;
float lowerLimitSet = 990.0;
// --- FUNCTION PROTOTYPES ---
void handleEncoderRotation();
void handleSwitchPolling(); // New polling function
void updateOLED();
void updateAlerts(float currentP, float lowerL, float upperL);
// =========================================================
// SETUP FUNCTION
// =========================================================
void setup() {
Serial.begin(9600);
// --- Initialize I/O Pins ---
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_RED, OUTPUT);
// Initialize Encoder Switch Pin with PULLUP
// This is required for the polling function to work correctly.
pinMode(ENCODER_SW_PIN, INPUT_PULLUP);
// --- Attach Interrupts ---
// Rotary switch interrupt REMOVED. Polling will handle it in the loop().
noTone(BUZZER_PIN);
digitalWrite(LED_GREEN, HIGH);
if (!bmp.begin()) {
digitalWrite(LED_RED, HIGH);
while (1);
}
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true);
}
display.display();
delay(1000);
display.clearDisplay();
}
// =========================================================
// LOOP FUNCTION (Main Logic)
// =========================================================
void loop() {
handleEncoderRotation(); // Rotation is fast (interrupt-based)
handleSwitchPolling(); // Switch is polled (software debounced)
float currentPressure = bmp.readPressure() / 100.0F;
updateAlerts(currentPressure, lowerLimitSet, upperLimitSet);
updateOLED();
delay(50);
}
// =========================================================
// CUSTOM FUNCTIONS
// =========================================================
/**
* @brief Polling function with software debounce to handle encoder switch presses.
*/
void handleSwitchPolling() {
// Static variable to store the time of the last valid press
static unsigned long lastPressTime = 0;
// Check if the button is pressed (LOW, due to INPUT_PULLUP)
if (digitalRead(ENCODER_SW_PIN) == LOW) {
// Check if enough time (200ms) has passed since the last press (debounce)
if (millis() - lastPressTime > 200) {
// --- Menu Logic Execution ---
if (currentState == MONITOR) {
if (currentMenuSelection == 0) {
currentState = SET_UPPER;
} else if (currentMenuSelection == 1) {
currentState = SET_LOWER;
}
} else if (currentState == SET_UPPER || currentState == SET_LOWER) {
// Save and exit edit mode
currentState = MONITOR;
// The selection arrow remains on the item just edited.
}
lastPressTime = millis(); // Record the time of this valid press
}
}
}
void handleEncoderRotation() {
long newPosition = knob.read();
if (newPosition != oldEncoderPosition) {
// Calculate delta and divide by a factor (e.g., 4) for stability/sensitivity
int delta = (newPosition - oldEncoderPosition) / 4;
if (delta != 0) {
// 1. Monitor/Selection State: Rotate between Upper/Lower setting
if (currentState == MONITOR) {
currentMenuSelection = (currentMenuSelection + delta) % 2;
if (currentMenuSelection < 0) currentMenuSelection = 1;
// 2. Setting State: Rotate changes the limit value
} else if (currentState == SET_UPPER) {
upperLimitSet = constrain(upperLimitSet + delta, lowerLimitSet + 1, MAX_UPPER_RANGE);
} else if (currentState == SET_LOWER) {
lowerLimitSet = constrain(lowerLimitSet + delta, MIN_LOWER_RANGE, upperLimitSet - 1);
}
}
oldEncoderPosition = newPosition;
}
}
/**
* @brief Drives the physical outputs (LEDs and Passive Buzzer) based on status
*/
void updateAlerts(float currentP, float lowerL, float upperL) {
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_RED, LOW);
noTone(BUZZER_PIN);
if (currentP >= upperL) {
digitalWrite(LED_RED, HIGH);
tone(BUZZER_PIN, ALERT_TONE);
}
else if (currentP <= lowerL) {
digitalWrite(LED_YELLOW, HIGH);
tone(BUZZER_PIN, ALERT_TONE);
}
else {
digitalWrite(LED_GREEN, HIGH);
}
}
/**
* @brief Updates the OLED display based on the current Menu State.
*/
void updateOLED() {
float currentPressure = bmp.readPressure() / 100.0F;
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// --- 1. Current Pressure (Smooth Font) ---
display.setFont(&FreeSansBold9pt7b);
display.setTextSize(1);
display.setCursor(0, 15);
display.print("P: ");
display.print((int)currentPressure);
display.print(" hPa");
// --- Switch back to standard font for menu/status ---
display.setFont();
display.setTextSize(1);
int status = (currentPressure >= upperLimitSet) ? 2 : (currentPressure <= lowerLimitSet) ? 1 : 0;
// --- 2. Status Indicator ---
display.setCursor(0, 25);
display.print("Status: ");
if (status == 2) display.print("HIGH ALERT");
else if (status == 1) display.print("LOW ALERT");
else display.print("NORMAL");
// --- 3. Limit Settings Menu ---
// Print UPPER Limit (Row 40)
display.setCursor(0, 40);
display.print("UPPER: ");
display.print((int)upperLimitSet);
display.print(" hPa");
// Print LOWER Limit (Row 55)
display.setCursor(0, 55);
display.print("LOWER: ");
display.print((int)lowerLimitSet);
display.print(" hPa");
// --- 4. Menu Selector / Editor Indicator ---
if (currentState == MONITOR) {
// Show arrow indicating selected limit
display.setCursor(75, (currentMenuSelection == 0) ? 40 : 55);
display.print(" <--");
} else if (currentState == SET_UPPER) {
// In edit mode: flash the value being edited
if (millis() % 500 < 250) {
display.setCursor(45, 40);
display.fillRect(45, 40, 30, 8, BLACK);
}
} else if (currentState == SET_LOWER) {
// In edit mode: flash the value being edited
if (millis() % 500 < 250) {
display.setCursor(45, 55);
display.fillRect(45, 55, 30, 8, BLACK);
}
}
display.display();
}