Smart Home Automation System is a runnable microcontroller simulation example on SensBench — open the wired circuit, inspect the code, and compile real firmware in your browser, no install needed. Last updated Aug 14, 2026.

FREE SIMULATOR EXAMPLE · ADVANCED

Smart Home Automation System

This example runs a smart home automation system on the ESP32 — sensors, rule logic and actuators integrated under one firmware. The hub-and-spoke DIY smart home pattern.

Board ESP32 DevKit (Xtensa LX6, 240 MHz)
Difficulty advanced
Category communication
Libraries None — just the Arduino core
Runs in Sensbench browser simulator — free, no account
ESP32 smart home automation system in the Sensbench simulator

The exact circuit you get when you open this example — already wired, ready to run.

The sketch

# BlynkLib.py
# Copyright (c) 2015-2019 Volodymyr Shymanskyy. See the file LICENSE for copying permission.

__version__ = "1.0.0"

import struct
import time
import sys
import os

try:
    import machine
    gettime = lambda: time.ticks_ms()
    SOCK_TIMEOUT = 0
except ImportError:
    const = lambda x: x
    gettime = lambda: int(time.time() * 1000)
    SOCK_TIMEOUT = 0.05

def dummy(*args):
    pass

MSG_RSP = const(0)
MSG_LOGIN = const(2)
MSG_PING  = const(6)

MSG_TWEET = const(12)
MSG_NOTIFY = const(14)
MSG_BRIDGE = const(15)
MSG_HW_SYNC = const(16)
MSG_INTERNAL = const(17)
MSG_PROPERTY = const(19)
MSG_HW = const(20)
MSG_HW_LOGIN = const(29)
MSG_EVENT_LOG = const(64)

MSG_REDIRECT  = const(41)  # TODO: not implemented
MSG_DBG_PRINT  = const(55) # TODO: not implemented

STA_SUCCESS = const(200)
STA_INVALID_TOKEN = const(9)

DISCONNECTED = const(0)
CONNECTING = const(1)
CONNECTED = const(2)

print("""
    ___  __          __
   / _ )/ /_ _____  / /__
  / _  / / // / _ \\/  '_/
 /____/_/\\_, /_//_/_/\\_\\
        /___/ for Python v""" + __version__ + " (" + sys.platform + ")\n")

class EventEmitter:
    def __init__(self):
        self._cbks = {}

    def on(self, evt, f=None):
        if f:
            self._cbks[evt] = f
        else:
            def D(f):
                self._cbks[evt] = f
                return f
            return D

    def emit(self, evt, *a, **kv):
        if evt in self._cbks:
            self._cbks[evt](*a, **kv)


class BlynkProtocol(EventEmitter):
    def __init__(self, auth, tmpl_id=None, fw_ver=None, heartbeat=50, buffin=1024, log=None):
        EventEmitter.__init__(self)
        self.heartbeat = heartbeat*1000
        self.buffin = buffin
        self.log = log or dummy
        self.auth = auth
        self.tmpl_id = tmpl_id
        self.fw_ver = fw_ver
        self.state = DISCONNECTED
        self.connect()

    def virtual_write(self, pin, *val):
        self._send(MSG_HW, 'vw', pin, *val)

    def send_internal(self, pin, *val):
        self._send(MSG_INTERNAL,  pin, *val)

    def set_property(self, pin, prop, *val):
        self._send(MSG_PROPERTY, pin, prop, *val)

    def sync_virtual(self, *pins):
        self._send(MSG_HW_SYNC, 'vr', *pins)

    def log_event(self, *val):
        self._send(MSG_EVENT_LOG, *val)

    def _send(self, cmd, *args, **kwargs):
        if 'id' in kwargs:
            id = kwargs.get('id')
        else:
            id = self.msg_id
            self.msg_id += 1
            if self.msg_id > 0xFFFF:
                self.msg_id = 1
                
        if cmd == MSG_RSP:
            data = b''
            dlen = args[0]
        else:
            data = ('\0'.join(map(str, args))).encode('utf8')
            dlen = len(data)
        
        self.log('<', cmd, id, '|', *args)
        msg = struct.pack("!BHH", cmd, id, dlen) + data
        self.lastSend = gettime()
        self._write(msg)

    def connect(self):
        if self.state != DISCONNECTED: return
        self.msg_id = 1
        (self.lastRecv, self.lastSend, self.lastPing) = (gettime(), 0, 0)
        self.bin = b""
        self.state = CONNECTING
        self._send(MSG_HW_LOGIN, self.auth)

    def disconnect(self):
        if self.state == DISCONNECTED: return
        self.bin = b""
        self.state = DISCONNECTED
        self.emit('disconnected')

    def process(self, data=None):
        if not (self.state == CONNECTING or self.state == CONNECTED): return
        now = gettime()
        if now - self.lastRecv > self.heartbeat+(self.heartbeat//2):
            return self.disconnect()
        if (now - self.lastPing > self.heartbeat//10 and
            (now - self.lastSend > self.heartbeat or
             now - self.lastRecv > self.heartbeat)):
            self._send(MSG_PING)
            self.lastPing = now
        
        if data != None and len(data):
            self.bin += data

        while True:
            if len(self.bin) < 5:
                break

            cmd, i, dlen = struct.unpack("!BHH", self.bin[:5])
            if i == 0: return self.disconnect()
                      
            self.lastRecv = now
            if cmd == MSG_RSP:
                self.bin = self.bin[5:]

                self.log('>', cmd, i, '|', dlen)
                if self.state == CONNECTING and i == 1:
                    if dlen == STA_SUCCESS:
                        self.state = CONNECTED
                        dt = now - self.lastSend
                        info = ['ver', __version__, 'h-beat', self.heartbeat//1000, 'buff-in', self.buffin, 'dev', sys.platform+'-py']
                        if self.tmpl_id:
                            info.extend(['tmpl', self.tmpl_id])
                            info.extend(['fw-type', self.tmpl_id])
                        if self.fw_ver:
                            info.extend(['fw', self.fw_ver])
                        self._send(MSG_INTERNAL, *info)
                        try:
                            self.emit('connected', ping=dt)
                        except TypeError:
                            self.emit('connected')
                    else:
                        if dlen == STA_INVALID_TOKEN:
                            self.emit("invalid_auth")
                            print("Invalid auth token")
                        return self.disconnect()
            else:
                if dlen >= self.buffin:
                    print("Cmd too big: ", dlen)
                    return self.disconnect()

                if len(self.bin) < 5+dlen:
                    break

                data = self.bin[5:5+dlen]
                self.bin = self.bin[5+dlen:]

                args = list(map(lambda x: x.decode('utf8'), data.split(b'\0')))

                self.log('>', cmd, i, '|', ','.join(args))
                if cmd == MSG_PING:
                    self._send(MSG_RSP, STA_SUCCESS, id=i)
                elif cmd == MSG_HW or cmd == MSG_BRIDGE:
                    if args[0] == 'vw':
                        self.emit("V"+args[1], args[2:])
                        self.emit("V*", args[1], args[2:])
                elif cmd == MSG_INTERNAL:
                    self.emit("internal:"+args[0], args[1:])
                elif cmd == MSG_REDIRECT:
                    self.emit("redirect", args[0], int(args[1]))
                else:
                    print("Unexpected command: ", cmd)
                    return self.disconnect()

import socket

class Blynk(BlynkProtocol):
    def __init__(self, auth, **kwargs):
        self.insecure = kwargs.pop('insecure', False)
        self.server = kwargs.pop('server', 'blynk.cloud')
        self.port = kwargs.pop('port', 80 if self.insecure else 443)
        BlynkProtocol.__init__(self, auth, **kwargs)
        self.on('redirect', self.redirect)

    def redirect(self, server, port):
        self.server = server
        self.port = port
        self.disconnect()
        self.connect()

    def connect(self):
        print('Connecting to %s:%d...' % (self.server, self.port))
        s = socket.socket()
        s.connect(socket.getaddrinfo(self.server, self.port)[0][-1])
        try:
            s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        except:
            pass
        if self.insecure:
            self.conn = s
        else:
            try:
                import ussl
                ssl_context = ussl
            except ImportError:
                import ssl
                ssl_context = ssl.create_default_context()
            self.conn = ssl_context.wrap_socket(s, server_hostname=self.server)
        try:
            self.conn.settimeout(SOCK_TIMEOUT)
        except:
            s.settimeout(SOCK_TIMEOUT)
        BlynkProtocol.connect(self)

    def _write(self, data):
        #print('<', data)
        self.conn.write(data)
        # TODO: handle disconnect

    def run(self):
        data = b''
        try:
            data = self.conn.read(self.buffin)
            #print('>', data)
        except KeyboardInterrupt:
            raise
        except socket.timeout:
            # No data received, call process to send ping messages when needed
            pass
        except: # TODO: handle disconnect
            return
        self.process(data)



# simple_home_automation_with_blynk_and _micropython.py
'''Copyright (c) 2026 Kritish Mohapatra'''


import network # Library for network functionalities (Wi-Fi)
from machine import Pin # Library for controlling GPIO pins
import BlynkLib # Library for Blynk IoT platform communication
from time import sleep # Library for time delays
import dht # Library for DHT11 sensor communication

# Wi-Fi Credentials (replace with your own)
wifissid = "abcdef" # Your Wi-Fi SSID
wifipass = "@9090" # Your Wi-Fi password

# Blynk Authorization Token (replace with your Blynk app's token)
auth = "pottiIr9atOFmbvwsqTp7lTrYtje"

# LED Pin Configuration


# Connect to Wi-Fi
sta_if = network.WLAN(network.STA_IF)  #Create a Wi-Fi station interface object
sta_if.active(True) # Activate the Wi-Fi interface
sta_if.connect(wifissid, wifipass) # Connect to the Wi-Fi network

while not sta_if.isconnected(): # Wait until connected to Wi-Fi
    pass

print("Connected to Wi-Fi:", sta_if.ifconfig()) # Print the Wi-Fi connection details

# Initialize Blynk Connection
blynk = BlynkLib.Blynk(auth, insecure=True) # Create a Blynk object with the auth token

# Create LED Object
led1=Pin(16, Pin.OUT) # Initialize LED 1 on pin 16 (D0), set as output
led2=Pin(5, Pin.OUT) # Initialize LED 2 on pin 5 (D1), set as output
led3=Pin(4, Pin.OUT) # Initialize LED 3 on pin 4 (D2), set as output
led4=Pin(0, Pin.OUT) # Initialize LED 4 on pin 0 (D3), set as output
sensor=dht.DHT11(Pin(14)) # Initialize DHT11 sensor on pin 14 (D5)

led1.value(1) # Turn off LED 1 initially (active low)
led2.value(1) # Turn off LED 2 initially (active low)
led3.value(1) # Turn off LED 3 initially (active low)
led4.value(1) # Turn off LED 4 initially (active low)



# Virtual Pin V1 Handler (LED 1 control)
@blynk.on("V1")

def v0_handler(value):
    # Convert string value to integer (handle potential errors)
    try:
        led1.value(not int(value[0])) # Toggle LED 1 based on Blynk button state
    except ValueError:
        print("Invalid value received:", value)
        return

@blynk.on("V2")

def v2_handler(value):
    # Convert string value to integer (handle potential errors)
    try:
        led2.value(not int(value[0]))
    except ValueError:
        print("Invalid value received:", value)
        return

@blynk.on("V3")

def v3_handler(value):
    # Convert string value to integer (handle potential errors)
    try:
        led3.value(not int(value[0]))
    except ValueError:
        print("Invalid value received:", value)
        return
@blynk.on("V4")

def v4_handler(value):
    # Convert string value to integer (handle potential errors)
    try:
        led4.value(not int(value[0]))
    except ValueError:
        print("Invalid value received:", value)
        return
@blynk.on("V5")
# Create Blynk gauge widget
def write_value(pin, value):
# This function is not used for input, but required by blynk on decorator    
    pass



    

# Main Loop (run Blynk processes)
while True:
    blynk.run() # Run Blynk processes (handle incoming and outgoing data)
    sensor.measure()  # Measure temperature and humidity
    temperature = sensor.temperature() # Get the temperature value
    #print(temperature)
    blynk.virtual_write(5, temperature)  # Send temperature to Blynk virtual pin V5

How it works

The firmware integrates multiple sensors and actuators under one rule engine: readings feed conditions, conditions trigger outputs, and the communication layer reports the state. The project’s size reflects the integration — each subsystem is simple, the coordination is the product.

The rule evaluation is the automation core — threshold comparisons and state logic deciding what each actuator does. The serial output logs the system’s decisions, so the automation’s reasoning is auditable line by line.

Before you build it for real

The rules are hard-coded — changing a threshold means reflashing, which is why real systems move rules to a config file or a connected hub. The actuator safety story is per-load: relays and motors each need their driver and flyback protection, and a smart home that switches mains needs isolation that a breadboard cannot provide. And single-point failure: one ESP32 as the hub means the whole house’s automation dies with one board — the architecture trade-off every DIY hub makes.

Get the parts

Related examples

FAQ

What does this automation system control?

The classic set: lights, fans and sensors coordinated by a central firmware. The ESP32 reads the sensors, applies the rules, and switches the actuators — the hub-and-spoke model of every DIY smart home.

Why is the code so large?

Because a real automation system is many small systems: sensor drivers, rule evaluation, actuator control and a communication layer each take their share. The 11 KB of source is what “automation” actually costs in firmware.