Arduino Projects
This is a collection of Arduino based projects I have been working on. Most of them are designed to tie into my broader homelab/Home assistant setup while some are standalone projects.
Project list:
Motion sensor camera
// Sketch to use an ESP32 + esp32 camera to run a motion detection algorithm and save snapshots when motion is detected
#include "esp_camera.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include "../secrets.h"
// Target Unraid Server
const char* serverName = "http://192.168.xx.xxx:8080/";
// ========== PINS FOR ESP32-S CAM ==========
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
// Globals
WiFiClient client;
unsigned long lastTriggerTime = 0;
long lastFingerprint = 0;
const int motionThreshold = 2500;
void setup_wifi() {
Serial.print("Connecting to WiFi");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected! IP: " + WiFi.localIP().toString());
}
void setupCamera() {
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 10000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 15;
config.fb_count = 1;
config.fb_location = CAMERA_FB_IN_DRAM;
config.grab_mode = CAMERA_GRAB_LATEST;
// 1. Initialize Camera
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed: 0x%x\n", err);
return;
}
// 2. Adjust Sensor Settings AFTER Init
sensor_t * s = esp_camera_sensor_get();
if (s) {
s->set_whitebal(s, 1);
s->set_awb_gain(s, 1);
s->set_exposure_ctrl(s, 0);
s->set_aec_value(s, 300);
}
Serial.println("Camera Ready!");
}
// Function to compare previous frame with current frame to detect motion
bool isMotion(camera_fb_t * fb) {
if (!fb || fb->len < 1000) return false;
long currentFingerprint = 0;
// Build up fingerprint from current frame buffer
for (size_t i = 500; i < fb->len; i += 100) {
currentFingerprint += fb->buf[i];
}
if (lastFingerprint == 0) {
lastFingerprint = currentFingerprint;
return false;
}
// compare new and old finger print with our threshold for motion
long diff = abs(currentFingerprint - lastFingerprint);
lastFingerprint = currentFingerprint;
if (diff > motionThreshold) {
Serial.printf("Motion Detected! Diff: %ld\n", diff);
return true;
}
return false;
}
// Function to send the image to unraid server
void sendImage(camera_fb_t * fb) {
HTTPClient http;
http.begin(client, serverName);
http.addHeader("Content-Type", "image/jpeg");
int httpResponseCode = http.POST(fb->buf, fb->len);
Serial.printf("Unraid Response: %d\n", httpResponseCode);
http.end();
}
void setup() {
Serial.begin(115200);
setup_wifi();
setupCamera();
}
void loop() {
// get most recent frame buffer
camera_fb_t * fb = esp_camera_fb_get();
if (!fb) return;
// Check if motion occurred AND if we are out of the 5-second cooldown to prevent constant detections
if (isMotion(fb) && (millis() - lastTriggerTime > 5000)) {
sendImage(fb); //Send image if motion was detected
lastTriggerTime = millis();
}
esp_camera_fb_return(fb);
delay(200); // 5 checks per second
}Task Timer
// Task Timer V2 for ESP32.
// After arduino Uno prototype, this script converts the script to run on an ESP32 board for a smaller form factor for the prototype device.
// Also includes a lot of logic and timing optimization to account for the less powerful ESP32 to prevent flicker and delays in operation.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Pin Definitions for ESP32
const int xPin = 35;
const int yPin = 34;
const int swPin = 23;
int latchPin = 33;
int dataPin = 25;
int clockPin = 32;
const int digitPins[4] = { 13, 14, 27, 26 };
// Common Anode bit Map for displaying the digits
const byte digitMap[] = {
0b00000011, 0b10011111, 0b00100101, 0b00001101, 0b10011001,
0b01001001, 0b01000001, 0b00011111, 0b00000001, 0b00001001,
0b10010001 // Index 10: "H"
};
// Timing and State
unsigned long previousMillis = 0;
unsigned long lastLogicUpdate = 0;
const int interval = 1000;
int menuScreen = 0;
bool loaded = false;
int taskSelected = 0;
// Global Digit Storage for 7-Seg
int d[4] = {0, 0, 0, 0};
bool globalColon = false;
struct Task {
String taskName;
long targetTime;
long timeSpent;
};
Task taskArray[3];
int numTasks = 2;
// Debounce variables
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 200;
void setup() {
Serial.begin(115200); // ESP32 standard is 115200
Wire.setClock(400000); // Speed up I2C for the LCD
lcd.init();
lcd.backlight();
pinMode(swPin, INPUT_PULLUP);
pinMode(latchPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
for (int i = 0; i < 4; i++) {
pinMode(digitPins[i], OUTPUT);
digitalWrite(digitPins[i], HIGH);
}
taskArray[0] = {"Task Timer", 3600000, 0};
taskArray[1] = {"Edit Photos", 3600000, 0};
taskArray[2] = {"Website", 3600000, 0};
}
void loop() {
unsigned long currentMillis = millis();
// 1. REFRESH 7-SEGMENT (One digit per loop for maximum speed)
long remaining = (taskArray[taskSelected].targetTime - taskArray[taskSelected].timeSpent) / 1000;
if (remaining < 0) remaining = 0;
updateDigitBuffer((int)remaining);
refreshSevenSegment();
// 2. RUN LOGIC (Every 50ms to keep I2C bus clear)
if (currentMillis - lastLogicUpdate > 50) {
lastLogicUpdate = currentMillis;
handleInputsAndMenu();
}
yield(); // Required for ESP32 background stability
}
// Pre-calculates the digits so the refresh function is lightning fast
void updateDigitBuffer(int seconds) {
if (seconds > 3600) {
d[0] = seconds / 3600;
d[1] = 10; // "H"
int mins = (seconds % 3600) / 60;
d[2] = mins / 10;
d[3] = mins % 10;
globalColon = false;
} else {
int mins = seconds / 60;
int secs = seconds % 60;
d[0] = mins / 10;
d[1] = mins % 10;
d[2] = secs / 10;
d[3] = secs % 10;
globalColon = true;
}
}
// Cycles through one digit each time it is called
void refreshSevenSegment() {
static int currentDigit = 0;
byte data = digitMap[d[currentDigit]];
// Apply colon to the second digit if required
if (currentDigit == 1 && globalColon) data &= 0b11111110;
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, ~data);
digitalWrite(latchPin, HIGH);
digitalWrite(digitPins[currentDigit], LOW); // ON
delayMicroseconds(600); // Brightness control TODO: tie value to potentiometer knob
digitalWrite(digitPins[currentDigit], HIGH); // OFF
currentDigit = (currentDigit + 1) % 4;
}
void handleInputsAndMenu() {
// ESP32 ADC is 12-bit (0-4095)
int xValue = analogRead(xPin);
int yValue = analogRead(yPin);
// The SW pin on joystick is usually active LOW (0 when pressed)
bool swState = digitalRead(swPin);
bool buttonPressed = debounce(swState);
// Determine Joystick Direction with 4095 scaling
// Center is roughly 2048
int joy = 0; // CENTER
if (yValue < 1000) joy = 1; // UP
else if (yValue > 3000) joy = 3; // DOWN
else if (xValue < 1000) joy = 4; // LEFT
else if (xValue > 3000) joy = 2; // RIGHT
// --- MENU NAVIGATION ---
// We use a "lastJoy" check to ensure we only move ONCE per tilt
static int lastJoy = 0;
if (joy != lastJoy && joy != 0) {
if (menuScreen == 1) { // SETTASK
if (joy == 2) { // RIGHT
taskSelected = (taskSelected + 1) % 3;
loaded = false;
} else if (joy == 4) { // LEFT
taskSelected = (taskSelected - 1 + 3) % 3;
loaded = false;
}
}
else if (menuScreen == 2) { // SETTIMER
if (joy == 1) { // UP
taskArray[taskSelected].targetTime += 60000;
loaded = false;
} else if (joy == 3) { // DOWN
if (taskArray[taskSelected].targetTime > 60000) {
taskArray[taskSelected].targetTime -= 60000;
loaded = false;
}
}
}
else if (menuScreen == 0) { // WELCOME
menuScreen = 1;
loaded = false;
}
}
lastJoy = joy;
// --- BUTTON NAVIGATION ---
if (buttonPressed) {
if (menuScreen == 1) menuScreen = 2; // From Task to Timer
else if (menuScreen == 2) menuScreen = 3; // From Timer to Running
else if (menuScreen == 3) menuScreen = 4; // From Running to Paused
else if (menuScreen == 4) menuScreen = 3; // From Paused to Running
loaded = false;
}
// --- TIMER TICKING ---
if (menuScreen == 3) { // RUNNING
if (millis() - previousMillis >= interval) {
previousMillis = millis();
taskArray[taskSelected].timeSpent += 1000;
int currentSecs = (taskArray[taskSelected].timeSpent / 1000) % 60;
// ONLY update LCD when seconds hit zero (every minute)
// or if we just started (loaded == false)
if (currentSecs == 0) {
updateLCDOnlyNumbers();
}
}
}
// Reload screen if menu changed
if (!loaded) {
renderFullLCD();
}
}
void renderFullLCD() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(taskArray[taskSelected].taskName);
updateLCDOnlyNumbers();
loaded = true;
}
void updateLCDOnlyNumbers() {
lcd.setCursor(0, 1);
lcd.print(taskArray[taskSelected].timeSpent / 60000);
lcd.print("min / ");
lcd.print(taskArray[taskSelected].targetTime / 60000);
lcd.print("min "); // Spaces clear old digits
}
bool debounce(int reading) {
if (reading == LOW && (millis() - lastDebounceTime > debounceDelay)) {
lastDebounceTime = millis();
return true;
}
return false;
}Temp and Humidity Sensors
// Greenhouse Temperature and humidity sensor
// Sketch to get data from dht22 sensor, connect to wifi, and send the info to home assistant through MQTT broker
#include "../secrets.h"
#include <WiFi.h>
#include <DHT.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>
// MQTT Settings
const int mqtt_port = 1883;
const char* mqtt_topic = "home/esp32/temperature";
// DHT Setup
#define DHTPIN 14 // GPIO14 = DHT22 temp sensor
#define DHTTYPE DHT22 // DHT 22 (AM2302)
DHT dht(DHTPIN, DHTTYPE);
// MQTT Topics for our two bits of info we are sending
const char* temperature_topic = "home/esp32/temperature/greenhouse";
const char* humidity_topic = "home/esp32/humidity/greenhouse";
WiFiClient espClient;
PubSubClient client(espClient);
void setup_wifi() {
Serial.println();
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.mode(WIFI_STA); //Station mode
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int retries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
retries++;
if (retries > 20) { // try for 10 seconds
Serial.println("Failed to connect to WiFi!");
return;
}
}
Serial.println("");
Serial.println("WiFi connected!");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void reconnect() {
// Loop until reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP32Client", MQTT_USER, MQTT_PASSWORD)) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
dht.begin();
setup_wifi();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// OTA Setup
ArduinoOTA.setHostname(GREENHOUSE_OTA_HOSTNAME);
ArduinoOTA.setPassword(GREENHOUSE_OTA_PASSWORD);
ArduinoOTA.onStart([]() {
Serial.println("Start updating...");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("OTA Ready");
client.setServer(MQTT_SERVER, 1883);
}
void loop() {
ArduinoOTA.handle();
if (!client.connected()) {
reconnect();
}
client.loop();
// Read DHT22 valuesj
float humidity = dht.readHumidity();
float temperature = dht.readTemperature(true); // Celsius by default
if (isnan(humidity) || isnan(temperature)) {
Serial.println("Failed to read from DHT sensor!");
return;
}
// Debug prints
Serial.print("Temperature: ");
Serial.print(temperature);
Serial.print(" °F | Humidity: ");
Serial.print(humidity);
Serial.println(" %");
// Publish to MQTT
client.publish(temperature_topic, String(temperature).c_str(), true);
client.publish(humidity_topic, String(humidity).c_str(), true);
delay(30000); // Read and publish every 30 seconds
}
Doorbell
// Doorbell imitation device
// Once 'doorbell' is pressed on breadboard, publish to MQTT so Home assistant can play doorbell sounds on my computer
#include "../../secrets.h"
#include <WiFi.h>
#include <DHT.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>
// constants
const int buttonPin = 33;
const int ledPin = 25;
// --- MQTT Settings ---
const int mqtt_port = 1883;
const char* mqtt_topic = "home/doorbell";
WiFiClient espClient;
PubSubClient client(espClient);
int buttonState = 0;
int lastButtonState = HIGH;
void setup_wifi() {
Serial.println();
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.mode(WIFI_STA); //Station mode
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int retries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
retries++;
if (retries > 20) { // After 10 seconds give up
Serial.println("Failed to connect to WiFi!");
retries = 0;
return;
}
}
Serial.println("");
Serial.println("WiFi connected!");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void reconnect() {
// Loop until reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP32Client", MQTT_USER, MQTT_PASSWORD)) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup_OTA() {
// OTA Setup
ArduinoOTA.setHostname(DOORBELL_OTA_HOSTNAME);
ArduinoOTA.setPassword(DOORBELL_OTA_PASSWORD);
ArduinoOTA.onStart([]() {
Serial.println("Start updating...");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("OTA Ready");
}
void setup() {
Serial.begin(115200);
// initialize the LED pin as an output:
pinMode(ledPin, OUTPUT);
// initialize the pushbutton pin as an input:
pinMode(buttonPin, INPUT_PULLUP);
setup_wifi(); //Call wifi setup
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
setup_OTA(); //Call OTA setup
client.setServer(MQTT_SERVER, 1883); //Set MQTT server
}
void loop() {
ArduinoOTA.handle();
if (!client.connected()) {
reconnect();
}
client.loop();
// Read the state
buttonState = digitalRead(buttonPin);
// Detect a press, LOW = Pressed
if (buttonState == LOW && lastButtonState == HIGH) {
Serial.println("Button Pressed");
digitalWrite(ledPin, HIGH);
//Publish to MQTT for Homeassistant to hear
client.publish(mqtt_topic, "pressed");
}
else if (buttonState == HIGH && lastButtonState == LOW) {
// Button released
digitalWrite(ledPin, LOW);
client.publish(mqtt_topic, "unpressed");
}
// Save the current state for next loop
lastButtonState = buttonState;
}
Desk Dashboard
// Hank Desk Dash V1
// beginning code for a desk dashboard to control my smarthome and view real time info
// With an ESP32, an oled display, temp/humidity sensor, and basic controls.
// Connects to wifi and displays info on screen and sends commands back to Home assistant through MQTT
#include "../secrets.h"
#include <WiFi.h>
#include <DHT.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>
#include <SPI.h>
#include <U8g2lib.h>
// MQTT Settings
const int mqtt_port = 1883; // default port
const char* mqtt_topic = "home/esp32/temperature";
// DHT Setup
#define DHTPIN 14 // GPIO14 DHT22 is
#define DHTTYPE DHT22 // DHT 22 (AM2302)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
// SPI OLED pin definitions
#define OLED_DC 17
#define OLED_CS 5
#define OLED_RESET 16
// Constructor for SH1106 128x64 SPI
U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ OLED_CS, /* dc=*/ OLED_DC, /* reset=*/ OLED_RESET);
DHT dht(DHTPIN, DHTTYPE);
// MQTT Topics
const char* temperature_topic = "home/esp32/temperature/hanksdesk";
const char* humidity_topic = "home/esp32/humidity/hanksdesk";
WiFiClient espClient;
PubSubClient client(espClient);
void setup_wifi() {
Serial.println();
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.mode(WIFI_STA); // Station mode
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int retries = 0;
while (WiFi.status() != WL_CONNECTED) {
// Show "Connecting..." screen with dot animation
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 12, "Connecting to WiFi");
u8g2.drawStr(0, 28, WIFI_SSID);
// Animate dots based on retry count
char dots[4] = " ";
for (int i = 0; i < (retries % 4); i++) dots[i] = '.';
u8g2.drawStr(0, 44, dots);
u8g2.sendBuffer();
delay(500);
Serial.print(".");
retries++;
if (retries > 20) { // After 10 seconds give up
Serial.println("Failed to connect to WiFi!");
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 12, "WiFi Failed!");
u8g2.drawStr(0, 28, "Check credentials");
u8g2.sendBuffer();
return;
}
}
Serial.println("");
Serial.println("WiFi connected!");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Show "Connected!" screen with IP address
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 12, "WiFi Connected!");
u8g2.drawStr(0, 28, WIFI_SSID);
// Display IP address
char ipStr[20];
WiFi.localIP().toString().toCharArray(ipStr, sizeof(ipStr));
u8g2.drawStr(0, 44, ipStr);
// Show current temp/humidity on connected screen if sensor is ready
float h = dht.readHumidity();
float t = dht.readTemperature(true); // Fahrenheit
if (!isnan(h) && !isnan(t)) {
char sensorStr[32];
snprintf(sensorStr, sizeof(sensorStr), "%.1fF %.1f%%RH", t, h);
u8g2.drawStr(0, 58, sensorStr);
}
u8g2.sendBuffer();
delay(2000); // Hold the connected screen for 2 seconds
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP32Client", MQTT_USER, MQTT_PASSWORD)) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup_OTA() {
// OTA Setup
ArduinoOTA.setHostname(HANKDESKDASH_OTA_HOSTNAME);
ArduinoOTA.setPassword(HANKDESKDASH_OTA_PASSWORD);
ArduinoOTA.onStart([]() {
Serial.println("Start updating...");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("OTA Ready");
}
void setup() {
Serial.begin(115200); //Serial setup
// Init display first so we can give feedback on wifi status
u8g2.begin();
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 12, "Hank's Desk");
u8g2.drawStr(0, 28, "Starting up...");
u8g2.sendBuffer();
dht.begin();
delay(2000);
setup_wifi(); //Call wifi setup
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
setup_OTA(); //Call OTA setup
client.setServer(MQTT_SERVER, 1883); //Set MQTT server
}
void loop() {
ArduinoOTA.handle();
if (!client.connected()) {
reconnect();
}
client.loop();
// Read DHT22 values
float humidity = dht.readHumidity();
float temperature = dht.readTemperature(true); // Fahrenheit
if (isnan(humidity) || isnan(temperature)) {
Serial.println("Failed to read from DHT sensor!");
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 12, "Sensor Error!");
u8g2.drawStr(0, 28, "DHT22 read failed");
u8g2.sendBuffer();
return;
}
// Debug prints
// Serial.print("Temperature: ");
// Serial.print(temperature);
// Serial.print(" °F | Humidity: ");
// Serial.print(humidity);
// Serial.println(" %");
// Publish to MQTT
client.publish(temperature_topic, String(temperature).c_str(), true);
client.publish(humidity_topic, String(humidity).c_str(), true);
// Update display with current readings
char tempStr[16];
char humStr[16];
char ipStr[20];
snprintf(tempStr, sizeof(tempStr), "Temp: %.1f F", temperature);
snprintf(humStr, sizeof(humStr), "Hum: %.1f %%", humidity);
WiFi.localIP().toString().toCharArray(ipStr, sizeof(ipStr));
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 12, "Hank's Desk");
u8g2.drawStr(0, 28, tempStr);
u8g2.drawStr(0, 44, humStr);
u8g2.drawStr(0, 58, ipStr);
u8g2.sendBuffer();
delay(30000); // Read and publish every 30 seconds
}