Skip to content

PGTBoos/ESP32HomeAutomation-RuleBased

Repository files navigation

ESP32HomeAutomation-RuleBased

ESP32 home automation powered by a fluent rule engine. Declarative rules for socket control based on time, light levels, phone presence, and solar production. Built with a clean DSL that reads like English. For about 30 Euro (or less, thanks to uncle Ali )

A typical day:

Wake up, light turns on if it's not light enough until you go to work.
At daytime, too much solar power? Then you turn on a heater (or cooler).
At some lux level, you turn on the main light, and at 6, the TV backlight flips on.
Between 23:00 / 24:00, randomly turn off a main light (or only when you're not at home).
But you're still watching TV, so the backlight turns off only if all other lights are off.
Or do you want sun up, sun down? This code has your back.
As it contains location-based sun up / down triggers.
And yes, it is NTP time synced, handling winter and summer time fine.
Some actions keep a switch for a duration under control.
While others allow for manual changes (or delayed actions).

Overview Schematic
split your breadboards its esp32 time schematic

How it looks from the website http(your ip addres):8080/index.html webview

(note in above picture it had no more archived data so week and month dont show)

MAJOR UPDATE 31-12-2025, 8 wall sockets support!

Major rewrites, since esp32 has some limits on active sockets.
The startup discovery is a bit slower now, though socket exhaustion is solved now.
Also stability updates, and a rule fix! (bug OnCondition didnt skip but turned off).

Supports 8 sockets now!

SmartRuleSystem Wiki

A flexible rule-based automation system for ESP32 smart home control.
Rules evaluate conditions and time windows to control WiFi sockets automatically.


Table of Contents


Core Concepts

Decision Meaning
On Turn the socket on
Off Turn the socket off
Skip Don't change anything, let other rules decide

Rules are evaluated in order. Later rules can override earlier ones if they return On or Off.
Time format: HH:MM. Duration parameters use seconds (600 = 5 minutes).


Configuration

setLocation - Configure sun position calculations
setLocation(latitude, longitude, elevationMeters)

Sets your geographic location for sunrise/sunset calculations. Use decimal degrees format (right-click in Google Maps to copy coordinates). Negative values for South latitude and West longitude. Elevation defaults to 0 and is optional.

SmartRuleSystem::setLocation(52.37, 4.90, 0);    // Amsterdam
SmartRuleSystem::setLocation(46.82, 8.40, 1500); // Swiss Alps

Rule Actions

period - Active during a time window
period(startTime, endTime, condition)

Turns on if condition is met, returns skip if not. Automatically turns off in the last 2 minutes of the period.

Use case: Morning light that only activates when it's dark.

boolPeriod - On/Off based on condition in time window
boolPeriod(startTime, endTime, condition)

Returns on when condition is true, off when false. Unlike period, this actively turns off when condition fails (no skip).

Use case: Light that follows a sensor throughout the entire period.

onAfter - Turn on after specified time
onAfter(timeStr, durationMins, condition)

Turns on after the specified time if condition is met. Returns skip otherwise. The optional durationMins creates a time window.

Use case: Turn on evening lights after 18:00 when phone is home.

offAfter - Turn off after specified time
offAfter(timeStr, durationMins, condition)

Turns off after the specified time if condition is met. Returns skip otherwise.

Use case: Force lights off after 23:30.

onCondition - Turn on when condition is true
onCondition(condition)

Turns on when condition is true, skip otherwise. No time restrictions.

Use case: Simple condition-based activation without time constraints.

offCondition - Turn off when condition is true
offCondition(condition)

Turns off when condition is true, skip otherwise.

Use case: Turn off when light level drops.

onConditionDelayed - Turn on after stable condition
onConditionDelayed(condition, delaySeconds)

Turns on only after the condition has been continuously true for the specified delay.

Use case: Prevent flickering by requiring stable conditions before switching.

offConditionDelayed - Turn off after stable condition
offConditionDelayed(condition, delaySeconds)

Turns off only after the condition has been continuously true for the specified delay.

Use case: Keep lights on briefly after motion stops.

delayedOnOff - Hysteresis control
delayedOnOff(startTime, endTime, onDelayMinutes, offDelayMinutes, condition)

Hysteresis control within a time window. Waits onDelayMinutes before turning on and offDelayMinutes before turning off.

Use case: Prevent rapid switching when conditions fluctuate near thresholds.

solarHeaterControl - Solar-powered device control
solarHeaterControl(exportThreshold, importThreshold, minOnTime, minOffTime, extraCondition)

Specialized rule for solar-powered devices. Turns on when exporting enough power, turns off when importing too much. Respects minimum on/off times to protect equipment.

Use case: Water heater that runs on excess solar production.


Conditions

Light Sensor - lightBelow, lightAbove

Requires BH1750 sensor.

Function Description
lightBelow(threshold) True when light level < threshold (lux)
lightAbove(threshold) True when light level > threshold (lux)
Temperature - temperatureAbove, temperatureBelow

Requires BME280 sensor. Returns false if sensor not found.

Function Description
temperatureAbove(threshold) True when temperature > threshold (°C)
temperatureBelow(threshold) True when temperature < threshold (°C)
Humidity - humidityAbove, humidityBelow

Requires BME280 sensor. Returns false if sensor not found.

Function Description
humidityAbove(threshold) True when relative humidity > threshold (%)
humidityBelow(threshold) True when relative humidity < threshold (%)
Air Pressure - pressureAbove, pressureBelow

Requires BME280 sensor. Returns false if sensor not found. Pressure is in hPa (hectopascal). Standard sea level pressure is 1013.25 hPa.

Function Description
pressureAbove(threshold) True when pressure > threshold (hPa)
pressureBelow(threshold) True when pressure < threshold (hPa)
Phone Presence - phonePresent, phoneNotPresent
Function Description
phonePresent() True when phone is detected on the network
phoneNotPresent() True when phone is not detected
Day of Week - isWorkday, isWeekend, isMonday, etc.
Function Description
isWorkday() True Monday through Friday
isWeekend() True Saturday and Sunday
isMonday() True on Monday
isTuesday() True on Tuesday
isWednesday() True on Wednesday
isThursday() True on Thursday
isFriday() True on Friday
isSaturday() True on Saturday
isSunday() True on Sunday
Sun Position - sunUp, sunDown, beforeSunrise, afterSunrise, beforeSunset, afterSunset

Requires setLocation() to be called at startup.

Function Description
sunUp() True when sun is above the horizon
sunDown() True when sun is below the horizon
beforeSunrise(minutes) True during the X minutes before sunrise
afterSunrise(minutes) True during the X minutes after sunrise
beforeSunset(minutes) True during the X minutes before sunset
afterSunset(minutes) True during the X minutes after sunset

The before/after functions create a time window. For example, beforeSunset(30) is true starting 30 minutes before sunset and ending at sunset.

Power / Solar - powerSolarActive, powerProducing, powerConsuming, etc.

Requires P1 meter.

Function Description
powerSolarActive() True when solar is producing
powerProducing() True when exporting to grid
powerConsuming() True when importing from grid
powerProductionAbove(threshold) True when export > threshold (watts)
powerProductionBelow(threshold) True when export < threshold (watts)
Socket State - socketIsOn, socketIsOff

Check the state of other sockets. Socket numbers are 1-indexed.

Function Description
socketIsOn(socketNumber) True if the specified socket is currently on
socketIsOff(socketNumber) True if the specified socket is currently off
Duration - hasBeenOnFor, hasBeenOffFor

Check how long a socket has been in its current state.

Function Description
hasBeenOnFor(socketNumber, minutes) True if socket has been on for at least X minutes
hasBeenOffFor(socketNumber, minutes) True if socket has been off for at least X minutes
Time Window - timeWindowBetween, after
Function Description
timeWindowBetween(start, end) True when current time is within the window
after(time, duration) True after specified time (optional duration window)

Combinators

allOf - AND logic
allOf({condition1, condition2, ...})

Returns true only if all conditions are true.

anyOf - OR logic
anyOf({condition1, condition2, ...})

Returns true if any condition is true.

notOf - NOT logic
notOf(condition)

Inverts a condition.


Time Utilities

getSunriseTime / getSunsetTime - Get today's sun times
getSunriseTime()  // Returns "HH:MM"
getSunsetTime()   // Returns "HH:MM"

Get today's sunrise or sunset time as a string. Compatible with other time functions.

rndTime - Random time offset
rndTime(baseTime, maxMinutes, extraSeed)

Generates a random time offset from a base time. The randomness is consistent per day.

Use case: Simulate natural lighting patterns that vary slightly each day.

addMinutesToTime / addHoursToTime - Time arithmetic
addMinutesToTime(baseTime, minutesToAdd)
addHoursToTime(baseTime, hoursToAdd)

Simple time arithmetic. Returns a new time string in "HH:MM" format.

getDailyRandom / getDailyRandom60 / getDailyRandom24 - Daily consistent randoms
getDailyRandom(index)    // Returns 0-99
getDailyRandom60(index)  // Returns 0-59
getDailyRandom24(index)  // Returns 0-23

Pre-generated random numbers that stay consistent throughout the day.


System Functions

addRule - Register a rule
addRule(socketNumber, ruleName, evaluateFunction, timeWindow)

Registers a new rule for a socket. Rules are evaluated in the order they are added.

update - Main loop function
update()

Call this in your main loop. Polls physical states, evaluates all rules, and applies changes.

pollPhysicalStates - Refresh socket states
pollPhysicalStates()

Manually refresh all socket states from the hardware. Called automatically by update().

getSocketState - Get socket state
getSocketState(socketIndex)

Returns the physical state (true/false) of a socket by its index (0-indexed).

clearRules - Remove all rules
clearRules()

Removes all registered rules.


Example Usage

This repo is based on a state machine to control all devices.
Similar to continuous flow coding as in a PLC.
Although the rule system has its own logic, which I show below.
And it can be easily extended to include a lot of rules.
Without conflicting with the main state machine loop code.

void setup() {
  SmartRuleSystem::setLocation(52.37, 4.90, 0);  // Amsterdam
  setupRules();
}

// Note now with 8 wall sockets, timeouts may miss the rs.period(...) function to handle stuff
// A better way to code a period i now show in my below example, as demo code
// (the period function may be removed soon)

void setupRules() {
  auto &rs = ruleSystem;

  rs.addRule(1, "Good morning",
             rs.onCondition(
                 rs.allOf({rs.lightBelow(9),
                           rs.isWorkday(),
                           rs.socketIsOff(1), //so.. we only fire this when needed !
                           rs.timeWindowBetween("07:10", "07:44")})));

  // Morning OFF rule (07:45-08:00, workdays only)
  rs.addRule(1, "Leave for car",
             rs.offCondition(
                 rs.allOf({rs.isWorkday(),
                           rs.socketIsOn(1), //again only when needed !.
                           rs.timeWindowBetween("07:45", "08:00")})));

  // Evening ON rule (weekdays)
  rs.addRule(1, "Evening",
             rs.onCondition(
                 rs.allOf({rs.lightBelow(7),
                           rs.isWorkday(),
                           rs.socketIsOff(1),
                           rs.timeWindowBetween("17:15", eveningEndTime)})));

  // Evening OFF rule (weekdays)
  rs.addRule(1, "Good night",
             rs.offCondition(
                 rs.allOf({rs.isWorkday(),
                           rs.socketIsOn(1),
                           rs.timeWindowBetween(eveningEndTime,
                                                rs.addMinutesToTime(eveningEndTime, 6))})));

Hardware Requirements

Component Purpose Required
ESP32 Main controller Yes
WiFi Sockets Controllable devices (HomeWizard, etc.) Yes
BH1750 Light sensor Optional
BME280 Temperature/Humidity/Pressure Optional
P1 Meter Power monitoring (Dutch smart meter) Optional
OLED Display (SH1106 128x64) Status display
(also futering a basic website)
Phone on static ip WiFi Presence detection via ping Optional

Price estimate ~ 30 to 40 Euro's total.

So far this code controls 4 home connect switches, but you can code more.

About

ESP32 home automation powered by a fluent rule engine. Declarative rules for socket control based on time, light levels, phone presence, and solar production. Built with a clean DSL that reads like English.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors