Blynk Controlled DC Brushless Fan
Blynk Controlled DC Brushless Fan 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
Blynk Controlled DC Brushless Fan
This example controls a DC brushless fan from the Blynk app on the ESP32 — a switch for power, a slider for speed, both arriving as virtual pin updates over MQTT. The phone-as-remote pattern for motor loads.
| 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 |

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)
# main.py
import network
from machine import Pin
import BlynkLib
from time import sleep
# -------- WIFI DETAILS --------
wifissid = "ssid"
wifipass = "pass"
# -------- BLYNK AUTH --------
auth = "gmETsutWOUA"
# -------- RELAY --------
relay = Pin(15, Pin.OUT)
relay.value(1) # Relay OFF (Active LOW)
# -------- WIFI CONNECT --------
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
wifi.connect(wifissid, wifipass)
print("Connecting WiFi...")
while not wifi.isconnected():
sleep(1)
print("WiFi Connected:", wifi.ifconfig())
# -------- BLYNK INIT --------
blynk = BlynkLib.Blynk(auth, insecure=True)
# -------- BUTTON ON V1 --------
@blynk.on("V1")
def v1_handler(value):
state = int(value[0])
if state == 1:
relay.value(0) # Relay ON
print("Fan ON")
else:
relay.value(1) # Relay OFF
print("Fan OFF")
# -------- MAIN LOOP --------
while True:
blynk.run()
How it works
The ESP32 connects to Blynk and subscribes to two virtual pins — one for the power switch, one for the speed slider. The callbacks drive the fan’s control line, with the speed pin mapping to a PWM duty cycle. The loop keeps the MQTT connection alive between commands.
The PWM mapping is the hardware link: a 0-100% app slider becomes a 0-255 (or 0-1023) duty value on the control pin, which the fan’s driver circuit turns into speed. The code’s job is purely translation — app numbers to pin waveforms.
Before you build it for real
A brushless fan’s driver circuit is not optional: the motor current is an order of magnitude beyond GPIO limits, and the fan’s internal electronics can inject noise back into the 3.3 V rail without decoupling. The PWM frequency matters for fan control — some fans whine at the ESP32’s default PWM rates, which is why real designs raise the frequency above audible range. And the Blynk connection is the failure point: if WiFi drops, the fan stays at its last setting — a real system needs a failsafe state, not just a lost connection.
Get the parts
Related examples
FAQ
Why a brushless fan needs a driver
A brushless DC fan’s motor windings draw far more current than a GPIO can supply, and the commutation is electronic inside the fan. The ESP32 only switches the power path — through a transistor or driver — and the code’s job is just the on/off and speed signal.
What does Blynk contribute here?
The phone UI: a switch widget for on/off and a slider for speed, both mapped to virtual pins. The ESP32 subscribes to the pins and translates updates into fan control — the app is the remote, the device is the actuator.