#include <Wire.h> // Built-in I2C library
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_SSD1306.h> // OLED screen library
#include <OneWire.h> // 1-Wire protocol library
#include <DallasTemperature.h> // DS18B20 sensor library
// --- Hardware Instantiations ---
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature tempSensors(&oneWire);
// --- Screen Settings ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// --- MUX Control Pins ---
#define MUX_SIG 34 // Analog read pin on ESP32
#define MUX_S0 13
#define MUX_S1 12
#define MUX_S2 14
#define MUX_S3 27
// --- Navigation ---
#define BTN_NEXT 18 // Pushbutton to switch views
// --- Data Structures ---
struct Breaker {
float voltage; // In Volts
float current; // In Amperes
float temperature; // In Celsius
bool isTripped; // Fault status
};
Breaker breakers[6];
int currentView = 0; // 0 = Summary Table, 1-6 = Individual Breaker Views
unsigned long lastButtonPress = 0;
unsigned long lastTempRequest = 0;
// Function Prototypes
float readMuxChannel(byte channel);
void updateSensors();
void renderUI();
void setup() {
Serial.begin(115200);
// Initialize I2C bus on GPIO 21 (SDA) and GPIO 22 (SCL)
Wire.begin(21, 22);
// Internal Pull-Up for DS18B20 DQ Line on GPIO 4
pinMode(ONE_WIRE_BUS, INPUT_PULLUP);
// Initialize MUX control pins
pinMode(MUX_S0, OUTPUT);
pinMode(MUX_S1, OUTPUT);
pinMode(MUX_S2, OUTPUT);
pinMode(MUX_S3, OUTPUT);
pinMode(MUX_SIG, INPUT);
// Initialize Navigation Button
pinMode(BTN_NEXT, INPUT_PULLUP);
// Initialize OLED Screen
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
// Initialize DS18B20 Temperature Sensors on 1-Wire bus
tempSensors.begin();
tempSensors.setWaitForConversion(false); // Non-blocking conversion
// Trigger initial temperature conversion
tempSensors.requestTemperatures();
// Splash Screen
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(15, 20);
display.println(F("SMART SSCB PANEL"));
display.setCursor(22, 38);
display.println(F("System Ready"));
display.display();
delay(1000);
}
void loop() {
// 1. Read sensor data across MUX & 1-Wire bus
updateSensors();
// 2. Handle button navigation (Debounced)
if (digitalRead(BTN_NEXT) == LOW && (millis() - lastButtonPress > 250)) {
lastButtonPress = millis();
currentView = (currentView + 1) % 7; // Cycles 0 -> 1 -> ... -> 6 -> 0
}
// 3. Render User Interface
renderUI();
delay(30); // Display refresh rate
}
// Reads standard 16-channel MUX via binary control pins
float readMuxChannel(byte channel) {
digitalWrite(MUX_S0, bitRead(channel, 0));
digitalWrite(MUX_S1, bitRead(channel, 1));
digitalWrite(MUX_S2, bitRead(channel, 2));
digitalWrite(MUX_S3, bitRead(channel, 3));
delayMicroseconds(10); // Settling delay for MUX switch
int rawADC = analogRead(MUX_SIG);
// Convert 12-bit ADC (0-4095) to 0.0V - 3.3V
return (rawADC / 4095.0) * 3.3;
}
void updateSensors() {
// 1. Fetch temperature readings every 1000ms
if (millis() - lastTempRequest >= 1000) {
lastTempRequest = millis();
for (int i = 0; i < 6; i++) {
float tempReading = tempSensors.getTempCByIndex(i);
if (tempReading != DEVICE_DISCONNECTED_C && tempReading > -50.0) {
breakers[i].temperature = tempReading;
} else {
breakers[i].temperature = 25.0; // Fallback if sensor index 'i' is missing
}
}
// Trigger next conversion cycle
tempSensors.requestTemperatures();
}
// 2. Read MUX channels and process trip logic
for (int i = 0; i < 6; i++) {
float rawVoltageSense = readMuxChannel(i * 2); // Channels 0, 2, 4, 6, 8, 10
float rawCurrentSense = readMuxChannel((i * 2) + 1); // Channels 1, 3, 5, 7, 9, 11
// 48V System Scaling
breakers[i].voltage = (rawVoltageSense / 3.3) * 60.0;
breakers[i].current = (rawCurrentSense / 3.3) * 25.0;
// Fault Trip Logic (48V nominal system)
breakers[i].isTripped = (breakers[i].voltage > 56.0 ||
breakers[i].current > 15.0 ||
breakers[i].temperature > 80.0);
}
}
// Draws the display screen based on current selection
void renderUI() {
display.clearDisplay();
if (currentView == 0) {
// --- DASHBOARD OVERVIEW TABLE ---
display.setTextSize(1);
display.setCursor(0, 0);
display.println(F("PANEL OVERVIEW [NEXT>]"));
display.drawLine(0, 9, 128, 9, SSD1306_WHITE);
for (int i = 0; i < 6; i++) {
int y = 13 + (i * 8);
display.setCursor(0, y);
display.print(F("B"));
display.print(i + 1);
display.print(F(": "));
if (breakers[i].isTripped) {
display.print(F("TRIPPED!"));
} else {
display.print(breakers[i].voltage, 1);
display.print(F("V "));
display.print(breakers[i].current, 1);
display.print(F("A"));
}
}
} else {
// --- INDIVIDUAL BREAKER DETAILED VIEW ---
int bIndex = currentView - 1;
display.setTextSize(1);
display.setCursor(0, 0);
display.print(F("BREAKER #"));
display.print(currentView);
display.print(F(" DETAILS"));
display.drawLine(0, 9, 128, 9, SSD1306_WHITE);
display.setCursor(0, 14);
display.print(F("Status: "));
if (breakers[bIndex].isTripped) {
display.println(F("[ TRIPPED ]"));
} else {
display.println(F("[ NORMAL OK ]"));
}
display.setCursor(0, 24);
display.print(F("Voltage: "));
display.print(breakers[bIndex].voltage, 1);
display.println(F(" V"));
display.setCursor(0, 34);
display.print(F("Current: "));
display.print(breakers[bIndex].current, 1);
display.println(F(" A"));
display.setCursor(0, 44);
display.print(F("Power: "));
float power = breakers[bIndex].voltage * breakers[bIndex].current;
display.print(power, 1);
display.println(F(" W"));
display.setCursor(0, 54);
display.print(F("Temp: "));
display.print(breakers[bIndex].temperature, 1);
display.println(F(" C"));
}
display.display();
}